From d4bdf59f46cf21024fef7cc8eedeaec8823a11f9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 8 Dec 2016 23:47:41 -0600 Subject: [PATCH] If minBG is below 55, then shut off basal, even if eventualBG is in range. Simplify code by removing allowPredictiveTempBelowRange flag. --- DoseMathTests/DoseMathTests.swift | 161 +++++------------- ...mend_tamp_basal_very_low_end_in_range.json | 7 + Loop.xcodeproj/project.pbxproj | 4 + Loop/Managers/DoseMath.swift | 10 +- Loop/Managers/LoopDataManager.swift | 3 +- 5 files changed, 58 insertions(+), 127 deletions(-) create mode 100644 DoseMathTests/Fixtures/recommend_tamp_basal_very_low_end_in_range.json diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 210fe45bba..4a52f5b772 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -97,8 +97,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -113,8 +112,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -134,8 +132,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(0, dose!.rate) @@ -151,21 +148,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false - ) - - XCTAssertEqual(0, dose!.rate) - XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) - - dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, - atDate: glucose.first!.startDate, - lastTempBasal: nil, - maxBasalRate: maxBasalRate, - glucoseTargetRange: glucoseTargetRange, - insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -184,8 +167,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(0, dose!.rate) @@ -195,64 +177,8 @@ class RecommendTempBasalTests: XCTestCase { func testCorrectLowAtMin() { let glucose = loadGlucoseValueFixture("recommend_temp_basal_correct_low_at_min") - var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, - atDate: glucose.first!.startDate, - lastTempBasal: nil, - maxBasalRate: maxBasalRate, - glucoseTargetRange: glucoseTargetRange, - insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false - ) - - XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0) - XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) - - // Ignore due to existing dose - var lastTempBasal = DoseEntry( - type: .tempBasal, - startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -11)), - endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 19)), - value: 0.125, - unit: .unitsPerHour - ) - - dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, - atDate: glucose.first!.startDate, - lastTempBasal: lastTempBasal, - maxBasalRate: maxBasalRate, - glucoseTargetRange: glucoseTargetRange, - insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false - ) - - XCTAssertNil(dose) - // Cancel existing dose - lastTempBasal = DoseEntry( - type: .tempBasal, - startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -11)), - endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 19)), - value: 1.225, - unit: .unitsPerHour - ) - - dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, - atDate: glucose.first!.startDate, - lastTempBasal: lastTempBasal, - maxBasalRate: maxBasalRate, - glucoseTargetRange: glucoseTargetRange, - insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false - ) - - XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0) - XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) - - // Continue existing dose - lastTempBasal = DoseEntry( + var lastTempBasal = DoseEntry( type: .tempBasal, startDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: -21)), endDate: glucose.first!.startDate.addingTimeInterval(TimeInterval(minutes: 9)), @@ -260,18 +186,17 @@ class RecommendTempBasalTests: XCTestCase { unit: .unitsPerHour ) - dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, + var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) - XCTAssertEqualWithAccuracy(0.125, dose!.rate, accuracy: 1.0 / 40.0) - XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) + XCTAssertEqual(0, dose!.rate) + XCTAssertEqual(TimeInterval(minutes: 0), dose!.duration) // Allow predictive temp below range dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, @@ -280,8 +205,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -300,8 +224,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(0, dose!.rate) @@ -317,8 +240,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(0, dose!.rate) @@ -328,28 +250,14 @@ class RecommendTempBasalTests: XCTestCase { func testStartLowEndHigh() { let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high") - var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, - atDate: glucose.first!.startDate, - lastTempBasal: nil, - maxBasalRate: maxBasalRate, - glucoseTargetRange: glucoseTargetRange, - insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false - ) - - XCTAssertEqual(0, dose!.rate) - XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) - // Allow predictive temp below range - dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, + var dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -368,8 +276,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(0, dose!.rate) @@ -385,8 +292,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(3.0, dose!.rate) @@ -402,8 +308,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqualWithAccuracy(1.425, dose!.rate, accuracy: 1.0 / 40.0) @@ -419,8 +324,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqualWithAccuracy(1.475, dose!.rate, accuracy: 1.0 / 40.0) @@ -436,8 +340,7 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: self.insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqual(3.0, dose!.rate) @@ -452,22 +355,37 @@ class RecommendTempBasalTests: XCTestCase { maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertEqualWithAccuracy(2.975, dose!.rate, accuracy: 1.0 / 40.0) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } + func testVeryLowAndRising() { + let glucose = loadGlucoseValueFixture("recommend_tamp_basal_very_low_end_in_range") + + let dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, + atDate: glucose.first!.startDate, + lastTempBasal: nil, + maxBasalRate: maxBasalRate, + glucoseTargetRange: glucoseTargetRange, + insulinSensitivity: self.insulinSensitivitySchedule, + basalRateSchedule: basalRateSchedule + ) + + XCTAssertEqual(0.0, dose!.rate) + XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) + } + + func testNoInputGlucose() { let dose = DoseMath.recommendTempBasalFromPredictedGlucose([], lastTempBasal: nil, maxBasalRate: maxBasalRate, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule, - allowPredictiveTempBelowRange: false + basalRateSchedule: basalRateSchedule ) XCTAssertNil(dose) @@ -728,4 +646,5 @@ class RecommendBolusTests: XCTestCase { XCTAssertEqual(0, dose) } + } diff --git a/DoseMathTests/Fixtures/recommend_tamp_basal_very_low_end_in_range.json b/DoseMathTests/Fixtures/recommend_tamp_basal_very_low_end_in_range.json new file mode 100644 index 0000000000..04f31ba3bf --- /dev/null +++ b/DoseMathTests/Fixtures/recommend_tamp_basal_very_low_end_in_range.json @@ -0,0 +1,7 @@ + [ + {"date": "2015-07-19T18:00:00", "amount": 60}, + {"date": "2015-07-19T18:30:00", "amount": 50}, + {"date": "2015-07-19T19:00:00", "amount": 60}, + {"date": "2015-07-19T19:30:00", "amount": 70}, + {"date": "2015-07-19T20:00:00", "amount": 100} +] diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 491a669137..cb81c24065 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ 4D5B7A4B1D457CCA00796CA9 /* GlucoseG4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5B7A4A1D457CCA00796CA9 /* GlucoseG4.swift */; }; 4F526D5D1DF0FD6500A04910 /* InsulinKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 43C6407B1DA051850093E25D /* InsulinKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */; }; + 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 */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; C17884631D51A7A400405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884621D51A7A400405663 /* BatteryIndicator.swift */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; @@ -392,6 +393,7 @@ 4D3B40021D4A9DFE00BC6334 /* G4ShareSpy.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = G4ShareSpy.framework; path = Carthage/Build/iOS/G4ShareSpy.framework; sourceTree = ""; }; 4D5B7A4A1D457CCA00796CA9 /* GlucoseG4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GlucoseG4.swift; path = Loop/Models/GlucoseG4.swift; sourceTree = SOURCE_ROOT; }; C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NightscoutUploadKit.framework; path = Carthage/Build/iOS/NightscoutUploadKit.framework; sourceTree = ""; }; + 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 = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; C17884621D51A7A400405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; @@ -634,6 +636,7 @@ 43E2D8E91D20C0DB004DA55F /* recommend_temp_basal_start_high_end_low.json */, 43E2D8EA1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_high.json */, 43E2D8EB1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_in_range.json */, + C12F21A61DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json */, ); path = Fixtures; sourceTree = ""; @@ -990,6 +993,7 @@ 43E2D8EF1D20C0DB004DA55F /* recommend_temp_basal_high_and_falling.json in Resources */, 43E2D8ED1D20C0DB004DA55F /* recommend_temp_basal_correct_low_at_min.json in Resources */, 43E2D8F01D20C0DB004DA55F /* recommend_temp_basal_high_and_rising.json in Resources */, + C12F21A71DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json in Resources */, 43E2D8F11D20C0DB004DA55F /* recommend_temp_basal_in_range_and_rising.json in Resources */, 43E2D8EE1D20C0DB004DA55F /* recommend_temp_basal_flat_and_high.json in Resources */, 43E2D8F31D20C0DB004DA55F /* recommend_temp_basal_start_high_end_in_range.json in Resources */, diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 284930e493..a7eec3743e 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -51,7 +51,6 @@ struct DoseMath { - parameter glucoseTargetRange: The schedule of target glucose ranges - parameter insulinSensitivity: The schedule of insulin sensitivities, in Units of insulin per glucose-unit - parameter basalRateSchedule: The schedule of basal rates - - 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. - returns: The recommended basal rate and duration */ @@ -61,8 +60,7 @@ struct DoseMath { maxBasalRate: Double, glucoseTargetRange: GlucoseRangeSchedule, insulinSensitivity: InsulinSensitivitySchedule, - basalRateSchedule: BasalRateSchedule, - allowPredictiveTempBelowRange: Bool + basalRateSchedule: BasalRateSchedule ) -> (rate: Double, duration: TimeInterval)? { guard glucose.count > 1 else { return nil @@ -79,7 +77,11 @@ struct DoseMath { var rate: Double? var duration = TimeInterval(minutes: 30) - if minGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) < minGlucoseTargets.minValue && (!allowPredictiveTempBelowRange || eventualGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) <= eventualGlucoseTargets.minValue) { + let alwaysLowTempBGThreshold: Double = 55 // mg/dL + + if minGlucose.quantity.doubleValue(for: HKUnit.milligramsPerDeciliterUnit()) <= alwaysLowTempBGThreshold { + rate = 0 + } else if minGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) < minGlucoseTargets.minValue && eventualGlucose.quantity.doubleValue(for: glucoseTargetRange.unit) <= eventualGlucoseTargets.minValue { let targetGlucose = HKQuantity(unit: glucoseTargetRange.unit, doubleValue: (minGlucoseTargets.minValue + minGlucoseTargets.maxValue) / 2) rate = calculateTempBasalRateForGlucose(minGlucose.quantity, toTargetGlucose: targetGlucose, diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 9e89414e37..5d114c3a65 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -545,8 +545,7 @@ final class LoopDataManager { maxBasalRate: maxBasal, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivity, - basalRateSchedule: basalRates, - allowPredictiveTempBelowRange: true + basalRateSchedule: basalRates ) else { recommendedTempBasal = nil