Skip to content

Commit 282ac69

Browse files
authored
Merge pull request #310 from LoopKit/hard-cutoff-for-low-bg
If minBG is below 55, then shut off basal, even if eventualBG is in r…
2 parents 8e62c42 + d4bdf59 commit 282ac69

File tree

5 files changed

+58
-127
lines changed

5 files changed

+58
-127
lines changed

DoseMathTests/DoseMathTests.swift

Lines changed: 40 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ class RecommendTempBasalTests: XCTestCase {
9797
maxBasalRate: maxBasalRate,
9898
glucoseTargetRange: glucoseTargetRange,
9999
insulinSensitivity: insulinSensitivitySchedule,
100-
basalRateSchedule: basalRateSchedule,
101-
allowPredictiveTempBelowRange: false
100+
basalRateSchedule: basalRateSchedule
102101
)
103102

104103
XCTAssertNil(dose)
@@ -113,8 +112,7 @@ class RecommendTempBasalTests: XCTestCase {
113112
maxBasalRate: maxBasalRate,
114113
glucoseTargetRange: glucoseTargetRange,
115114
insulinSensitivity: insulinSensitivitySchedule,
116-
basalRateSchedule: basalRateSchedule,
117-
allowPredictiveTempBelowRange: false
115+
basalRateSchedule: basalRateSchedule
118116
)
119117

120118
XCTAssertNil(dose)
@@ -134,8 +132,7 @@ class RecommendTempBasalTests: XCTestCase {
134132
maxBasalRate: maxBasalRate,
135133
glucoseTargetRange: glucoseTargetRange,
136134
insulinSensitivity: insulinSensitivitySchedule,
137-
basalRateSchedule: basalRateSchedule,
138-
allowPredictiveTempBelowRange: false
135+
basalRateSchedule: basalRateSchedule
139136
)
140137

141138
XCTAssertEqual(0, dose!.rate)
@@ -151,21 +148,7 @@ class RecommendTempBasalTests: XCTestCase {
151148
maxBasalRate: maxBasalRate,
152149
glucoseTargetRange: glucoseTargetRange,
153150
insulinSensitivity: insulinSensitivitySchedule,
154-
basalRateSchedule: basalRateSchedule,
155-
allowPredictiveTempBelowRange: false
156-
)
157-
158-
XCTAssertEqual(0, dose!.rate)
159-
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
160-
161-
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
162-
atDate: glucose.first!.startDate,
163-
lastTempBasal: nil,
164-
maxBasalRate: maxBasalRate,
165-
glucoseTargetRange: glucoseTargetRange,
166-
insulinSensitivity: insulinSensitivitySchedule,
167-
basalRateSchedule: basalRateSchedule,
168-
allowPredictiveTempBelowRange: true
151+
basalRateSchedule: basalRateSchedule
169152
)
170153

171154
XCTAssertNil(dose)
@@ -184,8 +167,7 @@ class RecommendTempBasalTests: XCTestCase {
184167
maxBasalRate: maxBasalRate,
185168
glucoseTargetRange: glucoseTargetRange,
186169
insulinSensitivity: insulinSensitivitySchedule,
187-
basalRateSchedule: basalRateSchedule,
188-
allowPredictiveTempBelowRange: true
170+
basalRateSchedule: basalRateSchedule
189171
)
190172

191173
XCTAssertEqual(0, dose!.rate)
@@ -195,83 +177,26 @@ class RecommendTempBasalTests: XCTestCase {
195177
func testCorrectLowAtMin() {
196178
let glucose = loadGlucoseValueFixture("recommend_temp_basal_correct_low_at_min")
197179

198-
var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
199-
atDate: glucose.first!.startDate,
200-
lastTempBasal: nil,
201-
maxBasalRate: maxBasalRate,
202-
glucoseTargetRange: glucoseTargetRange,
203-
insulinSensitivity: insulinSensitivitySchedule,
204-
basalRateSchedule: basalRateSchedule,
205-
allowPredictiveTempBelowRange: false
206-
)
207-
208-
XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0)
209-
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
210-
211-
// Ignore due to existing dose
212-
var lastTempBasal = DoseEntry(
213-
type: .tempBasal,
214-
startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -11)),
215-
endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 19)),
216-
value: 0.125,
217-
unit: .unitsPerHour
218-
)
219-
220-
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
221-
atDate: glucose.first!.startDate,
222-
lastTempBasal: lastTempBasal,
223-
maxBasalRate: maxBasalRate,
224-
glucoseTargetRange: glucoseTargetRange,
225-
insulinSensitivity: insulinSensitivitySchedule,
226-
basalRateSchedule: basalRateSchedule,
227-
allowPredictiveTempBelowRange: false
228-
)
229-
230-
XCTAssertNil(dose)
231-
232180
// Cancel existing dose
233-
lastTempBasal = DoseEntry(
234-
type: .tempBasal,
235-
startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -11)),
236-
endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 19)),
237-
value: 1.225,
238-
unit: .unitsPerHour
239-
)
240-
241-
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
242-
atDate: glucose.first!.startDate,
243-
lastTempBasal: lastTempBasal,
244-
maxBasalRate: maxBasalRate,
245-
glucoseTargetRange: glucoseTargetRange,
246-
insulinSensitivity: insulinSensitivitySchedule,
247-
basalRateSchedule: basalRateSchedule,
248-
allowPredictiveTempBelowRange: false
249-
)
250-
251-
XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0)
252-
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
253-
254-
// Continue existing dose
255-
lastTempBasal = DoseEntry(
181+
var lastTempBasal = DoseEntry(
256182
type: .tempBasal,
257183
startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -21)),
258184
endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 9)),
259185
value: 0.125,
260186
unit: .unitsPerHour
261187
)
262188

