From e14f9da6edc1f6710a0ab5c96a4a2ce52bc80432 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 26 Apr 2018 22:01:22 +0200 Subject: [PATCH 1/7] Add a setting to limit maximum Insulin on Board This is useful to prevent multiple bolus' to create accidentally very high insulin on board values. It is primarly a safety feature, e.g. if someone enters too many carbs by using multiple apps. --- DoseMathTests/DoseMathTests.swift | 261 ++++++++++++++++-- Loop/Extensions/NSUserDefaults.swift | 1 + Loop/Managers/DoseMath.swift | 30 +- Loop/Managers/LoopDataManager.swift | 93 +++++-- Loop/Models/LoopSettings.swift | 4 + .../SettingsTableViewController.swift | 19 +- .../TextFieldTableViewController.swift | 16 +- 7 files changed, 369 insertions(+), 55 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index f54990834b..988d84ae1b 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -101,6 +101,14 @@ class RecommendTempBasalTests: XCTestCase { return TimeInterval(hours: 4) } + var insulinOnBoard: Double { + return 0 + } + + var maxInsulinOnBoard: Double { + return 25 + } + func testNoChange() { let glucose = loadGlucoseValueFixture("recommend_temp_basal_no_change_glucose") @@ -112,6 +120,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -129,6 +139,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -151,6 +163,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: lastTempBasal ) @@ -169,6 +183,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -190,6 +206,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: lastTempBasal ) @@ -217,6 +235,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: lastTempBasal ) @@ -231,6 +251,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -248,6 +270,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -266,6 +290,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -287,6 +313,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: lastTempBasal ) @@ -305,13 +333,74 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) - + // Basal 0.8, 1.1 units required -> 2.2 units extra for 30 minutes XCTAssertEqual(3.0, dose!.unitsPerHour) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } + func testFlatAndHighLimitIob() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_flat_and_high") + + let dose = glucose.recommendedTempBasal( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + basalRates: basalRateSchedule, + maxBasalRate: maxBasalRate, + insulinOnBoard: 0, + maxInsulinOnBoard: 1, + lastTempBasal: nil + ) + // Basal 0.8, 1.1 units required, limited to 1 -> 2 units extra for 30 minutes = 2.8 + XCTAssertEqual(2.8, dose!.unitsPerHour) + XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) + } + + func testFlatAndHighLimitIobWithOnboard() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_flat_and_high") + + let dose = glucose.recommendedTempBasal( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + basalRates: basalRateSchedule, + maxBasalRate: maxBasalRate, + insulinOnBoard: 1.5, + maxInsulinOnBoard: 2, + lastTempBasal: nil + ) + // Basal 0.8, 1.1 units required, limited to 0.5 -> 1 units extra for 30 minutes = 1.8 + XCTAssertEqual(1.8, dose!.unitsPerHour) + XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) + } + + func testFlatAndHighLimitIobExceeded() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_flat_and_high") + + let dose = glucose.recommendedTempBasal( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + basalRates: basalRateSchedule, + maxBasalRate: maxBasalRate, + insulinOnBoard: 2.5, + maxInsulinOnBoard: 2, + lastTempBasal: nil + ) + + XCTAssertNil(dose) + } + func testHighAndFalling() { let glucose = loadGlucoseValueFixture("recommend_temp_basal_high_and_falling") @@ -323,6 +412,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -341,6 +432,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -359,6 +452,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -376,6 +471,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -394,6 +491,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -412,6 +511,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -429,6 +530,8 @@ class RecommendTempBasalTests: XCTestCase { model: insulinModel, basalRates: basalRateSchedule, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, lastTempBasal: nil ) @@ -487,6 +590,14 @@ class RecommendBolusTests: XCTestCase { return TimeInterval(hours: 4) } + var insulinOnBoard: Double { + return 0 + } + + var maxInsulinOnBoard: Double { + return 25 + } + func testNoChange() { let glucose = loadGlucoseValueFixture("recommend_temp_basal_no_change_glucose") @@ -497,7 +608,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -513,7 +626,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -529,7 +644,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -545,7 +662,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -561,7 +680,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(1.575, dose.amount) @@ -573,6 +694,78 @@ class RecommendBolusTests: XCTestCase { } } + func testStartLowEndHighLimitIob() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high") + + let dose = glucose.recommendedBolus( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + pendingInsulin: 0, + maxBolus: maxBolus, + insulinOnBoard: 0, + maxInsulinOnBoard: 1.3 + ) + + XCTAssertEqual(1.3, dose.amount) + + if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + } else { + XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") + } + } + + func testStartLowEndHighLimitIobWithOnboard() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high") + + let dose = glucose.recommendedBolus( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + pendingInsulin: 0, + maxBolus: maxBolus, + insulinOnBoard: 1.0, + maxInsulinOnBoard: 1.3 + ) + + XCTAssertEqual(0.3, dose.amount) + + if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + } else { + XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") + } + } + + func testStartLowEndHighLimitIobExceeded() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high") + + let dose = glucose.recommendedBolus( + to: glucoseTargetRange, + at: glucose.first!.startDate, + suspendThreshold: suspendThreshold.quantity, + sensitivity: insulinSensitivitySchedule, + model: insulinModel, + pendingInsulin: 0, + maxBolus: maxBolus, + insulinOnBoard: 2.0, + maxInsulinOnBoard: 1.3 + ) + + XCTAssertEqual(0, dose.amount) + + if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + } else { + XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") + } + } + func testStartBelowSuspendThresholdEndHigh() { // 60 - 200 mg/dL let glucose = loadGlucoseValueFixture("recommend_temp_basal_start_low_end_high") @@ -584,7 +777,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -607,7 +802,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -629,7 +826,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(1.4, dose.amount) @@ -647,7 +846,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 1, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0.575, dose.amount) @@ -663,7 +864,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) @@ -679,7 +882,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(1.575, dose.amount, accuracy: 1.0 / 40.0) @@ -695,7 +900,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0.325, dose.amount, accuracy: 1.0 / 40.0) @@ -711,7 +918,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0.325, dose.amount, accuracy: 1.0 / 40.0) @@ -725,7 +934,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0.8, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount, accuracy: .ulpOfOne) @@ -741,7 +952,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: ExponentialInsulinModel(actionDuration: 21600.0, peakActivityTime: 4500.0), pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0.275, dose.amount) @@ -757,7 +970,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: self.insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(1.25, dose.amount) @@ -772,7 +987,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(1.25, dose.amount, accuracy: 1.0 / 40.0) @@ -788,7 +1005,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0.0, dose.amount) @@ -804,7 +1023,9 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard ) XCTAssertEqual(0, dose.amount) diff --git a/Loop/Extensions/NSUserDefaults.swift b/Loop/Extensions/NSUserDefaults.swift index 88101cb1b5..a1a9fcde96 100644 --- a/Loop/Extensions/NSUserDefaults.swift +++ b/Loop/Extensions/NSUserDefaults.swift @@ -168,6 +168,7 @@ extension UserDefaults { glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, + maximumInsulinOnBoard: nil, suspendThreshold: suspendThreshold, retrospectiveCorrectionEnabled: bool(forKey: "com.loudnate.Loop.RetrospectiveCorrectionEnabled") ) diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 10c0872e8f..400f68af20 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -39,15 +39,20 @@ extension InsulinCorrection { /// - Parameters: /// - scheduledBasalRate: The scheduled basal rate at the time the correction is delivered /// - maxBasalRate: The maximum allowed basal rate + /// - insulinOnBoard: The current insulin on board + /// - maxInsulinOnBoard: The maximum insulin allowed /// - duration: The duration of the temporary basal /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in basal delivery /// - Returns: A temp basal recommendation fileprivate func asTempBasal( scheduledBasalRate: Double, maxBasalRate: Double, + insulinOnBoard: Double, + maxInsulinOnBoard: Double, duration: TimeInterval, minimumProgrammableIncrementPerUnit: Double ) -> TempBasalRecommendation { + let units = Swift.min(self.units, Swift.max(0, maxInsulinOnBoard - insulinOnBoard)) var rate = units / (duration / TimeInterval(hours: 1)) // units/hour switch self { case .aboveRange, .inRange, .entirelyBelowRange: @@ -85,15 +90,20 @@ extension InsulinCorrection { /// - Parameters: /// - pendingInsulin: The number of units expected to be delivered, but not yet reflected in the correction /// - maxBolus: The maximum allowable bolus value in units + /// - insulinOnBoard: The current insulin on board + /// - maxInsulinOnBoard: The maximum insulin allowed /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in bolus delivery /// - Returns: A bolus recommendation fileprivate func asBolus( pendingInsulin: Double, maxBolus: Double, + insulinOnBoard: Double, + maxInsulinOnBoard: Double, minimumProgrammableIncrementPerUnit: Double ) -> BolusRecommendation { - var units = self.units - pendingInsulin - units = Swift.min(maxBolus, Swift.max(0, units)) + let netUnits = self.units - pendingInsulin + var units = Swift.min(maxBolus, Swift.max(0, netUnits)) + units = Swift.min(units, Swift.max(0, maxInsulinOnBoard - insulinOnBoard)) units = round(units * minimumProgrammableIncrementPerUnit) / minimumProgrammableIncrementPerUnit return BolusRecommendation( @@ -347,6 +357,8 @@ extension Collection where Iterator.Element == GlucoseValue { /// - model: The insulin absorption model /// - basalRates: The schedule of basal rates /// - maxBasalRate: The maximum allowed basal rate + /// - insulinOnBoard: The current insulin on board + /// - maxInsulinOnBoard: The maximum insulin allowed /// - lastTempBasal: The previously set temp basal /// - duration: The duration of the temporary basal /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in basal delivery @@ -360,7 +372,10 @@ extension Collection where Iterator.Element == GlucoseValue { model: InsulinModel, basalRates: BasalRateSchedule, maxBasalRate: Double, + insulinOnBoard: Double, + maxInsulinOnBoard: Double, lastTempBasal: DoseEntry?, + lowerOnly: Bool = false, // only lower the basal, never raise duration: TimeInterval = .minutes(30), minimumProgrammableIncrementPerUnit: Double = 40, continuationInterval: TimeInterval = .minutes(11) @@ -386,6 +401,8 @@ extension Collection where Iterator.Element == GlucoseValue { let temp = correction?.asTempBasal( scheduledBasalRate: scheduledBasalRate, maxBasalRate: maxBasalRate, + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, duration: duration, minimumProgrammableIncrementPerUnit: minimumProgrammableIncrementPerUnit ) @@ -408,6 +425,8 @@ extension Collection where Iterator.Element == GlucoseValue { /// - model: The insulin absorption model /// - pendingInsulin: The number of units expected to be delivered, but not yet reflected in the correction /// - maxBolus: The maximum bolus to return + /// - insulinOnBoard: The current insulin on board + /// - maxInsulinOnBoard: The maximum insulin allowed /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in bolus delivery /// - Returns: A bolus recommendation func recommendedBolus( @@ -418,7 +437,8 @@ extension Collection where Iterator.Element == GlucoseValue { model: InsulinModel, pendingInsulin: Double, maxBolus: Double, - minimumProgrammableIncrementPerUnit: Double = 40 + insulinOnBoard: Double, + maxInsulinOnBoard: Double ) -> BolusRecommendation { guard let correction = self.insulinCorrection( to: correctionRange, @@ -433,7 +453,9 @@ extension Collection where Iterator.Element == GlucoseValue { var bolus = correction.asBolus( pendingInsulin: pendingInsulin, maxBolus: maxBolus, - minimumProgrammableIncrementPerUnit: minimumProgrammableIncrementPerUnit + insulinOnBoard: insulinOnBoard, + maxInsulinOnBoard: maxInsulinOnBoard, + minimumProgrammableIncrementPerUnit: 40 ) // Handle the "current BG below target" notice here diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 75abd492c3..c4661049e0 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -573,6 +573,26 @@ final class LoopDataManager { updateGroup.leave() } } + + if insulinOnBoard == nil { + updateGroup.enter() + let now = Date() + doseStore.getInsulinOnBoardValues(start: retrospectiveStart, end: now) { (result) in + switch result { + case .success(let value): + if let recentValue = value.closestPriorToDate(now) { + self.insulinOnBoard = recentValue + } else { + self.insulinOnBoard = InsulinValue(startDate: now, value: 0.0) + } + case .failure(let error): + NSLog("getInsulinOnBoardValues - error: \(error)") + self.logger.error(error) + self.insulinOnBoard = nil + } + updateGroup.leave() + } + } _ = updateGroup.wait(timeout: .distantFuture) @@ -690,10 +710,18 @@ final class LoopDataManager { } } private var insulinEffect: [GlucoseEffect]? { + didSet { + predictedGlucose = nil + insulinOnBoard = nil + } + } + + fileprivate var insulinOnBoard: InsulinValue? { didSet { predictedGlucose = nil } } + private var glucoseMomentumEffect: [GlucoseEffect]? { didSet { predictedGlucose = nil @@ -876,6 +904,7 @@ final class LoopDataManager { guard let maxBasal = settings.maximumBasalRatePerHour, + let maximumInsulinOnBoard = settings.maximumInsulinOnBoard, let glucoseTargetRange = settings.glucoseTargetRangeSchedule, let insulinSensitivity = insulinSensitivitySchedule, let basalRates = basalRateSchedule, @@ -883,6 +912,15 @@ final class LoopDataManager { else { throw LoopError.configurationError("Check settings") } + + guard let insulinOnBoard = insulinOnBoard + else { + throw LoopError.missingDataError(details: "Insulin on Board not available (updatePredictedGlucoseAndRecommendedBasal)", recovery: "Pump data up to date?") + } + +// guard cgmCalibrated else { +// throw LoopError.missingDataError(details: "CGM", recovery: "CGM Recently calibrated") +// } guard lastRequestedBolus == nil, // Don't recommend changes if a bolus was just set @@ -893,6 +931,8 @@ final class LoopDataManager { model: model, basalRates: basalRates, maxBasalRate: maxBasal, + insulinOnBoard: insulinOnBoard.value, + maxInsulinOnBoard: maximumInsulinOnBoard, lastTempBasal: lastTempBasal ) else { @@ -914,13 +954,19 @@ final class LoopDataManager { guard let predictedGlucose = predictedGlucose, let maxBolus = settings.maximumBolus, + let maximumInsulinOnBoard = settings.maximumInsulinOnBoard, let glucoseTargetRange = settings.glucoseTargetRangeSchedule, let insulinSensitivity = insulinSensitivitySchedule, let model = insulinModelSettings?.model else { throw LoopError.configurationError("Check Settings") } - + + guard let insulinOnBoard = insulinOnBoard + else { + throw LoopError.missingDataError(details: "Insulin on Board not available (recommendBolus)", recovery: "Pump data up to date?") + } + guard let glucoseDate = predictedGlucose.first?.startDate else { throw LoopError.missingDataError(details: "No glucose data found", recovery: "Check your CGM source") } @@ -937,7 +983,9 @@ final class LoopDataManager { sensitivity: insulinSensitivity, model: model, pendingInsulin: pendingInsulin, - maxBolus: maxBolus + maxBolus: maxBolus, + insulinOnBoard: insulinOnBoard.value, + maxInsulinOnBoard: maximumInsulinOnBoard ) return recommendation @@ -979,6 +1027,8 @@ protocol LoopState { /// The last-calculated carbs on board var carbsOnBoard: CarbValue? { get } + var insulinOnBoard: InsulinValue? { get } + /// An error in the current state of the loop, or one that happened during the last attempt to loop. var error: Error? { get } @@ -1034,6 +1084,11 @@ extension LoopDataManager { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) return loopDataManager.carbsOnBoard } + + var insulinOnBoard: InsulinValue? { + dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + return loopDataManager.insulinOnBoard + } var error: Error? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) @@ -1115,6 +1170,7 @@ extension LoopDataManager { "## LoopDataManager", "settings: \(String(reflecting: manager.settings))", "insulinCounteractionEffects: \(String(reflecting: manager.insulinCounteractionEffects))", + "insulinOnBoard: \(String(describing: state.insulinOnBoard))", "predictedGlucose: \(state.predictedGlucose ?? [])", "retrospectivePredictedGlucose: \(state.retrospectivePredictedGlucose ?? [])", "recommendedTempBasal: \(String(describing: state.recommendedTempBasal))", @@ -1125,45 +1181,24 @@ extension LoopDataManager { "lastTempBasal: \(String(describing: state.lastTempBasal))", "carbsOnBoard: \(String(describing: state.carbsOnBoard))" ] - var loopError = state.error - - // TODO: this should be moved to doseStore.generateDiagnosticReport - self.doseStore.insulinOnBoard(at: Date()) { (result) in - let insulinOnBoard: InsulinValue? - - switch result { - case .success(let value): - insulinOnBoard = value - case .failure(let error): - insulinOnBoard = nil - - if loopError == nil { - loopError = error - } - } - - entries.append("insulinOnBoard: \(String(describing: insulinOnBoard))") - entries.append("error: \(String(describing: loopError))") + self.glucoseStore.generateDiagnosticReport { (report) in + entries.append(report) entries.append("") - self.glucoseStore.generateDiagnosticReport { (report) in + self.carbStore.generateDiagnosticReport { (report) in entries.append(report) entries.append("") - self.carbStore.generateDiagnosticReport { (report) in + self.doseStore.generateDiagnosticReport { (report) in entries.append(report) entries.append("") - self.doseStore.generateDiagnosticReport { (report) in - entries.append(report) - entries.append("") - - completion(entries.joined(separator: "\n")) - } + completion(entries.joined(separator: "\n")) } } } + } } } diff --git a/Loop/Models/LoopSettings.swift b/Loop/Models/LoopSettings.swift index 24d374a153..d4b0b2ee0f 100644 --- a/Loop/Models/LoopSettings.swift +++ b/Loop/Models/LoopSettings.swift @@ -19,6 +19,8 @@ struct LoopSettings { var maximumBasalRatePerHour: Double? var maximumBolus: Double? + + var maximumInsulinOnBoard: Double? var suspendThreshold: GlucoseThreshold? = nil @@ -65,6 +67,7 @@ extension LoopSettings: RawRepresentable { self.maximumBasalRatePerHour = rawValue["maximumBasalRatePerHour"] as? Double + self.maximumInsulinOnBoard = rawValue["maximumInsulinOnBoard"] as? Double self.maximumBolus = rawValue["maximumBolus"] as? Double if let rawThreshold = rawValue["minimumBGGuard"] as? GlucoseThreshold.RawValue { @@ -85,6 +88,7 @@ extension LoopSettings: RawRepresentable { raw["glucoseTargetRangeSchedule"] = glucoseTargetRangeSchedule?.rawValue raw["maximumBasalRatePerHour"] = maximumBasalRatePerHour + raw["maximumInsulinOnBoard"] = maximumInsulinOnBoard raw["maximumBolus"] = maximumBolus raw["minimumBGGuard"] = suspendThreshold?.rawValue diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 660b64fe18..da5ceae27e 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -106,6 +106,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case insulinSensitivity case maxBasal case maxBolus + case maxInsulinOnBoard } fileprivate enum ServiceRow: Int, CaseCountable { @@ -345,6 +346,14 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } else { configCell.detailTextLabel?.text = TapToSetString } + case .maxInsulinOnBoard: + configCell.textLabel?.text = NSLocalizedString("Maximum IOB", comment: "The title text for the maximum insulin on board value") + + if let maxInsulinOnBoard = dataManager.loopManager.settings.maximumInsulinOnBoard { + configCell.detailTextLabel?.text = "\(valueNumberFormatter.string(from: NSNumber(value: maxInsulinOnBoard))!) U" + } else { + configCell.detailTextLabel?.text = TapToSetString + } } return configCell @@ -484,7 +493,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case .configuration: let row = ConfigurationRow(rawValue: indexPath.row)! switch row { - case .maxBasal, .maxBolus: + case .maxBasal, .maxBolus, .maxInsulinOnBoard: let vc: LoopKit.TextFieldTableViewController switch row { @@ -492,6 +501,8 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu vc = .maxBasal(dataManager.loopManager.settings.maximumBasalRatePerHour) case .maxBolus: vc = .maxBolus(dataManager.loopManager.settings.maximumBolus) + case .maxInsulinOnBoard: + vc = .maxInsulinOnBoard(dataManager.loopManager.settings.maximumInsulinOnBoard) default: fatalError() } @@ -1012,6 +1023,12 @@ extension SettingsTableViewController: LoopKit.TextFieldTableViewControllerDeleg } else { dataManager.loopManager.settings.maximumBolus = nil } + case .maxInsulinOnBoard: + if let value = controller.value, let units = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.loopManager.settings.maximumInsulinOnBoard = units + } else { + dataManager.loopManager.settings.maximumInsulinOnBoard = nil + } default: assertionFailure() } diff --git a/Loop/View Controllers/TextFieldTableViewController.swift b/Loop/View Controllers/TextFieldTableViewController.swift index c343def6be..08afe249f0 100644 --- a/Loop/View Controllers/TextFieldTableViewController.swift +++ b/Loop/View Controllers/TextFieldTableViewController.swift @@ -71,5 +71,19 @@ extension TextFieldTableViewController { } return vc - } + } + + static func maxInsulinOnBoard(_ value: Double?) -> T { + let vc = T() + + vc.placeholder = NSLocalizedString("Enter a number of units", comment: "The placeholder text instructing users how to enter a maximum iob") + vc.keyboardType = .decimalPad + vc.unit = NSLocalizedString("Units", comment: "The unit string for units") + + if let maxIOB = value { + vc.value = valueNumberFormatter.string(from: NSNumber(value: maxIOB)) + } + + return vc + } } From fb22de4382c6afe57d86dd12df305b0752336879 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 26 Apr 2018 22:18:53 +0200 Subject: [PATCH 2/7] Cosmetics - Revert accidental change - Clarify the exceed IOB intention - Remove some merge artifacts --- DoseMathTests/DoseMathTests.swift | 3 ++- Loop/Managers/DoseMath.swift | 6 +++--- Loop/Managers/LoopDataManager.swift | 9 +++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 988d84ae1b..73e0d7e313 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -397,7 +397,8 @@ class RecommendTempBasalTests: XCTestCase { maxInsulinOnBoard: 2, lastTempBasal: nil ) - + // If the IOB is exceeded the rate is limited to the default + // basal rate. XCTAssertNil(dose) } diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 400f68af20..e8331c59c6 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -375,7 +375,6 @@ extension Collection where Iterator.Element == GlucoseValue { insulinOnBoard: Double, maxInsulinOnBoard: Double, lastTempBasal: DoseEntry?, - lowerOnly: Bool = false, // only lower the basal, never raise duration: TimeInterval = .minutes(30), minimumProgrammableIncrementPerUnit: Double = 40, continuationInterval: TimeInterval = .minutes(11) @@ -438,7 +437,8 @@ extension Collection where Iterator.Element == GlucoseValue { pendingInsulin: Double, maxBolus: Double, insulinOnBoard: Double, - maxInsulinOnBoard: Double + maxInsulinOnBoard: Double, + minimumProgrammableIncrementPerUnit: Double = 40 ) -> BolusRecommendation { guard let correction = self.insulinCorrection( to: correctionRange, @@ -455,7 +455,7 @@ extension Collection where Iterator.Element == GlucoseValue { maxBolus: maxBolus, insulinOnBoard: insulinOnBoard, maxInsulinOnBoard: maxInsulinOnBoard, - minimumProgrammableIncrementPerUnit: 40 + minimumProgrammableIncrementPerUnit: minimumProgrammableIncrementPerUnit ) // Handle the "current BG below target" notice here diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index c4661049e0..2cb9fc116b 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -913,14 +913,11 @@ final class LoopDataManager { throw LoopError.configurationError("Check settings") } - guard let insulinOnBoard = insulinOnBoard - else { + guard let + insulinOnBoard = insulinOnBoard + else { throw LoopError.missingDataError(details: "Insulin on Board not available (updatePredictedGlucoseAndRecommendedBasal)", recovery: "Pump data up to date?") } - -// guard cgmCalibrated else { -// throw LoopError.missingDataError(details: "CGM", recovery: "CGM Recently calibrated") -// } guard lastRequestedBolus == nil, // Don't recommend changes if a bolus was just set From 800215a85e402faa41ff825b8caab27a469d0486 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 26 Apr 2018 22:28:29 +0200 Subject: [PATCH 3/7] Two more cosmetic fixes. --- Loop/Managers/LoopDataManager.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 2cb9fc116b..c050a51f83 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -586,7 +586,6 @@ final class LoopDataManager { self.insulinOnBoard = InsulinValue(startDate: now, value: 0.0) } case .failure(let error): - NSLog("getInsulinOnBoardValues - error: \(error)") self.logger.error(error) self.insulinOnBoard = nil } @@ -916,7 +915,7 @@ final class LoopDataManager { guard let insulinOnBoard = insulinOnBoard else { - throw LoopError.missingDataError(details: "Insulin on Board not available (updatePredictedGlucoseAndRecommendedBasal)", recovery: "Pump data up to date?") + throw LoopError.missingDataError(details: "Insulin on Board not available (updatePredictedGlucoseAndRecommendedBasal)", recovery: "Pump data up to date?") } guard From 7540620b7e7418c7baee22f4ffef5f78482ac668 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 15 Jun 2018 12:01:34 +0200 Subject: [PATCH 4/7] Add maximum Insulin on board protection to Bolus dialog as well. --- .../BolusViewController+LoopDataManager.swift | 5 +++++ Loop/View Controllers/BolusViewController.swift | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/Loop/View Controllers/BolusViewController+LoopDataManager.swift b/Loop/View Controllers/BolusViewController+LoopDataManager.swift index 84246ef882..e215aa2882 100644 --- a/Loop/View Controllers/BolusViewController+LoopDataManager.swift +++ b/Loop/View Controllers/BolusViewController+LoopDataManager.swift @@ -13,6 +13,7 @@ extension BolusViewController { func configureWithLoopManager(_ manager: LoopDataManager, recommendation: BolusRecommendation?, glucoseUnit: HKUnit) { manager.getLoopState { (manager, state) in let maximumBolus = manager.settings.maximumBolus + let maximumInsulinOnBoard = manager.settings.maximumInsulinOnBoard let activeCarbohydrates = state.carbsOnBoard?.quantity.doubleValue(for: .gram()) let bolusRecommendation: BolusRecommendation? @@ -38,6 +39,10 @@ extension BolusViewController { self.maxBolus = maxBolus } + if let maxInsulinOnBoard = maximumInsulinOnBoard { + self.maxInsulinOnBoard = maxInsulinOnBoard + } + self.glucoseUnit = glucoseUnit self.activeInsulin = activeInsulin self.activeCarbohydrates = activeCarbohydrates diff --git a/Loop/View Controllers/BolusViewController.swift b/Loop/View Controllers/BolusViewController.swift index 58d01a89c2..f3d0a77444 100644 --- a/Loop/View Controllers/BolusViewController.swift +++ b/Loop/View Controllers/BolusViewController.swift @@ -116,6 +116,7 @@ final class BolusViewController: UITableViewController, IdentifiableClass, UITex var maxBolus: Double = 25 + var maxInsulinOnBoard: Double = 0 private(set) var bolus: Double? @@ -186,6 +187,15 @@ final class BolusViewController: UITableViewController, IdentifiableClass, UITex return } + if maxInsulinOnBoard > 0 { + guard bolus + (activeInsulin ?? 0) <= maxInsulinOnBoard else { + NSLog("BolusViewController - maxIOB") + presentAlertController(withTitle: NSLocalizedString("Would exceed Maximum Insulin on Board", comment: "The title of the alert describing a maximum insulin on board validation error"), message: String(format: NSLocalizedString("The insulin on board amount is %@ Units", comment: "Body of the alert describing a maximum iob validation error. (1: The localized max iob value)"), + bolusUnitsFormatter.string(from: NSNumber(value: maxInsulinOnBoard)) ?? "")) + return + } + } + let context = LAContext() if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) { From f2a66b6e41ccc4998a100557d07a548bae6e1836 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 15 Jun 2018 12:15:16 +0200 Subject: [PATCH 5/7] Improve error message for IOB validation. --- Loop/View Controllers/BolusViewController.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Loop/View Controllers/BolusViewController.swift b/Loop/View Controllers/BolusViewController.swift index f3d0a77444..ce0dd173ad 100644 --- a/Loop/View Controllers/BolusViewController.swift +++ b/Loop/View Controllers/BolusViewController.swift @@ -187,11 +187,16 @@ final class BolusViewController: UITableViewController, IdentifiableClass, UITex return } + let iob = activeInsulin ?? 0 if maxInsulinOnBoard > 0 { - guard bolus + (activeInsulin ?? 0) <= maxInsulinOnBoard else { + guard bolus + iob <= maxInsulinOnBoard else { NSLog("BolusViewController - maxIOB") - presentAlertController(withTitle: NSLocalizedString("Would exceed Maximum Insulin on Board", comment: "The title of the alert describing a maximum insulin on board validation error"), message: String(format: NSLocalizedString("The insulin on board amount is %@ Units", comment: "Body of the alert describing a maximum iob validation error. (1: The localized max iob value)"), - bolusUnitsFormatter.string(from: NSNumber(value: maxInsulinOnBoard)) ?? "")) + presentAlertController(withTitle: NSLocalizedString("Exceeds Maximum Insulin on Board", comment: "The title of the alert describing a maximum insulin on board validation error"), message: String(format: NSLocalizedString("The insulin on board amount is %@ Units. Together with the entered value of %@ Units it exceeds the configured maximum of %@ Units.", comment: "Body of the alert describing a maximum iob validation error. (1: The bolus value, 2: The IOB value, 3: The maximum IOB permitted)"), + bolusUnitsFormatter.string(from: NSNumber(value: iob)) ?? "", + bolusUnitsFormatter.string(from: NSNumber(value: bolus)) ?? "", + bolusUnitsFormatter.string(from: NSNumber(value: maxInsulinOnBoard)) ?? "" + )) + return } } From 353e9d593e0977af4a909bb3501a764872eed149 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 23 Jun 2018 23:40:46 +0200 Subject: [PATCH 6/7] Fix test code after merge --- DoseMathTests/DoseMathTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 7df9cd335f..9d807c9df5 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -712,7 +712,7 @@ class RecommendBolusTests: XCTestCase { XCTAssertEqual(1.3, dose.amount) if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { - XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter), 60) } else { XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") } @@ -736,7 +736,7 @@ class RecommendBolusTests: XCTestCase { XCTAssertEqual(0.3, dose.amount) if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { - XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter), 60) } else { XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") } @@ -760,7 +760,7 @@ class RecommendBolusTests: XCTestCase { XCTAssertEqual(0, dose.amount) if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { - XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter()), 60) + XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter), 60) } else { XCTFail("Expected currentGlucoseBelowTarget, but got \(dose.notice!)") } From 36e948f747f4e0c80e0c7e12c135dd51d8d59114 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 24 Jun 2018 21:03:28 +0200 Subject: [PATCH 7/7] Re-add diagnostic report entries which were accidentally removed during the merge --- Loop/Managers/LoopDataManager.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 3e94982751..b7218bbd84 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1139,6 +1139,21 @@ extension LoopDataManager { var entries = [ "## LoopDataManager", "settings: \(String(reflecting: manager.settings))", + + "insulinCounteractionEffects: [", + "* GlucoseEffectVelocity(start, end, mg/dL/min)", + manager.insulinCounteractionEffects.reduce(into: "", { (entries, entry) in + entries.append("* \(entry.startDate), \(entry.endDate), \(entry.quantity.doubleValue(for: GlucoseEffectVelocity.unit))\n") + }), + "]", + + "predictedGlucose: [", + "* PredictedGlucoseValue(start, mg/dL)", + (state.predictedGlucose ?? []).reduce(into: "", { (entries, entry) in + entries.append("* \(entry.startDate), \(entry.quantity.doubleValue(for: .milligramsPerDeciliter))\n") + }), + "]", + "retrospectivePredictedGlucose: \(state.retrospectivePredictedGlucose ?? [])", "glucoseMomentumEffect: \(manager.glucoseMomentumEffect ?? [])", "retrospectiveGlucoseEffect: \(manager.retrospectiveGlucoseEffect)",