From 77d501c622658e08709a138b921d6f5ef8fae64b Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 26 Apr 2018 22:53:28 +0200 Subject: [PATCH 1/2] Refactor Bolus Recommendation Make Bolus recommendation part of Loop update and don't allow external calls to it. The data doesn't change in any case and update() is called in all places where we want a Bolus recommendation. This is in preparation of automated Bolus code, which needs consistent Bolus and Basal data. --- Loop/Managers/LoopDataManager.swift | 81 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 75abd492c3..a80f70deee 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -299,7 +299,7 @@ final class LoopDataManager { do { try self.update() - completion(.success(try self.recommendBolus())) + completion(.success(self.recommendedBolus?.recommendation)) } catch let error { completion(.failure(error)) } @@ -586,7 +586,7 @@ final class LoopDataManager { if predictedGlucose == nil { do { - try updatePredictedGlucoseAndRecommendedBasal() + try updatePredictedGlucoseAndRecommendedBasalAndBolus() } catch let error { logger.error(error) @@ -717,6 +717,7 @@ final class LoopDataManager { fileprivate var predictedGlucose: [GlucoseValue]? { didSet { recommendedTempBasal = nil + recommendedBolus = nil } } fileprivate var retrospectivePredictedGlucose: [GlucoseValue]? { @@ -726,6 +727,8 @@ final class LoopDataManager { } fileprivate var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? + fileprivate var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? + fileprivate var carbsOnBoard: CarbValue? fileprivate var lastTempBasal: DoseEntry? @@ -841,7 +844,7 @@ final class LoopDataManager { /// - LoopError.glucoseTooOld /// - LoopError.missingDataError /// - LoopError.pumpDataTooOld - private func updatePredictedGlucoseAndRecommendedBasal() throws { + private func updatePredictedGlucoseAndRecommendedBasalAndBolus() throws { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) guard let glucose = glucoseStore.latestGlucose else { @@ -879,14 +882,25 @@ final class LoopDataManager { let glucoseTargetRange = settings.glucoseTargetRangeSchedule, let insulinSensitivity = insulinSensitivitySchedule, let basalRates = basalRateSchedule, + let maxBolus = settings.maximumBolus, let model = insulinModelSettings?.model else { throw LoopError.configurationError("Check settings") } + + let pendingInsulin = try self.getPendingInsulin() - guard - lastRequestedBolus == nil, // Don't recommend changes if a bolus was just set - let tempBasal = predictedGlucose.recommendedTempBasal( + guard lastRequestedBolus == nil + else { + // Don't recommend changes if a bolus was just requested. + // Sending additional pump commands is not going to be + // successful in any case. + recommendedBolus = nil + recommendedTempBasal = nil + return + } + + let tempBasal = predictedGlucose.recommendedTempBasal( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, sensitivity: insulinSensitivity, @@ -895,42 +909,13 @@ final class LoopDataManager { maxBasalRate: maxBasal, lastTempBasal: lastTempBasal ) - else { + + if let temp = tempBasal { + recommendedTempBasal = (recommendation: temp, date: startDate) + } else { recommendedTempBasal = nil - return - } - - recommendedTempBasal = (recommendation: tempBasal, date: Date()) - } - - /// - Returns: A bolus recommendation from the current data - /// - Throws: - /// - LoopError.configurationError - /// - LoopError.glucoseTooOld - /// - LoopError.missingDataError - fileprivate func recommendBolus() throws -> BolusRecommendation { - dispatchPrecondition(condition: .onQueue(dataAccessQueue)) - - guard - let predictedGlucose = predictedGlucose, - let maxBolus = settings.maximumBolus, - let glucoseTargetRange = settings.glucoseTargetRangeSchedule, - let insulinSensitivity = insulinSensitivitySchedule, - let model = insulinModelSettings?.model - else { - throw LoopError.configurationError("Check Settings") - } - - guard let glucoseDate = predictedGlucose.first?.startDate else { - throw LoopError.missingDataError(details: "No glucose data found", recovery: "Check your CGM source") } - - guard abs(glucoseDate.timeIntervalSinceNow) <= recencyInterval else { - throw LoopError.glucoseTooOld(date: glucoseDate) - } - - let pendingInsulin = try self.getPendingInsulin() - + let recommendation = predictedGlucose.recommendedBolus( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, @@ -939,8 +924,7 @@ final class LoopDataManager { pendingInsulin: pendingInsulin, maxBolus: maxBolus ) - - return recommendation + recommendedBolus = (recommendation: recommendation, date: startDate) } /// *This method should only be called from the `dataAccessQueue`* @@ -997,6 +981,8 @@ protocol LoopState { /// The recommended temp basal based on predicted glucose var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? { get } + var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? { get } + /// The retrospective prediction over a recent period of glucose samples var retrospectivePredictedGlucose: [GlucoseValue]? { get } @@ -1064,6 +1050,11 @@ extension LoopDataManager { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) return loopDataManager.recommendedTempBasal } + + var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? { + dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + return loopDataManager.recommendedBolus + } var retrospectivePredictedGlucose: [GlucoseValue]? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) @@ -1075,7 +1066,10 @@ extension LoopDataManager { } func recommendBolus() throws -> BolusRecommendation { - return try loopDataManager.recommendBolus() + if let bolus = loopDataManager.recommendedBolus { + return bolus.recommendation + } + throw LoopError.missingDataError(details: "Recommended Bolus data not available.", recovery: "Check you loop state.") } } @@ -1118,6 +1112,7 @@ extension LoopDataManager { "predictedGlucose: \(state.predictedGlucose ?? [])", "retrospectivePredictedGlucose: \(state.retrospectivePredictedGlucose ?? [])", "recommendedTempBasal: \(String(describing: state.recommendedTempBasal))", + "recommendedBolus: \(String(describing: state.recommendedBolus))", "lastBolus: \(String(describing: manager.lastRequestedBolus))", "lastGlucoseChange: \(String(describing: manager.lastGlucoseChange))", "retrospectiveGlucoseChange: \(String(describing: manager.retrospectiveGlucoseChange))", From e32732798fd756943ea10320dd346ca8713094d4 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 26 Apr 2018 23:00:18 +0200 Subject: [PATCH 2/2] Remove recommendBolus function from LoopState The recommendedBolus variable contains the same information. --- Loop/Managers/LoopDataManager.swift | 16 ---------------- Loop/Managers/NightscoutDataManager.swift | 10 +--------- Loop/Managers/WatchDataManager.swift | 2 +- .../BolusViewController+LoopDataManager.swift | 2 +- 4 files changed, 3 insertions(+), 27 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index a80f70deee..da836e102d 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -994,15 +994,6 @@ protocol LoopState { /// - Returns: An timeline of predicted glucose values /// - Throws: LoopError.missingDataError if prediction cannot be computed func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] - - /// Calculates a recommended bolus based on predicted glucose - /// - /// - Returns: A bolus recommendation - /// - Throws: An error describing why a bolus couldnʼt be computed - /// - LoopError.configurationError - /// - LoopError.glucoseTooOld - /// - LoopError.missingDataError - func recommendBolus() throws -> BolusRecommendation } @@ -1064,13 +1055,6 @@ extension LoopDataManager { func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] { return try loopDataManager.predictGlucose(using: inputs) } - - func recommendBolus() throws -> BolusRecommendation { - if let bolus = loopDataManager.recommendedBolus { - return bolus.recommendation - } - throw LoopError.missingDataError(details: "Recommended Bolus data not available.", recovery: "Check you loop state.") - } } /// Executes a closure with access to the current state of the loop. diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 5e7706cce4..a773507f84 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -43,15 +43,7 @@ final class NightscoutDataManager { var loopError = state.error let recommendedBolus: Double? - do { - recommendedBolus = try state.recommendBolus().amount - } catch let error { - recommendedBolus = nil - - if loopError == nil { - loopError = error - } - } + recommendedBolus = state.recommendedBolus?.recommendation.amount let carbsOnBoard = state.carbsOnBoard let predictedGlucose = state.predictedGlucose diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 44255cd80f..a20a326050 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -125,7 +125,7 @@ final class WatchDataManager: NSObject, WCSessionDelegate { context.reservoir = reservoir?.unitVolume context.loopLastRunDate = state.lastLoopCompleted - context.recommendedBolusDose = try? state.recommendBolus().amount + context.recommendedBolusDose = state.recommendedBolus?.recommendation.amount context.maxBolus = manager.settings.maximumBolus if let glucoseTargetRangeSchedule = manager.settings.glucoseTargetRangeSchedule { diff --git a/Loop/View Controllers/BolusViewController+LoopDataManager.swift b/Loop/View Controllers/BolusViewController+LoopDataManager.swift index 280d333530..84246ef882 100644 --- a/Loop/View Controllers/BolusViewController+LoopDataManager.swift +++ b/Loop/View Controllers/BolusViewController+LoopDataManager.swift @@ -20,7 +20,7 @@ extension BolusViewController { if let recommendation = recommendation { bolusRecommendation = recommendation } else { - bolusRecommendation = try? state.recommendBolus() + bolusRecommendation = state.recommendedBolus?.recommendation } manager.doseStore.insulinOnBoard(at: Date()) { (result) in