263-
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
189+
var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
264190
atDate: glucose.first!.startDate,
265191
lastTempBasal: lastTempBasal,
266192
maxBasalRate: maxBasalRate,
267193
glucoseTargetRange: glucoseTargetRange,
268194
insulinSensitivity: insulinSensitivitySchedule,
269-
basalRateSchedule: basalRateSchedule,
270-
allowPredictiveTempBelowRange: false
195+
basalRateSchedule: basalRateSchedule
271196
)
272197

273-
XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0)
274-
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
198+
XCTAssertEqual(0, dose!.rate)
199+
XCTAssertEqual(TimeInterval(minutes: 0), dose!.duration)
275200

276201
// Allow predictive temp below range
277202
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
@@ -280,8 +205,7 @@ class RecommendTempBasalTests: XCTestCase {
280205
maxBasalRate: maxBasalRate,
281206
glucoseTargetRange: glucoseTargetRange,
282207
insulinSensitivity: insulinSensitivitySchedule,
283-
basalRateSchedule: basalRateSchedule,
284-
allowPredictiveTempBelowRange: true
208+
basalRateSchedule: basalRateSchedule
285209
)
286210

287211
XCTAssertNil(dose)
@@ -300,8 +224,7 @@ class RecommendTempBasalTests: XCTestCase {
300224
maxBasalRate: maxBasalRate,
301225
glucoseTargetRange: glucoseTargetRange,
302226
insulinSensitivity: insulinSensitivitySchedule,
303-
basalRateSchedule: basalRateSchedule,
304-
allowPredictiveTempBelowRange: true
227+
basalRateSchedule: basalRateSchedule
305228
)
306229

307230
XCTAssertEqual(0, dose!.rate)
@@ -317,8 +240,7 @@ class RecommendTempBasalTests: XCTestCase {
317240
maxBasalRate: maxBasalRate,
318241
glucoseTargetRange: glucoseTargetRange,
319242
insulinSensitivity: insulinSensitivitySchedule,
320-
basalRateSchedule: basalRateSchedule,
321-
allowPredictiveTempBelowRange: false
243+
basalRateSchedule: basalRateSchedule
322244
)
323245

324246
XCTAssertEqual(0, dose!.rate)
@@ -328,28 +250,14 @@ class RecommendTempBasalTests: XCTestCase {
328250
func testStartLowEndHigh() {
329251
let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high")
330252

331-
var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
332-
atDate: glucose.first!.startDate,
333-
lastTempBasal: nil,
334-
maxBasalRate: maxBasalRate,
335-
glucoseTargetRange: glucoseTargetRange,
336-
insulinSensitivity: insulinSensitivitySchedule,
337-
basalRateSchedule: basalRateSchedule,
338-
allowPredictiveTempBelowRange: false
339-
)
340-
341-
XCTAssertEqual(0, dose!.rate)
342-
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
343-
344253
// Allow predictive temp below range
345-
dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
254+
var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
346255
atDate: glucose.first!.startDate,
347256
lastTempBasal: nil,
348257
maxBasalRate: maxBasalRate,
349258
glucoseTargetRange: glucoseTargetRange,
350259
insulinSensitivity: insulinSensitivitySchedule,
351-
basalRateSchedule: basalRateSchedule,
352-
allowPredictiveTempBelowRange: true
260+
basalRateSchedule: basalRateSchedule
353261
)
354262

355263
XCTAssertNil(dose)
@@ -368,8 +276,7 @@ class RecommendTempBasalTests: XCTestCase {
368276
maxBasalRate: maxBasalRate,
369277
glucoseTargetRange: glucoseTargetRange,
370278
insulinSensitivity: insulinSensitivitySchedule,
371-
basalRateSchedule: basalRateSchedule,
372-
allowPredictiveTempBelowRange: true
279+
basalRateSchedule: basalRateSchedule
373280
)
374281

