Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Bolus recommendation code #708

Merged
merged 2 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 34 additions & 55 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -586,7 +586,7 @@ final class LoopDataManager {

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

Expand Down Expand Up @@ -717,6 +717,7 @@ final class LoopDataManager {
fileprivate var predictedGlucose: [GlucoseValue]? {
didSet {
recommendedTempBasal = nil
recommendedBolus = nil
}
}
fileprivate var retrospectivePredictedGlucose: [GlucoseValue]? {
Expand All @@ -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?
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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`*
Expand Down Expand Up @@ -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 }

Expand All @@ -1008,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
}


Expand Down Expand Up @@ -1064,6 +1041,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 @@ -1073,10 +1055,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 @@ -1118,6 +1096,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