-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMy BolusStateModel.swift
321 lines (270 loc) · 13.3 KB
/
My BolusStateModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
import LoopKit
import CoreData
import SwiftUI
import Swinject
extension Bolus {
final class StateModel: BaseStateModel<Provider> {
@Injected() var unlockmanager: UnlockManager!
@Injected() var apsManager: APSManager!
@Injected() var broadcaster: Broadcaster!
@Injected() var pumpHistoryStorage: PumpHistoryStorage!
// added for bolus calculator
@Injected() var settings: SettingsManager!
@Injected() var nsManager: NightscoutManager!
@Published var suggestion: Suggestion?
@Published var predictions: Predictions?
@Published var amount: Decimal = 0
@Published var insulinRecommended: Decimal = 0
@Published var insulinRequired: Decimal = 0
@Published var units: GlucoseUnits = .mmolL
@Published var percentage: Decimal = 0
@Published var threshold: Decimal = 0
@Published var maxBolus: Decimal = 0
@Published var errorString: Decimal = 0
@Published var evBG: Int = 0
@Published var insulin: Decimal = 0
@Published var isf: Decimal = 0
@Published var error: Bool = false
@Published var minGuardBG: Decimal = 0
@Published var minDelta: Decimal = 0
@Published var expectedDelta: Decimal = 0
@Published var minPredBG: Decimal = 0
@Published var waitForSuggestion: Bool = false
@Published var carbRatio: Decimal = 0
var waitForSuggestionInitial: Bool = false
// added for bolus calculator
@Published var recentGlucose: BloodGlucose?
@Published var target: Decimal = 0
@Published var cob: Decimal = 0
@Published var iob: Decimal = 0
@Published var currentBG: Decimal = 0
@Published var fifteenMinInsulin: Decimal = 0
@Published var deltaBG: Decimal = 0
@Published var targetDifferenceInsulin: Decimal = 0
@Published var wholeCobInsulin: Decimal = 0
@Published var iobInsulinReduction: Decimal = 0
@Published var wholeCalc: Decimal = 0
@Published var roundedWholeCalc: Decimal = 0
@Published var insulinCalculated: Decimal = 0
@Published var roundedInsulinCalculated: Decimal = 0
@Published var fraction: Decimal = 0
@Published var useCalc: Bool = false
@Published var basal: Decimal = 0
@Published var fattyMeals: Bool = false
@Published var fattyMealFactor: Decimal = 0
@Published var useFattyMealCorrectionFactor: Bool = false
@Published var displayPredictions: Bool = true
@Published var LatestCarbEntryInsulin: Decimal = 0
@Published var roundedLatestCarbEntryInsulin: Decimal = 0
@Published var log_roundedWholeCalc: Decimal = 0
@Published var wholeCalc_carbs: Decimal = 0
@Published var meal: [CarbsEntry]?
@Published var carbs: Decimal = 0
@Published var fat: Decimal = 0
@Published var protein: Decimal = 0
@Published var note: String = ""
@FetchRequest(
entity: Meals.entity(),
sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false)]
) var Latestmeal: FetchedResults<Meals>
@Published var logMessage: String = ""
@Published var latestCarbValue: Decimal = 0
@Published var carbs2: Decimal = 0
override func subscribe() {
setupInsulinRequired()
broadcaster.register(SuggestionObserver.self, observer: self)
units = settingsManager.settings.units
percentage = settingsManager.settings.insulinReqPercentage
threshold = provider.suggestion?.threshold ?? 0
maxBolus = provider.pumpSettings().maxBolus
// profileCarbRatio = provider.altCalcProfile().carb_ratio
// added
fraction = settings.settings.overrideFactor
useCalc = settings.settings.useCalc
fattyMeals = settings.settings.fattyMeals
fattyMealFactor = settings.settings.fattyMealFactor
displayPredictions = settings.settings.displayPredictions
if waitForSuggestionInitial {
apsManager.determineBasal()
.receive(on: DispatchQueue.main)
.sink { [weak self] ok in
guard let self = self else { return }
if !ok {
self.waitForSuggestion = false
self.insulinRequired = 0
self.insulinRecommended = 0
}
}.store(in: &lifetime)
}
if let notNilSugguestion = provider.suggestion {
suggestion = notNilSugguestion
if let notNilPredictions = suggestion?.predictions {
predictions = notNilPredictions
}
}
}
func getDeltaBG() {
let glucose = provider.fetchGlucose()
guard glucose.count >= 3 else { return }
let lastGlucose = glucose.first?.glucose ?? 0
let thirdLastGlucose = glucose[2]
let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose.glucose)
deltaBG = delta
}
func calculateInsulin(carbs2: Decimal) -> Decimal {
var conversion: Decimal = 1.0
if units == .mmolL {
conversion = 0.0555
}
// insulin needed for the current blood glucose
let targetDifference = (currentBG - target) * conversion
targetDifferenceInsulin = targetDifference / isf
// more or less insulin because of bg trend in the last 15 minutes (DISABLED)
// fifteenMinInsulin = (deltaBG * conversion) / isf
// determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
wholeCobInsulin = cob / carbRatio
// determine how much the calculator reduces/ increases the bolus because of IOB
iobInsulinReduction = (-1) * iob
// adding everything together for COB
// add a calc for the case that no fifteenMinInsulin is available
if deltaBG != 0 {
wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinInsulin)
} else {
// add (rare) case that no glucose value is available -> maybe display warning?
// if no bg is available, ?? sets its value to 0
if currentBG == 0 {
wholeCalc = (iobInsulinReduction + wholeCobInsulin)
} else {
wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
}
}
if carbs2 > 0 {
// For max_cob = 60
if carbs2 >= 60 {
//calculate insulin for latest carb entry
LatestCarbEntryInsulin = 60 / carbRatio
wholeCalc_carbs = LatestCarbEntryInsulin
} else {
//calculate insulin for latest carb entry
LatestCarbEntryInsulin = carbs2 / carbRatio
wholeCalc_carbs = LatestCarbEntryInsulin
}
// logging and rounding LatestCarbEntryInsulin and wholecalc
let LatestCarbEntryInsulinAsDouble = Double(LatestCarbEntryInsulin)
roundedLatestCarbEntryInsulin = Decimal(round(100 * LatestCarbEntryInsulinAsDouble) / 100)
let Log_wholeCalcAsDouble = Double(wholeCalc)
log_roundedWholeCalc = Decimal(round(100 * Log_wholeCalcAsDouble) / 100)
logMessage = "Carbs:\(carbs2) -> \(roundedLatestCarbEntryInsulin)\nwholeCalc:\(log_roundedWholeCalc)"
wholeCalc = min(wholeCalc, wholeCalc_carbs)
} else {
logMessage = "\nNo New Carbs (carbs2=0)"
}
// rounding
let wholeCalcAsDouble = Double(wholeCalc)
roundedWholeCalc = Decimal(round(100 * wholeCalcAsDouble) / 100)
// apply custom factor at the end of the calculations
let result = wholeCalc * fraction
// apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
if useFattyMealCorrectionFactor {
insulinCalculated = result * fattyMealFactor
} else {
insulinCalculated = result
}
// display no negative insulinCalculated
insulinCalculated = max(insulinCalculated, 0)
let insulinCalculatedAsDouble = Double(insulinCalculated)
roundedInsulinCalculated = Decimal(round(100 * insulinCalculatedAsDouble) / 100)
insulinCalculated = min(insulinCalculated, maxBolus)
return apsManager
.roundBolus(amount: max(insulinCalculated, 0))
}
func add() {
guard amount > 0 else {
showModal(for: nil)
return
}
let maxAmount = Double(min(amount, provider.pumpSettings().maxBolus))
unlockmanager.unlock()
.sink { _ in } receiveValue: { [weak self] _ in
guard let self = self else { return }
self.apsManager.enactBolus(amount: maxAmount, isSMB: false)
self.showModal(for: nil)
}
.store(in: &lifetime)
}
func setupInsulinRequired() {
DispatchQueue.main.async {
self.insulinRequired = self.provider.suggestion?.insulinReq ?? 0
var conversion: Decimal = 1.0
if self.units == .mmolL {
conversion = 0.0555
}
self.evBG = self.provider.suggestion?.eventualBG ?? 0
self.insulin = self.provider.suggestion?.insulinForManualBolus ?? 0
self.target = self.provider.suggestion?.current_target ?? 0
self.isf = self.provider.suggestion?.isf ?? 0
self.iob = self.provider.suggestion?.iob ?? 0
self.currentBG = (self.provider.suggestion?.bg ?? 0)
self.cob = self.provider.suggestion?.cob ?? 0
self.basal = self.provider.suggestion?.rate ?? 0
self.carbRatio = self.provider.suggestion?.carbRatio ?? 0
if self.settingsManager.settings.insulinReqPercentage != 100 {
self.insulinRecommended = self.insulin * (self.settingsManager.settings.insulinReqPercentage / 100)
} else { self.insulinRecommended = self.insulin }
self.errorString = self.provider.suggestion?.manualBolusErrorString ?? 0
if self.errorString != 0 {
self.error = true
self.minGuardBG = (self.provider.suggestion?.minGuardBG ?? 0) * conversion
self.minDelta = (self.provider.suggestion?.minDelta ?? 0) * conversion
self.expectedDelta = (self.provider.suggestion?.expectedDelta ?? 0) * conversion
self.minPredBG = (self.provider.suggestion?.minPredBG ?? 0) * conversion
} else { self.error = false }
self.insulinRecommended = self.apsManager
.roundBolus(amount: max(self.insulinRecommended, 0))
// Disabling the intial insulin calculation to require an OnClick in the View code. Allows carb entries to be assessed in the calculation rather than only COB
// if self.useCalc {
// self.getDeltaBG()
// self.insulinCalculated = self.calculateInsulin()
// }
}
}
func backToCarbsView(complexEntry: Bool, _ meal: FetchedResults<Meals>, override: Bool) {
delete(deleteTwice: complexEntry, meal: meal)
showModal(for: .addCarbs(editMode: complexEntry, override: override))
}
func delete(deleteTwice: Bool, meal: FetchedResults<Meals>) {
guard let meals = meal.first else {
return
}
var date = Date()
if let mealDate = meals.actualDate {
date = mealDate
} else if let mealdate = meals.createdAt {
date = mealdate
}
let mealArray = DataTable.Treatment(
units: units,
type: .carbs,
date: date,
id: meals.id ?? "",
isFPU: deleteTwice ? true : false,
fpuID: deleteTwice ? (meals.fpuID ?? "") : ""
)
print(
"meals 2: ID: " + mealArray.id.description + " FPU ID: " + (mealArray.fpuID ?? "")
.description
)
if deleteTwice {
nsManager.deleteCarbs(mealArray, complexMeal: true)
}
}
}
}
extension Bolus.StateModel: SuggestionObserver {
func suggestionDidUpdate(_: Suggestion) {
DispatchQueue.main.async {
self.waitForSuggestion = false
}
setupInsulinRequired()
}
}