375282
XCTAssertEqual(0, dose!.rate)
@@ -385,8 +292,7 @@ class RecommendTempBasalTests: XCTestCase {
385292
maxBasalRate: maxBasalRate,
386293
glucoseTargetRange: glucoseTargetRange,
387294
insulinSensitivity: insulinSensitivitySchedule,
388-
basalRateSchedule: basalRateSchedule,
389-
allowPredictiveTempBelowRange: false
295+
basalRateSchedule: basalRateSchedule
390296
)
391297

392298
XCTAssertEqual(3.0, dose!.rate)
@@ -402,8 +308,7 @@ class RecommendTempBasalTests: XCTestCase {
402308
maxBasalRate: maxBasalRate,
403309
glucoseTargetRange: glucoseTargetRange,
404310
insulinSensitivity: insulinSensitivitySchedule,
405-
basalRateSchedule: basalRateSchedule,
406-
allowPredictiveTempBelowRange: false
311+
basalRateSchedule: basalRateSchedule
407312
)
408313

409314
XCTAssertEqualWithAccuracy(1.425, dose!.rate, accuracy: 1.0 / 40.0)
@@ -419,8 +324,7 @@ class RecommendTempBasalTests: XCTestCase {
419324
maxBasalRate: maxBasalRate,
420325
glucoseTargetRange: glucoseTargetRange,
421326
insulinSensitivity: insulinSensitivitySchedule,
422-
basalRateSchedule: basalRateSchedule,
423-
allowPredictiveTempBelowRange: false
327+
basalRateSchedule: basalRateSchedule
424328
)
425329

426330
XCTAssertEqualWithAccuracy(1.475, dose!.rate, accuracy: 1.0 / 40.0)
@@ -436,8 +340,7 @@ class RecommendTempBasalTests: XCTestCase {
436340
maxBasalRate: maxBasalRate,
437341
glucoseTargetRange: glucoseTargetRange,
438342
insulinSensitivity: self.insulinSensitivitySchedule,
439-
basalRateSchedule: basalRateSchedule,
440-
allowPredictiveTempBelowRange: false
343+
basalRateSchedule: basalRateSchedule
441344
)
442345

443346
XCTAssertEqual(3.0, dose!.rate)
@@ -452,22 +355,37 @@ class RecommendTempBasalTests: XCTestCase {
452355
maxBasalRate: maxBasalRate,
453356
glucoseTargetRange: glucoseTargetRange,
454357
insulinSensitivity: insulinSensitivitySchedule,
455-
basalRateSchedule: basalRateSchedule,
456-
allowPredictiveTempBelowRange: false
358+
basalRateSchedule: basalRateSchedule
457359
)
458360

459361
XCTAssertEqualWithAccuracy(2.975, dose!.rate, accuracy: 1.0 / 40.0)
460362
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
461363
}
462364

