Skip to content

Commit

Permalink
Merge pull request #497 from tidepool-org/ps2/LOOP-3820/bolus-without…
Browse files Browse the repository at this point in the history
…-momentum

LOOP-3820 Mitigation for CGM Sensitivity analysis
  • Loading branch information
ps2 authored Mar 3, 2022
2 parents 2179112 + f7fe44f commit 9c4a2c1
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 101 deletions.
9 changes: 9 additions & 0 deletions Common/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct FeatureFlagConfiguration: Decodable {
let simulatedCoreDataEnabled: Bool
let siriEnabled: Bool
let simpleBolusCalculatorEnabled: Bool
let usePositiveMomentumAndRCForManualBoluses: Bool

fileprivate init() {
// Swift compiler config is inverse, since the default state is enabled.
Expand Down Expand Up @@ -150,6 +151,13 @@ struct FeatureFlagConfiguration: Decodable {
#else
self.simpleBolusCalculatorEnabled = false
#endif

// Swift compiler config is inverse, since the default state is enabled.
#if DISABLE_POSITIVE_MOMENTUM_AND_RC_FOR_MANUAL_BOLUSES
self.usePositiveMomentumAndRCForManualBoluses = false
#else
self.usePositiveMomentumAndRCForManualBoluses = true
#endif
}
}

Expand All @@ -175,6 +183,7 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible {
"* manualDoseEntryEnabled: \(manualDoseEntryEnabled)",
"* allowDebugFeatures: \(allowDebugFeatures)",
"* simpleBolusCalculatorEnabled: \(simpleBolusCalculatorEnabled)",
"* usePositiveMomentumAndRCForManualBoluses: \(usePositiveMomentumAndRCForManualBoluses)",
].joined(separator: "\n")
}
}
Expand Down
3 changes: 0 additions & 3 deletions Common/Models/WatchContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ final class WatchContext: RawRepresentable {
var recommendedBolusDose: Double?

var potentialCarbEntry: NewCarbEntry?
var recommendedBolusDoseConsideringPotentialCarbEntry: Double?

var cob: Double?
var iob: Double?
Expand Down Expand Up @@ -95,7 +94,6 @@ final class WatchContext: RawRepresentable {
if let rawPotentialCarbEntry = rawValue["pce"] as? NewCarbEntry.RawValue {
potentialCarbEntry = NewCarbEntry(rawValue: rawPotentialCarbEntry)
}
recommendedBolusDoseConsideringPotentialCarbEntry = rawValue["rbce"] as? Double
cob = rawValue["cob"] as? Double

cgmManagerState = rawValue["cgmManagerState"] as? CGMManager.RawStateValue
Expand Down Expand Up @@ -140,7 +138,6 @@ final class WatchContext: RawRepresentable {
raw["r"] = reservoir
raw["rbo"] = recommendedBolusDose
raw["pce"] = potentialCarbEntry?.rawValue
raw["rbce"] = recommendedBolusDoseConsideringPotentialCarbEntry
raw["rp"] = reservoirPercentage

raw["pg"] = predictedGlucose?.rawValue
Expand Down
112 changes: 45 additions & 67 deletions Loop/Managers/LoopDataManager.swift

Large diffs are not rendered by default.

20 changes: 8 additions & 12 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,10 @@ final class WatchDataManager: NSObject {
let updateGroup = DispatchGroup()

let carbsOnBoard = state.carbsOnBoard
let recommendedBolus = state.recommendedBolus

let context = WatchContext(glucose: glucose, glucoseUnit: self.deviceManager.glucoseStore.preferredUnit)
context.reservoir = reservoir?.unitVolume
context.loopLastRunDate = manager.lastLoopCompleted
context.recommendedBolusDose = recommendedBolus?.recommendation.amount
context.cob = carbsOnBoard?.quantity.doubleValue(for: HKUnit.gram())

if let glucoseDisplay = self.deviceManager.glucoseDisplay(for: glucose) {
Expand All @@ -249,23 +247,21 @@ final class WatchDataManager: NSObject {
}

dosingDecision.carbsOnBoard = carbsOnBoard
dosingDecision.manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendedBolus)

context.cgmManagerState = self.deviceManager.cgmManager?.rawValue

let settings = self.deviceManager.loopManager.settings

context.isClosedLoop = settings.dosingEnabled

if let potentialCarbEntry = potentialCarbEntry {
context.potentialCarbEntry = potentialCarbEntry
if let recommendedBolusDoseConsideringPotentialCarbEntry = try? state.recommendBolus(consideringPotentialCarbEntry: potentialCarbEntry, replacingCarbEntry: nil) {
context.recommendedBolusDoseConsideringPotentialCarbEntry = recommendedBolusDoseConsideringPotentialCarbEntry.amount
dosingDecision.manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: recommendedBolusDoseConsideringPotentialCarbEntry,
date: Date())
}

context.potentialCarbEntry = potentialCarbEntry
if let recommendedBolus = try? state.recommendBolus(consideringPotentialCarbEntry: potentialCarbEntry, replacingCarbEntry: nil, considerPositiveVelocityAndRC: FeatureFlags.usePositiveMomentumAndRCForManualBoluses)
{
context.recommendedBolusDose = recommendedBolus.amount
dosingDecision.manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: recommendedBolus,
date: Date())
}

var historicalGlucose: [HistoricalGlucoseValue]?
if let glucose = glucose {
updateGroup.enter()
Expand Down
12 changes: 8 additions & 4 deletions Loop/View Models/BolusEntryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -585,15 +585,17 @@ final class BolusEntryViewModel: ObservableObject {
potentialBolus: enteredBolusDose,
potentialCarbEntry: potentialCarbEntry,
replacingCarbEntry: originalCarbEntry,
includingPendingInsulin: true
includingPendingInsulin: true,
considerPositiveVelocityAndRC: true
)
} else {
predictedGlucoseValues = try state.predictGlucose(
using: .all,
potentialBolus: enteredBolusDose,
potentialCarbEntry: potentialCarbEntry,
replacingCarbEntry: originalCarbEntry,
includingPendingInsulin: true
includingPendingInsulin: true,
considerPositiveVelocityAndRC: true
)
}
} catch {
Expand Down Expand Up @@ -748,12 +750,14 @@ final class BolusEntryViewModel: ObservableObject {
return try state.recommendBolusForManualGlucose(
manualGlucoseSample!,
consideringPotentialCarbEntry: potentialCarbEntry,
replacingCarbEntry: originalCarbEntry
replacingCarbEntry: originalCarbEntry,
considerPositiveVelocityAndRC: FeatureFlags.usePositiveMomentumAndRCForManualBoluses
)
} else {
return try state.recommendBolus(
consideringPotentialCarbEntry: potentialCarbEntry,
replacingCarbEntry: originalCarbEntry
replacingCarbEntry: originalCarbEntry,
considerPositiveVelocityAndRC: FeatureFlags.usePositiveMomentumAndRCForManualBoluses
)
}
}
Expand Down
3 changes: 2 additions & 1 deletion Loop/View Models/ManualEntryDoseViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ final class ManualEntryDoseViewModel: ObservableObject {
potentialBolus: enteredBolusDose,
potentialCarbEntry: nil,
replacingCarbEntry: nil,
includingPendingInsulin: true
includingPendingInsulin: true,
considerPositiveVelocityAndRC: true
)
} catch {
predictedGlucoseValues = []
Expand Down
34 changes: 29 additions & 5 deletions LoopTests/Managers/LoopDataManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class LoopDataManagerDosingTests: XCTestCase {
var recommendedBolus: ManualBolusRecommendation?
self.loopDataManager.getLoopState { _, state in
predictedGlucose = state.predictedGlucose
recommendedBolus = state.recommendedBolus?.recommendation
recommendedBolus = try? state.recommendBolus(consideringPotentialCarbEntry: nil, replacingCarbEntry: nil, considerPositiveVelocityAndRC: true)
updateGroup.leave()
}
// We need to wait until the task completes to get outputs
Expand Down Expand Up @@ -464,16 +464,40 @@ class LoopDataManagerDosingTests: XCTestCase {

func testLoopGetStateRecommendsManualBolus() {
setUp(for: .highAndStable)
waitOnDataQueue()
let exp = expectation(description: #function)
var recommendedBolus: (recommendation: ManualBolusRecommendation, date: Date)?
var recommendedBolus: ManualBolusRecommendation?
loopDataManager.getLoopState { (_, loopState) in
recommendedBolus = try? loopState.recommendBolus(consideringPotentialCarbEntry: nil, replacingCarbEntry: nil, considerPositiveVelocityAndRC: true)
exp.fulfill()
}
wait(for: [exp], timeout: 100000.0)
XCTAssertEqual(recommendedBolus!.amount, 1.82, accuracy: 0.01)
}

func testLoopGetStateRecommendsManualBolusWithMomentum() {
setUp(for: .highAndRisingWithCOB)
let exp = expectation(description: #function)
var recommendedBolus: ManualBolusRecommendation?
loopDataManager.getLoopState { (_, loopState) in
recommendedBolus = try? loopState.recommendBolus(consideringPotentialCarbEntry: nil, replacingCarbEntry: nil, considerPositiveVelocityAndRC: true)
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(recommendedBolus!.amount, 1.62, accuracy: 0.01)
}

func testLoopGetStateRecommendsManualBolusWithoutMomentum() {
setUp(for: .highAndRisingWithCOB)
let exp = expectation(description: #function)
var recommendedBolus: ManualBolusRecommendation?
loopDataManager.getLoopState { (_, loopState) in
recommendedBolus = try? loopState.recommendBolus(consideringPotentialCarbEntry: nil, replacingCarbEntry: nil, considerPositiveVelocityAndRC: false)
exp.fulfill()
recommendedBolus = loopState.recommendedBolus
}
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(recommendedBolus?.recommendation.amount, 1.7888738147050955)
XCTAssertEqual(recommendedBolus!.amount, 1.52, accuracy: 0.01)
}

}

extension LoopDataManagerDosingTests {
Expand Down
12 changes: 5 additions & 7 deletions LoopTests/ViewModels/BolusEntryViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -859,33 +859,31 @@ fileprivate class MockLoopState: LoopState {

var recommendedAutomaticDose: (recommendation: AutomaticDoseRecommendation, date: Date)?

var recommendedBolus: (recommendation: ManualBolusRecommendation, date: Date)?

var retrospectiveGlucoseDiscrepancies: [GlucoseChange]?

var totalRetrospectiveCorrection: HKQuantity?

var predictGlucoseValueResult: [PredictedGlucoseValue] = []
func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool) throws -> [PredictedGlucoseValue] {
func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool) throws -> [PredictedGlucoseValue] {
return predictGlucoseValueResult
}

func predictGlucoseFromManualGlucose(_ glucose: NewGlucoseSample, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool) throws -> [PredictedGlucoseValue] {
func predictGlucoseFromManualGlucose(_ glucose: NewGlucoseSample, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool) throws -> [PredictedGlucoseValue] {
return predictGlucoseValueResult
}

var bolusRecommendationResult: ManualBolusRecommendation?
var bolusRecommendationError: Error?
var consideringPotentialCarbEntryPassed: NewCarbEntry??
var replacingCarbEntryPassed: StoredCarbEntry??
func recommendBolus(consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?) throws -> ManualBolusRecommendation? {
func recommendBolus(consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, considerPositiveVelocityAndRC: Bool) throws -> ManualBolusRecommendation? {
consideringPotentialCarbEntryPassed = potentialCarbEntry
replacingCarbEntryPassed = replacedCarbEntry
if let error = bolusRecommendationError { throw error }
return bolusRecommendationResult
}

func recommendBolusForManualGlucose(_ glucose: NewGlucoseSample, consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?) throws -> ManualBolusRecommendation? {
func recommendBolusForManualGlucose(_ glucose: NewGlucoseSample, consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, considerPositiveVelocityAndRC: Bool) throws -> ManualBolusRecommendation? {
consideringPotentialCarbEntryPassed = potentialCarbEntry
replacingCarbEntryPassed = replacedCarbEntry
if let error = bolusRecommendationError { throw error }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ final class CarbAndBolusFlowViewModel: ObservableObject {
self.contextDate = context.creationDate

// Don't publish a new value if the recommendation has not changed.
guard self.recommendedBolusAmount != context.recommendedBolusDoseConsideringPotentialCarbEntry else {
guard self.recommendedBolusAmount != context.recommendedBolusDose else {
return
}

self.recommendedBolusAmount = context.recommendedBolusDoseConsideringPotentialCarbEntry
self.recommendedBolusAmount = context.recommendedBolusDose
}
},
errorHandler: { error in
Expand Down

0 comments on commit 9c4a2c1

Please sign in to comment.