From 3673368b425c5993603e5e59403bcc0755ce1969 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 3 Feb 2017 09:33:35 +0100 Subject: [PATCH 1/8] Add maxIOB #1 (NSUserDefaults, AnalyticsManager) --- Loop/Extensions/NSUserDefaults.swift | 15 +++++++++++++++ Loop/Managers/AnalyticsManager.swift | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Loop/Extensions/NSUserDefaults.swift b/Loop/Extensions/NSUserDefaults.swift index cc13e51eb5..b2d7be63c7 100644 --- a/Loop/Extensions/NSUserDefaults.swift +++ b/Loop/Extensions/NSUserDefaults.swift @@ -24,6 +24,7 @@ extension UserDefaults { case GlucoseTargetRangeSchedule = "com.loudnate.Naterade.GlucoseTargetRangeSchedule" case MaximumBasalRatePerHour = "com.loudnate.Naterade.MaximumBasalRatePerHour" case MaximumBolus = "com.loudnate.Naterade.MaximumBolus" + case MaximumIOB = "com.loudnate.Naterade.MaximumIOB" case PreferredInsulinDataSource = "com.loudnate.Loop.PreferredInsulinDataSource" case PumpID = "com.loudnate.Naterade.PumpID" case PumpModelNumber = "com.loudnate.Naterade.PumpModelNumber" @@ -147,6 +148,20 @@ extension UserDefaults { } } } + var maximumIOB: Double? { + get { + let value = double(forKey: Key.MaximumIOB.rawValue) + + return value > 0 ? value : nil + } + set { + if let maximumIOB = newValue { + set(maximumIOB, forKey: Key.MaximumIOB.rawValue) + } else { + removeObject(forKey: Key.MaximumIOB.rawValue) + } + } + } var preferredInsulinDataSource: InsulinDataSource? { get { diff --git a/Loop/Managers/AnalyticsManager.swift b/Loop/Managers/AnalyticsManager.swift index 51ef938d64..cb807f723f 100644 --- a/Loop/Managers/AnalyticsManager.swift +++ b/Loop/Managers/AnalyticsManager.swift @@ -106,7 +106,10 @@ final class AnalyticsManager { func didChangeMaximumBolus() { logEvent("Maximum bolus change") } - + + func didChangeMaximumIOB() { + logEvent("Maximum IOB change") + } // MARK: - Loop Events func didAddCarbsFromWatch(_ carbs: Double) { From 695370d1cb7a38259361ac6471a3775e68f96f90 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 3 Feb 2017 09:34:56 +0100 Subject: [PATCH 2/8] Add maxIOB #2 (SettingsTable) --- .../SettingsTableViewController.swift | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 5250817f37..20bd824e49 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -104,9 +104,10 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case insulinSensitivity case maxBasal case maxBolus + case maxIOB case batteryChemistry - static let count = 11 + static let count = 12 } fileprivate enum ServiceRow: Int { @@ -274,6 +275,14 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } else { configCell.detailTextLabel?.text = TapToSetString } + case .maxIOB: + configCell.textLabel?.text = NSLocalizedString("Maximum IOB", comment: "The title text for the maximum insulin-on-board value") + + if let maxIOB = dataManager.maximumIOB { + configCell.detailTextLabel?.text = "\(valueNumberFormatter.string(from: NSNumber(value: maxIOB))!) U" + } else { + configCell.detailTextLabel?.text = TapToSetString + } case .batteryChemistry: configCell.textLabel?.text = NSLocalizedString("Pump Battery Type", comment: "The title text for the battery type value") configCell.detailTextLabel?.text = String(describing: dataManager.batteryChemistry) @@ -283,7 +292,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu // configCell.isUserInteractionEnabled = false // } } - cell = configCell case .devices: let deviceCell = tableView.dequeueReusableCell(withIdentifier: RileyLinkDeviceTableViewCell.className) as! RileyLinkDeviceTableViewCell @@ -351,7 +359,11 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case .configuration: let row = ConfigurationRow(rawValue: indexPath.row)! switch row { +<<<<<<< HEAD case .pumpID, .transmitterID, .insulinActionDuration, .maxBasal, .maxBolus: +======= + case .pumpID, .transmitterID, .insulinActionDuration, .minBasal, .maxBasal, .maxBolus, .maxIOB: +>>>>>>> 6f8c346... add maxIOB #2 let vc: TextFieldTableViewController switch row { @@ -365,6 +377,8 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu vc = .maxBasal(dataManager.maximumBasalRatePerHour) case .maxBolus: vc = .maxBolus(dataManager.maximumBolus) + case .maxIOB: + vc = .maxIOB(dataManager.maximumIOB) default: fatalError() } @@ -677,6 +691,12 @@ extension SettingsTableViewController: TextFieldTableViewControllerDelegate { } else { dataManager.maximumBolus = nil } + case .maxIOB: + if let value = controller.value, let units = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.maximumIOB = units + } else { + dataManager.maximumIOB = nil + } default: assertionFailure() } From 67a5878ef4eae7980c997f8ca2508941d3ece036 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 3 Feb 2017 09:35:18 +0100 Subject: [PATCH 3/8] Add maxIOB #3 (TextFieldTableViewController) --- .../TextFieldTableViewController.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Loop/View Controllers/TextFieldTableViewController.swift b/Loop/View Controllers/TextFieldTableViewController.swift index 501add03b0..8ad0f596e8 100644 --- a/Loop/View Controllers/TextFieldTableViewController.swift +++ b/Loop/View Controllers/TextFieldTableViewController.swift @@ -85,4 +85,18 @@ extension TextFieldTableViewController { return vc } + + static func maxIOB(_ 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 insulin on board") + 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 4be22f6d4381d687840452a5109f87208ebf51b5 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 3 Feb 2017 09:58:29 +0100 Subject: [PATCH 4/8] Handle IOB in bolus/basal calculation in DoseMath / LoopDataManager --- Loop/Managers/DoseMath.swift | 25 +++++++++++++++++++++---- Loop/Managers/LoopDataManager.swift | 7 ++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index a7eec3743e..616716e0d6 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -11,7 +11,6 @@ import HealthKit import InsulinKit import LoopKit - struct DoseMath { /// The allowed precision static let basalStrokes: Double = 40 @@ -58,9 +57,12 @@ struct DoseMath { atDate date: Date = Date(), lastTempBasal: DoseEntry?, maxBasalRate: Double, + maxIOB: Double, glucoseTargetRange: GlucoseRangeSchedule, insulinSensitivity: InsulinSensitivitySchedule, - basalRateSchedule: BasalRateSchedule + basalRateSchedule: BasalRateSchedule, + allowPredictiveTempBelowRange: Bool = false, + insulinOnBoard: Double? ) -> (rate: Double, duration: TimeInterval)? { guard glucose.count > 1 else { return nil @@ -122,7 +124,13 @@ struct DoseMath { duration = TimeInterval(0) } } - + if let iob = insulinOnBoard { + if iob > maxIOB { + // We hit the max IOB, thus reduce the rate until we are below IOB. + rate = minBasalRate + // TODO this should log an event to nightscout + } + } if let rate = rate { return (rate: rate, duration: duration) } else { @@ -147,9 +155,11 @@ struct DoseMath { atDate date: Date = Date(), lastTempBasal: DoseEntry?, maxBolus: Double, + maxIOB: Double, glucoseTargetRange: GlucoseRangeSchedule, insulinSensitivity: InsulinSensitivitySchedule, - basalRateSchedule: BasalRateSchedule + basalRateSchedule: BasalRateSchedule, + insulinOnBoard: Double? ) -> Double { guard glucose.count > 1 else { return 0 @@ -179,6 +189,13 @@ struct DoseMath { doseUnits -= max(0, remainingUnits) } + + if let iob = insulinOnBoard { + if iob + doseUnits > maxIOB, maxIOB > 0 { + doseUnits = maxIOB - iob + } + } + doseUnits = round(doseUnits * 40) / 40 return min(maxBolus, max(0, doseUnits)) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 394ebe78cb..30ad9aab83 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -543,6 +543,7 @@ final class LoopDataManager { guard let maxBasal = deviceDataManager.maximumBasalRatePerHour, + let maxIOB = deviceDataManager.maximumIOB, let glucoseTargetRange = deviceDataManager.glucoseTargetRangeSchedule, let insulinSensitivity = deviceDataManager.insulinSensitivitySchedule, let basalRates = deviceDataManager.basalRateSchedule @@ -557,9 +558,11 @@ final class LoopDataManager { let tempBasal = DoseMath.recommendTempBasalFromPredictedGlucose(predictedGlucose, lastTempBasal: lastTempBasal, maxBasalRate: maxBasal, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivity, - basalRateSchedule: basalRates + basalRateSchedule: basalRates, + insulinOnBoard: self.insulinOnBoard?.value ) else { recommendedTempBasal = nil @@ -598,6 +601,7 @@ final class LoopDataManager { guard let glucose = self.predictedGlucose, let maxBolus = self.deviceDataManager.maximumBolus, + let maxIOB = self.deviceDataManager.maximumIOB, let glucoseTargetRange = self.deviceDataManager.glucoseTargetRangeSchedule, let insulinSensitivity = self.deviceDataManager.insulinSensitivitySchedule, let basalRates = self.deviceDataManager.basalRateSchedule @@ -620,6 +624,7 @@ final class LoopDataManager { return max(0, DoseMath.recommendBolusFromPredictedGlucose(glucose, lastTempBasal: self.lastTempBasal, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivity, basalRateSchedule: basalRates From e772a706cf7d4446425c75271fb3c5d463e1f1fd Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 10 Mar 2017 14:56:53 +0100 Subject: [PATCH 5/8] Fix merge conflict in DoseMath. --- Loop/Managers/DoseMath.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 616716e0d6..99d763b228 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -61,7 +61,6 @@ struct DoseMath { glucoseTargetRange: GlucoseRangeSchedule, insulinSensitivity: InsulinSensitivitySchedule, basalRateSchedule: BasalRateSchedule, - allowPredictiveTempBelowRange: Bool = false, insulinOnBoard: Double? ) -> (rate: Double, duration: TimeInterval)? { guard glucose.count > 1 else { From c1b918b0dc3f06d01eed2b147747a12f208494a7 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 10 Mar 2017 14:58:18 +0100 Subject: [PATCH 6/8] Add max iob analytics settings to DeviceDataManager --- Loop/Managers/DeviceDataManager.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 495ef0333e..0d3b3870c3 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -866,6 +866,14 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto } } + var maximumIOB: Double? = UserDefaults.standard.maximumIOB { + didSet { + UserDefaults.standard.maximumIOB = maximumIOB + + AnalyticsManager.sharedManager.didChangeMaximumIOB() + } + } + // MARK: - CarbKit let carbStore: CarbStore? From 16b878382dd8d1641d116adc32973fd73dbef600 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 10 Mar 2017 15:01:17 +0100 Subject: [PATCH 7/8] Fix further merge conflicts in DoseMath, SettingsTable, LoopDataManager. --- Loop/Managers/DoseMath.swift | 6 +++--- Loop/Managers/LoopDataManager.swift | 3 ++- Loop/View Controllers/SettingsTableViewController.swift | 6 +----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 99d763b228..9e1baef57b 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -125,9 +125,9 @@ struct DoseMath { } if let iob = insulinOnBoard { if iob > maxIOB { - // We hit the max IOB, thus reduce the rate until we are below IOB. - rate = minBasalRate - // TODO this should log an event to nightscout + // We hit the max IOB, cancel the one in progress. + rate = 0 + duration = TimeInterval(0) } } if let rate = rate { diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 30ad9aab83..94792453a3 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -627,7 +627,8 @@ final class LoopDataManager { maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivity, - basalRateSchedule: basalRates + basalRateSchedule: basalRates, + insulinOnBoard: self.insulinOnBoard?.value ) - pendingBolusAmount) } diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 20bd824e49..f8a0f1f295 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -359,11 +359,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case .configuration: let row = ConfigurationRow(rawValue: indexPath.row)! switch row { -<<<<<<< HEAD - case .pumpID, .transmitterID, .insulinActionDuration, .maxBasal, .maxBolus: -======= - case .pumpID, .transmitterID, .insulinActionDuration, .minBasal, .maxBasal, .maxBolus, .maxIOB: ->>>>>>> 6f8c346... add maxIOB #2 + case .pumpID, .transmitterID, .insulinActionDuration, .maxBasal, .maxBolus, .maxIOB: let vc: TextFieldTableViewController switch row { From 6edb078ecad9bf089ac7e5aaff67c4fd9d7c08c4 Mon Sep 17 00:00:00 2001 From: Jan Dittmer Date: Fri, 10 Mar 2017 15:19:23 +0100 Subject: [PATCH 8/8] Fix test cases for new function signature and add test cases for maxIOB. --- DoseMathTests/DoseMathTests.swift | 167 ++++++++++++++++++++++++------ 1 file changed, 134 insertions(+), 33 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 4a52f5b772..9342282a83 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -53,6 +53,7 @@ struct GlucoseFixtureValue: GlucoseValue { class RecommendTempBasalTests: XCTestCase { fileprivate let maxBasalRate = 3.0 + fileprivate let maxIOB = 10.0 func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) @@ -95,9 +96,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -110,9 +113,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -130,9 +135,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -146,9 +153,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -165,9 +174,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -190,9 +201,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -203,9 +216,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -222,9 +237,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -238,9 +255,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -255,9 +274,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -274,9 +295,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0, dose!.rate) @@ -290,9 +313,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(3.0, dose!.rate) @@ -306,9 +331,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqualWithAccuracy(1.425, dose!.rate, accuracy: 1.0 / 40.0) @@ -322,9 +349,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqualWithAccuracy(1.475, dose!.rate, accuracy: 1.0 / 40.0) @@ -338,9 +367,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: self.insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(3.0, dose!.rate) @@ -353,14 +384,34 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqualWithAccuracy(2.975, dose!.rate, accuracy: 1.0 / 40.0) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } + + func testHighAndRisingAtIOB() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_high_and_rising") + + let dose = DoseMath.recommendTempBasalFromPredictedGlucose(glucose, + atDate: glucose.first!.startDate, + lastTempBasal: nil, + maxBasalRate: maxBasalRate, + maxIOB: maxIOB, + glucoseTargetRange: glucoseTargetRange, + insulinSensitivity: self.insulinSensitivitySchedule, + basalRateSchedule: basalRateSchedule, + insulinOnBoard: maxIOB + 1 + ) + + XCTAssertEqual(0, dose!.rate) + XCTAssertEqual(TimeInterval(minutes: 0), dose!.duration) + } func testVeryLowAndRising() { let glucose = loadGlucoseValueFixture("recommend_tamp_basal_very_low_end_in_range") @@ -369,9 +420,11 @@ class RecommendTempBasalTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: self.insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertEqual(0.0, dose!.rate) @@ -383,9 +436,11 @@ class RecommendTempBasalTests: XCTestCase { let dose = DoseMath.recommendTempBasalFromPredictedGlucose([], lastTempBasal: nil, maxBasalRate: maxBasalRate, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0 ) XCTAssertNil(dose) @@ -396,6 +451,7 @@ class RecommendTempBasalTests: XCTestCase { class RecommendBolusTests: XCTestCase { fileprivate let maxBolus = 10.0 + fileprivate let maxIOB = 20.0 func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) @@ -438,9 +494,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -453,9 +511,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -473,9 +533,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -488,9 +550,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -503,9 +567,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -518,9 +584,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(0, dose) @@ -533,9 +601,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(1.333, dose, accuracy: 1.0 / 40.0) @@ -548,9 +618,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(0.067, dose, accuracy: 1.0 / 40.0) @@ -563,9 +635,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(0.083, dose, accuracy: 1.0 / 40.0) @@ -583,9 +657,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(0, dose, accuracy: 1e-13) @@ -603,9 +679,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: lastTempBasal, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(0.083, dose, accuracy: 1.0 / 40.0) @@ -618,9 +696,11 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: self.insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqual(1.0, dose) @@ -632,17 +712,38 @@ class RecommendBolusTests: XCTestCase { atDate: glucose.first!.startDate, lastTempBasal: nil, maxBolus: maxBolus, + maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0 ) XCTAssertEqualWithAccuracy(1.0, dose, accuracy: 1.0 / 40.0) } + + func testHighAndRisingAtIOB() { + let glucose = loadGlucoseValueFixture("recommend_temp_basal_high_and_rising") + + let dose = DoseMath.recommendBolusFromPredictedGlucose(glucose, + atDate: glucose.first!.startDate, + lastTempBasal: nil, + maxBolus: maxBolus, + maxIOB: maxIOB, + glucoseTargetRange: glucoseTargetRange, + insulinSensitivity: self.insulinSensitivitySchedule, + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 20.0 + ) + + XCTAssertEqual(0.0, dose) + } func testNoInputGlucose() { - let dose = DoseMath.recommendBolusFromPredictedGlucose([], lastTempBasal: nil, maxBolus: 4, glucoseTargetRange: glucoseTargetRange, insulinSensitivity: insulinSensitivitySchedule, - basalRateSchedule: basalRateSchedule) + let dose = DoseMath.recommendBolusFromPredictedGlucose([], lastTempBasal: nil, maxBolus: 4, maxIOB: maxIOB, glucoseTargetRange: glucoseTargetRange, + insulinSensitivity: insulinSensitivitySchedule, + basalRateSchedule: basalRateSchedule, + insulinOnBoard: 0.0) XCTAssertEqual(0, dose) }