Skip to content

Commit a2ccc51

Browse files
authored
Merge pull request #582 from LoopKit/extend-prediction-to-dia
Ensure that bg prediction extends to full DIA
2 parents 0844576 + 4c7a759 commit a2ccc51

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

DoseMathTests/DoseMathTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,22 @@ class RecommendBolusTests: XCTestCase {
685685
XCTAssertEqualWithAccuracy(0, dose.amount, accuracy: 1e-13)
686686
}
687687

688+
func testStartLowEndJustAboveRange() {
689+
let glucose = loadGlucoseValueFixture("recommended_temp_start_low_end_just_above_range")
690+
691+
let dose = glucose.recommendedBolus(
692+
to: glucoseTargetRange,
693+
at: glucose.first!.startDate,
694+
suspendThreshold: HKQuantity(unit: .milligramsPerDeciliter(), doubleValue: 0),
695+
sensitivity: insulinSensitivitySchedule,
696+
model: ExponentialInsulinModel(actionDuration: 21600.0, peakActivityTime: 4500.0),
697+
pendingInsulin: 0,
698+
maxBolus: maxBolus
699+
)
700+
701+
XCTAssertEqual(0.275, dose.amount)
702+
}
703+
688704
func testHighAndRising() {
689705
let glucose = loadGlucoseValueFixture("recommend_temp_basal_high_and_rising")
690706

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[
2+
{"date": "2017-09-17T10:38:21", "amount": 57},
3+
{"date": "2017-09-17T10:40:00", "amount": 57.6448},
4+
{"date": "2017-09-17T10:45:00", "amount": 59.7488},
5+
{"date": "2017-09-17T10:50:00", "amount": 61.8207},
6+
{"date": "2017-09-17T10:55:00", "amount": 63.8623},
7+
{"date": "2017-09-17T11:00:00", "amount": 65.8754},
8+
{"date": "2017-09-17T11:05:00", "amount": 67.8615},
9+
{"date": "2017-09-17T11:10:00", "amount": 69.8222},
10+
{"date": "2017-09-17T11:15:00", "amount": 71.759},
11+
{"date": "2017-09-17T11:20:00", "amount": 73.6728},
12+
{"date": "2017-09-17T11:25:00", "amount": 75.5648},
13+
{"date": "2017-09-17T11:30:00", "amount": 77.436},
14+
{"date": "2017-09-17T11:35:00", "amount": 79.2873},
15+
{"date": "2017-09-17T11:40:00", "amount": 81.1198},
16+
{"date": "2017-09-17T11:45:00", "amount": 82.9344},
17+
{"date": "2017-09-17T11:50:00", "amount": 84.7321},
18+
{"date": "2017-09-17T11:55:00", "amount": 86.5139},
19+
{"date": "2017-09-17T12:00:00", "amount": 88.281},
20+
{"date": "2017-09-17T12:05:00", "amount": 90.0348},
21+
{"date": "2017-09-17T12:10:00", "amount": 91.7764},
22+
{"date": "2017-09-17T12:15:00", "amount": 93.507},
23+
{"date": "2017-09-17T12:20:00", "amount": 95.2275},
24+
{"date": "2017-09-17T12:25:00", "amount": 96.9392},
25+
{"date": "2017-09-17T12:30:00", "amount": 98.6428},
26+
{"date": "2017-09-17T12:35:00", "amount": 100.339},
27+
{"date": "2017-09-17T12:40:00", "amount": 102.03},
28+
{"date": "2017-09-17T12:45:00", "amount": 103.715},
29+
{"date": "2017-09-17T12:50:00", "amount": 105.395},
30+
{"date": "2017-09-17T12:55:00", "amount": 107.072},
31+
{"date": "2017-09-17T13:00:00", "amount": 108.746},
32+
{"date": "2017-09-17T13:05:00", "amount": 110.417},
33+
{"date": "2017-09-17T13:10:00", "amount": 112.086},
34+
{"date": "2017-09-17T13:15:00", "amount": 113.753},
35+
{"date": "2017-09-17T13:20:00", "amount": 115.42},
36+
{"date": "2017-09-17T13:25:00", "amount": 117.087},
37+
{"date": "2017-09-17T13:30:00", "amount": 118.754},
38+
{"date": "2017-09-17T13:35:00", "amount": 120.42},
39+
{"date": "2017-09-17T13:40:00", "amount": 121.914},
40+
{"date": "2017-09-17T13:45:00", "amount": 121.914},
41+
{"date": "2017-09-17T13:50:00", "amount": 121.914},
42+
{"date": "2017-09-17T16:38:21", "amount": 121.914}
43+
]

Loop.xcodeproj/project.pbxproj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
435CB6291F37B01300C320C7 /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; };
6363
436961911F19D11E00447E89 /* ChartPointsContextFillLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */; };
6464
436A0DA51D236A2A00104B24 /* LoopError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0DA41D236A2A00104B24 /* LoopError.swift */; };
65+
436D9BF81F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json in Resources */ = {isa = PBXBuildFile; fileRef = 436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */; };
6566
436FACEE1D0BA636004E2427 /* InsulinDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436FACED1D0BA636004E2427 /* InsulinDataSource.swift */; };
6667
437272DF1F09E41200A3DA02 /* WCSession+Swift4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437272DE1F09E41200A3DA02 /* WCSession+Swift4.swift */; };
6768
437272E01F09E41600A3DA02 /* WCSession+Swift4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437272DE1F09E41200A3DA02 /* WCSession+Swift4.swift */; };
@@ -428,6 +429,7 @@
428429
43649A621C7A347F00523D7F /* CollectionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionType.swift; sourceTree = "<group>"; };
429430
4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPointsContextFillLayer.swift; sourceTree = "<group>"; };
430431
436A0DA41D236A2A00104B24 /* LoopError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopError.swift; sourceTree = "<group>"; };
432+
436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommended_temp_start_low_end_just_above_range.json; sourceTree = "<group>"; };
431433
436FACED1D0BA636004E2427 /* InsulinDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDataSource.swift; sourceTree = "<group>"; };
432434
437272DE1F09E41200A3DA02 /* WCSession+Swift4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCSession+Swift4.swift"; sourceTree = "<group>"; };
433435
43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -858,8 +860,10 @@
858860
43E2D8E01D20C0CB004DA55F /* Fixtures */ = {
859861
isa = PBXGroup;
860862
children = (
863+
C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */,
861864
43E2D8E11D20C0DB004DA55F /* read_selected_basal_profile.json */,
862865
43E2D8E21D20C0DB004DA55F /* recommend_temp_basal_correct_low_at_min.json */,
866+
C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */,
863867
43E2D8E31D20C0DB004DA55F /* recommend_temp_basal_flat_and_high.json */,
864868
43E2D8E41D20C0DB004DA55F /* recommend_temp_basal_high_and_falling.json */,
865869
43E2D8E51D20C0DB004DA55F /* recommend_temp_basal_high_and_rising.json */,
@@ -869,10 +873,9 @@
869873
43E2D8E91D20C0DB004DA55F /* recommend_temp_basal_start_high_end_low.json */,
870874
43E2D8EA1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_high.json */,
871875
43E2D8EB1D20C0DB004DA55F /* recommend_temp_basal_start_low_end_in_range.json */,
872-
C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */,
873876
C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */,
874-
C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */,
875-
C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */,
877+
C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */,
878+
436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */,
876879
);
877880
path = Fixtures;
878881
sourceTree = "<group>";
@@ -1420,6 +1423,7 @@
14201423
C17824A31E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json in Resources */,
14211424
43E2D8F41D20C0DB004DA55F /* recommend_temp_basal_start_high_end_low.json in Resources */,
14221425
43E2D8EF1D20C0DB004DA55F /* recommend_temp_basal_high_and_falling.json in Resources */,
1426+
436D9BF81F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json in Resources */,
14231427
43E2D8ED1D20C0DB004DA55F /* recommend_temp_basal_correct_low_at_min.json in Resources */,
14241428
43E2D8F01D20C0DB004DA55F /* recommend_temp_basal_high_and_rising.json in Resources */,
14251429
C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */,

Loop/Managers/DoseMath.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ extension Collection where Iterator.Element == GlucoseValue {
264264
let targetValue = targetGlucoseValue(
265265
percentEffectDuration: time / model.effectDuration,
266266
minValue: suspendThresholdValue,
267-
maxValue: correctionRange.value(at: prediction.startDate).averageValue)
267+
maxValue: correctionRange.value(at: prediction.startDate).averageValue
268+
)
268269

269270
// Compute the dose required to bring this prediction to target:
270271
// dose = (Glucose Δ) / (% effect × sensitivity)

Loop/Managers/LoopDataManager.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,10 @@ final class LoopDataManager {
638638
fileprivate func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] {
639639
dispatchPrecondition(condition: .onQueue(dataAccessQueue))
640640

641+
guard let model = insulinModelSettings?.model else {
642+
throw LoopError.configurationError("Check settings")
643+
}
644+
641645
guard let glucose = self.glucoseStore.latestGlucose else {
642646
throw LoopError.missingDataError(details: "Cannot predict glucose due to missing input data", recovery: "Check your CGM data source")
643647
}
@@ -661,7 +665,16 @@ final class LoopDataManager {
661665
effects.append(self.retrospectiveGlucoseEffect)
662666
}
663667

664-
return LoopMath.predictGlucose(glucose, momentum: momentum, effects: effects)
668+
var prediction = LoopMath.predictGlucose(glucose, momentum: momentum, effects: effects)
669+
670+
// Dosing requires prediction entries at as long as the insulin model duration.
671+
// If our prediciton is shorter than that, then extend it here.
672+
let finalDate = glucose.startDate.addingTimeInterval(model.effectDuration)
673+
if let last = prediction.last, last.startDate < finalDate {
674+
prediction.append(PredictedGlucoseValue(startDate: finalDate, quantity: last.quantity))
675+
}
676+
677+
return prediction
665678
}
666679

667680
// MARK: - Calculation state

0 commit comments

Comments
 (0)