365+
func testVeryLowAndRising() {
366+
let glucose = loadGlucoseValueFixture("recommend_tamp_basal_very_low_end_in_range")
367+
368+
let dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose,
369+
atDate: glucose.first!.startDate,
370+
lastTempBasal: nil,
371+
maxBasalRate: maxBasalRate,
372+
glucoseTargetRange: glucoseTargetRange,
373+
insulinSensitivity: self.insulinSensitivitySchedule,
374+
basalRateSchedule: basalRateSchedule
375+
)
376+
377+
XCTAssertEqual(0.0, dose!.rate)
378+
XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration)
379+
}
380+
381+
463382
func testNoInputGlucose() {
464383
let dose = DoseMath.recommendTempBasalFromPredictedGlucose([],
465384
lastTempBasal: nil,
466385
maxBasalRate: maxBasalRate,
467386
glucoseTargetRange: glucoseTargetRange,
468387
insulinSensitivity: insulinSensitivitySchedule,
469-
basalRateSchedule: basalRateSchedule,
470-
allowPredictiveTempBelowRange: false
388+
basalRateSchedule: basalRateSchedule
471389
)
472390

473391
XCTAssertNil(dose)
@@ -728,4 +646,5 @@ class RecommendBolusTests: XCTestCase {
728646

729647
XCTAssertEqual(0, dose)
730648
}
649+
731650
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{"date": "2015-07-19T18:00:00", "amount": 60},
3+
{"date": "2015-07-19T18:30:00", "amount": 50},
4+
{"date": "2015-07-19T19:00:00", "amount": 60},
5+
{"date": "2015-07-19T19:30:00", "amount": 70},
6+
{"date": "2015-07-19T20:00:00", "amount": 100}
7+
]

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; };
181181
4FC8C8021DEB943800A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; };
182182
C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */; };
183+
C12F21A71DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json */; };
183184
C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; };
184185
C17884631D51A7A400405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884621D51A7A400405663 /* BatteryIndicator.swift */; };
185186
C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; };
@@ -448,6 +449,7 @@
448449
4F70C2111DE900EA006380B7 /* StatusExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusExtensionContext.swift; sourceTree = "<group>"; };
449450
4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+StatusExtension.swift"; sourceTree = "<group>"; };
450451
C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NightscoutUploadKit.framework; path = Carthage/Build/iOS/NightscoutUploadKit.framework; sourceTree = "<group>"; };
452+
C12F21A61DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_tamp_basal_very_low_end_in_range.json; sourceTree = "<group>"; };
451453
C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = "<group>"; };
452454
C17884621D51A7A400405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = "<group>"; };
453455
C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = "<group>"; };
@@ -701,6 +703,7 @@
701703
43E2D8E91D20C0DB004DA55F /* recommend_temp_basal_start_high_end_low.json */,
702704
43E2D8EA1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_high.json */,
703705
43E2D8EB1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_in_range.json */,
706+
C12F21A61DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json */,
704707
);
705708
path = Fixtures;
706709
sourceTree = "<group>";
@@ -1102,6 +1105,7 @@
11021105
43E2D8EF1D20C0DB004DA55F /* recommend_temp_basal_high_and_falling.json in Resources */,
11031106
43E2D8ED1D20C0DB004DA55F /* recommend_temp_basal_correct_low_at_min.json in Resources */,
11041107
43E2D8F01D20C0DB004DA55F /* recommend_temp_basal_high_and_rising.json in Resources */,
1108+
C12F21A71DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json in Resources */,
11051109
43E2D8F11D20C0DB004DA55F /* recommend_temp_basal_in_range_and_rising.json in Resources */,
11061110
43E2D8EE1D20C0DB004DA55F /* recommend_temp_basal_flat_and_high.json in Resources */,
11071111
43E2D8F31D20C0DB004DA55F /* recommend_temp_basal_start_high_end_in_range.json in Resources */,

Loop/Managers/DoseMath.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ struct DoseMath {
5151
- parameter glucoseTargetRange: The schedule of target glucose ranges
5252
- parameter insulinSensitivity: The schedule of insulin sensitivities, in Units of insulin per glucose-unit
5353
- parameter basalRateSchedule: The schedule of basal rates
54-
- parameter allowPredictiveTempBelowRange: Whether to allow a higher basal rate, up to the normal scheduled rate, than is necessary to correct the lowest predicted value, if the eventual predicted value is in or above the target range. Defaults to false.
5554

5655
- returns: The recommended basal rate and duration
5756
*/
@@ -61,8 +60,7 @@ struct DoseMath {
6160
maxBasalRate: Double,
6261
glucoseTargetRange: GlucoseRangeSchedule,
6362
insulinSensitivity: InsulinSensitivitySchedule,
64-
basalRateSchedule: BasalRateSchedule,
65-
allowPredictiveTempBelowRange: Bool
63+
basalRateSchedule: BasalRateSchedule
6664
) -> (rate: Double, duration: TimeInterval)? {
6765
guard glucose.count > 1 else {
6866
return nil
@@ -79,7 +77,11 @@ struct DoseMath {
7977
var rate: Double?
8078
var duration = TimeInterval(minutes: 30)
8179

82-
if minGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) < minGlucoseTargets.minValue && (!allowPredictiveTempBelowRange || eventualGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) <= eventualGlucoseTargets.minValue) {
80+
let alwaysLowTempBGThreshold: Double = 55 // mg/dL
81+
82+
if minGlucose.quantity.doubleValue(for: HKUnit.milligramsPerDeciliterUnit()) <= alwaysLowTempBGThreshold {
83+
rate = 0
84+
} else if minGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) < minGlucoseTargets.minValue && eventualGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) <= eventualGlucoseTargets.minValue {
8385
let targetGlucose = HKQuantity(unit: glucoseTargetRange.unit, doubleValue: (minGlucoseTargets.minValue + minGlucoseTargets.maxValue) / 2)
8486
rate = calculateTempBasalRateForGlucose(minGlucose.quantity,
8587
toTargetGlucose: targetGlucose,

Loop/Managers/LoopDataManager.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,8 +559,7 @@ final class LoopDataManager {
559559
maxBasalRate: maxBasal,
560560
glucoseTargetRange: glucoseTargetRange,
561561
insulinSensitivity: insulinSensitivity,
562-
basalRateSchedule: basalRates,
563-
allowPredictiveTempBelowRange: true
562+
basalRateSchedule: basalRates
564563
)
565564
else {
566565
recommendedTempBasal = nil

0 commit comments

Comments
 (0)