Skip to content

Commit

Permalink
Refactor Bolus recommendation code (#708)
Browse files Browse the repository at this point in the history
* 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.

* Remove recommendBolus function from LoopState

The recommendedBolus variable contains the same information.
  • Loading branch information
erikdi authored and ps2 committed May 24, 2018
1 parent 1cabd3a commit 140df85
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 66 deletions.
89 changes: 34 additions & 55 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ final class LoopDataManager {
do {
try self.update()

completion(.success(try self.recommendBolus()))
completion(.success(self.recommendedBolus?.recommendation))
} catch let error {
completion(.failure(error))
}
Expand Down Expand Up @@ -580,7 +580,7 @@ final class LoopDataManager {

if predictedGlucose == nil {
do {
try updatePredictedGlucoseAndRecommendedBasal()
try updatePredictedGlucoseAndRecommendedBasalAndBolus()
} catch let error {
logger.error(error)

Expand Down Expand Up @@ -719,6 +719,7 @@ final class LoopDataManager {
fileprivate var predictedGlucose: [GlucoseValue]? {
didSet {
recommendedTempBasal = nil
recommendedBolus = nil
}
}
fileprivate var retrospectivePredictedGlucose: [GlucoseValue]? {
Expand All @@ -728,6 +729,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?
Expand Down Expand Up @@ -843,7 +846,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 {
Expand Down Expand Up @@ -881,14 +884,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,
Expand All @@ -897,42 +911,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,
Expand All @@ -941,8 +926,7 @@ final class LoopDataManager {
pendingInsulin: pendingInsulin,
maxBolus: maxBolus
)

return recommendation
recommendedBolus = (recommendation: recommendation, date: startDate)
}

/// *This method should only be called from the `dataAccessQueue`*
Expand Down Expand Up @@ -999,6 +983,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 }

Expand All @@ -1010,15 +996,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
}


Expand Down Expand Up @@ -1066,6 +1043,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))
Expand All @@ -1075,10 +1057,6 @@ extension LoopDataManager {
func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] {
return try loopDataManager.predictGlucose(using: inputs)
}

func recommendBolus() throws -> BolusRecommendation {
return try loopDataManager.recommendBolus()
}
}

/// Executes a closure with access to the current state of the loop.
Expand Down Expand Up @@ -1120,6 +1098,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))",
Expand Down
10 changes: 1 addition & 9 deletions Loop/Managers/NightscoutDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 140df85

Please sign in to comment.