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

Recommendation Breakdown and Auto Bolus Carbs - PR against main #2277

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0883440
Recommend for carbs only, excluding corrections
Dec 28, 2023
d16fbea
Support carb prediction for testing recommendations
Dec 29, 2023
d4ba097
Add feature flag to control whether used or not
Dec 30, 2023
742352d
Enable recommendation bolus breakdown
Dec 31, 2023
332f2e3
Use chevron icon
Jan 1, 2024
ac655eb
Fix spacing issue
Jan 1, 2024
1464933
Work in progress for breakdown
Jan 2, 2024
6c61b0a
Fix up calculations. Add support for max bolus to breakdown
Jan 3, 2024
27d6485
Merge pull request #12 from motinis/temp-breakdown
motinis Jan 3, 2024
9600aa6
Add unit tests
Jan 3, 2024
8bcfcb0
Fixup correction calc when clamped by max bolus
Jan 3, 2024
eb785de
Round to 2 fractional digits in UI
Jan 3, 2024
b66ce5b
Change icon for bidi languages
Jan 3, 2024
2ce4777
Add support for Limit to 0 Bolus
Jan 4, 2024
ed552c7
Don't calc correction when in range
Jan 4, 2024
2e5a709
Don't calc correction when in range
Jan 4, 2024
816fb6a
initial update for cob and bg correction split
Mar 23, 2024
0538ac7
Merge branch 'LoopKit:dev' into bolus-breakdown-cob
motinis Mar 23, 2024
01b8917
refine COB calculation
Mar 24, 2024
2145564
initial unit testing
Mar 25, 2024
b2d568c
Updates to tests, add controls to UI
Mar 27, 2024
8d88f77
Support both maxBolusExcess and safetyLimit
Mar 28, 2024
9ba70fe
Fix rounding mode
Mar 29, 2024
1aaee2d
Bump version to 3.5.0 to signify dev branch
ps2 Jul 13, 2024
e8a17e5
Merge pull request #13 from LoopKit/dev
motinis Aug 2, 2024
7522d54
Fixes
Aug 23, 2024
79efd20
Separate out recommendation from UI interaction. Fix slowdown too
Aug 23, 2024
a3696e7
Add Diable BG Correction options. Only use carbs and insulin for max …
Aug 24, 2024
546d25c
Handle negative insulin better for COB
Aug 28, 2024
f4345d9
in progress
Sep 23, 2024
afceef4
Unify code paths. Tests not yet passing
Oct 10, 2024
4d1d0df
After refactoring. Tests passing
Oct 11, 2024
20409ad
Add comment and minor generalization
Oct 11, 2024
ae6fb73
Add test for missing insulin due to suspend threshold effects on dosage
Oct 11, 2024
8c2a86d
Fix display calculations for max and safety limits
Oct 13, 2024
7d781ee
Merge branch 'main-custom' into bolus-breakdown-cob
motinis Oct 25, 2024
f208d25
Add Auto-Bolus Carbs Experiment
Dec 26, 2024
621973d
Add missing tests and fix beneath correction range
Dec 26, 2024
b6dff3f
Support automaticDosingIOBLimit for ABC
Jan 5, 2025
e1c2382
Force reload from UserDefaults
Jan 6, 2025
4eb434d
Use AppStorage instead
Jan 7, 2025
9e4c32a
Fix potential rounding issues that can occur with temp basals
Jan 7, 2025
1ee8993
Refactor so to optimize ABC
Jan 7, 2025
08f45c6
Use AppStorage
Jan 21, 2025
a4d9fcb
Update to reflect appstorage use
Jan 21, 2025
9ba803e
Under Development Section + Exclusions
Jan 21, 2025
abb2927
Update display on change to AlgorithmExperiments
Jan 21, 2025
ad7b0ea
Style missing insulin rows when there are exclusions
Jan 23, 2025
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
Prev Previous commit
Next Next commit
initial unit testing
Moti Nisenson-Ken committed Mar 25, 2024
commit 2145564dcb419a84addcc537d7da42984c86a75b
7 changes: 1 addition & 6 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
@@ -1483,16 +1483,11 @@ extension LoopDataManager {

let cobPrediction = totalCobPrediction.map{PredictedGlucoseValue(startDate: $0.startDate, quantity: totalCobPrediction.last!.quantity)}

let totalCobAmount : Double

if let cobBreakdownRecommendation = try recommendBolusValidatingDataRecency(forPrediction: cobPrediction, consideringPotentialCarbEntry: potentialCarbEntry, usage: .cobBreakdown) {
guard let totalCobAmount = try recommendBolusValidatingDataRecency(forPrediction: cobPrediction, consideringPotentialCarbEntry: potentialCarbEntry, usage: .cobBreakdown)?.amount else {

totalCobAmount = cobBreakdownRecommendation.amount
} else {
return recommendation // unable to differentiate between correction amounts
}


let carbBreakdownRecommendation = try recommendBolusValidatingDataRecency(forPrediction: totalCobPrediction, consideringPotentialCarbEntry: potentialCarbEntry, usage: .carbBreakdown)

guard carbBreakdownRecommendation != nil else {
76 changes: 76 additions & 0 deletions LoopTests/Managers/LoopDataManagerDosingTests.swift
Original file line number Diff line number Diff line change
@@ -508,6 +508,82 @@ class LoopDataManagerDosingTests: LoopDataManagerTests {
XCTAssertEqual(recommendedBolus!.missingAmount!, 0.82, accuracy: 0.01)
}

func dummyReplacementEntry() -> StoredCarbEntry{
StoredCarbEntry(startDate: now, quantity: HKQuantity(unit: .gram(), doubleValue: -1))
}

func dummyCarbEntry() -> NewCarbEntry {
NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 1E-50), startDate: now.addingTimeInterval(TimeInterval(days: -2)),
foodType: nil, absorptionTime: TimeInterval(hours: 3))
}

func testLoopGetStateRecommendsManualBolusForCob() {
// note that the default setup for .highAndStable has a _carb_effect file reflecting a 5g carb effect (with 45 ISF)
// predicted glucose starts from 200 and goes down to 176.21882841682697 (taking into account _insulin_effect)
let isf = 45.0
let cir = 10.0

let expectedCobCorrectionAmount = 0.5
let expectedBgCorrectionAmount = 1.82 + (200 - 176.21882841682697) / isf // COB correction is to 200. BG correction is the rest

let carbValue = 5.0 + cir * ((200 - 176.21882841682697) / isf + expectedCobCorrectionAmount)

setUp(for: .highAndStable, predictCarbGlucoseEffects: true,
carbHistorySupplier: {[StoredCarbEntry(startDate: $0, quantity: HKQuantity(unit: .gram(), doubleValue: carbValue))]})

let exp = expectation(description: #function)

var recommendedBolus: ManualBolusRecommendation?

loopDataManager.getLoopState { (_, loopState) in
recommendedBolus = try? loopState.recommendBolus(consideringPotentialCarbEntry: self.dummyCarbEntry(), replacingCarbEntry: self.dummyReplacementEntry(), considerPositiveVelocityAndRC: false)
exp.fulfill()
}
wait(for: [exp], timeout: 100000.0)
XCTAssertEqual(recommendedBolus!.amount, expectedBgCorrectionAmount + expectedCobCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.bgCorrectionAmount!, expectedBgCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.cobCorrectionAmount!, expectedCobCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.carbsAmount!, 0, accuracy: 0.01)
XCTAssertNil(recommendedBolus!.missingAmount)
}

func testLoopGetStateRecommendsManualBolusForCobAndReducingCarbEntry() {
// note that the default setup for .highAndStable has a _carb_effect file reflecting a 5g carb effect (with 45 ISF)
// predicted glucose starts from 200 and goes down to 176.21882841682697 (taking into account _insulin_effect)
let isf = 45.0
let cir = 10.0

let expectedCobCorrectionAmount = 0.6
let expectedBgCorrectionAmount = 1.82 + (200 - 176.21882841682697) / isf // COB correction is to 200. BG correction is the rest
let expectedCarbsAmount = 0.5

let carbValue = 5.0 + cir * ((200 - 176.21882841682697) / isf + expectedCobCorrectionAmount)

setUp(for: .highAndStable, predictCarbGlucoseEffects: true,
carbHistorySupplier: {[
StoredCarbEntry(startDate: $0, quantity: HKQuantity(unit: .gram(), doubleValue: carbValue)),
StoredCarbEntry(startDate: $0, quantity: HKQuantity(unit: .gram(), doubleValue: 10))
]})

let exp = expectation(description: #function)

let carbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: expectedCarbsAmount * cir), startDate: now, foodType: nil, absorptionTime: TimeInterval(hours: 1))

var recommendedBolus: ManualBolusRecommendation?

loopDataManager.getLoopState { (_, loopState) in

recommendedBolus = try? loopState.recommendBolus(consideringPotentialCarbEntry: carbEntry, replacingCarbEntry: StoredCarbEntry(startDate: self.now, quantity: HKQuantity(unit: .gram(), doubleValue: 10)), considerPositiveVelocityAndRC: false)
exp.fulfill()
}
wait(for: [exp], timeout: 100000.0)
XCTAssertEqual(recommendedBolus!.amount, expectedCarbsAmount + expectedBgCorrectionAmount + expectedCobCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.bgCorrectionAmount!, expectedBgCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.cobCorrectionAmount!, expectedCobCorrectionAmount, accuracy: 0.01)
XCTAssertEqual(recommendedBolus!.carbsAmount!, expectedCarbsAmount, accuracy: 0.01)
XCTAssertNil(recommendedBolus!.missingAmount)
}

func testLoopGetStateRecommendsManualBolusForCarbEntry() {
setUp(for: .highAndStable, predictCarbGlucoseEffects: true)
let exp = expectation(description: #function)
13 changes: 9 additions & 4 deletions LoopTests/Managers/LoopDataManagerTests.swift
Original file line number Diff line number Diff line change
@@ -130,7 +130,10 @@ class LoopDataManagerTests: XCTestCase {
dosingStrategy: AutomaticDosingStrategy = .tempBasalOnly,
predictCarbGlucoseEffects: Bool = false,
correctionRanges: GlucoseRangeSchedule? = nil,
suspendThresholdValue: Double? = nil
suspendThresholdValue: Double? = nil,
// note that carbHistory is independent from carb effects;
// one can use dummy replacement carb entry to force recalculation when getting a manual bolus recommendation
carbHistorySupplier: ((Date) -> [StoredCarbEntry]?)? = nil
)
{
let basalRateSchedule = loadBasalRateScheduleFixture("basal_profile")
@@ -170,13 +173,15 @@ class LoopDataManagerTests: XCTestCase {
doseStore.basalProfileApplyingOverrideHistory = doseStore.basalProfile
doseStore.sensitivitySchedule = insulinSensitivitySchedule
let glucoseStore = MockGlucoseStore(for: test)
let carbStore = MockCarbStore(for: test, predictGlucose: predictCarbGlucoseEffects)
carbStore.insulinSensitivityScheduleApplyingOverrideHistory = insulinSensitivitySchedule
carbStore.carbRatioSchedule = carbRatioSchedule

let currentDate = glucoseStore.latestGlucose!.startDate
now = currentDate

let carbStore = MockCarbStore(for: test, predictGlucose: predictCarbGlucoseEffects, carbHistory: carbHistorySupplier?(now))
carbStore.insulinSensitivityScheduleApplyingOverrideHistory = insulinSensitivitySchedule
carbStore.carbRatioSchedule = carbRatioSchedule


dosingDecisionStore = MockDosingDecisionStore()
automaticDosingStatus = AutomaticDosingStatus(automaticDosingEnabled: true, isAutomaticDosingAllowed: true)
loopDataManager = LoopDataManager(
4 changes: 2 additions & 2 deletions LoopTests/Mock Stores/MockCarbStore.swift
Original file line number Diff line number Diff line change
@@ -14,10 +14,10 @@ class MockCarbStore: CarbStoreProtocol {
var predictGlucose: Bool
var carbHistory: [StoredCarbEntry]?

init(for scenario: DosingTestScenario = .flatAndStable, predictGlucose: Bool = false) {
init(for scenario: DosingTestScenario = .flatAndStable, predictGlucose: Bool = false, carbHistory: [StoredCarbEntry]? = nil) {
self.scenario = scenario // The store returns different effect values based on the scenario
self.predictGlucose = predictGlucose
self.carbHistory = loadHistoricCarbEntries(scenario: scenario)
self.carbHistory = carbHistory ?? loadHistoricCarbEntries(scenario: scenario)
}

var scenario: DosingTestScenario