Skip to content

Commit d1f6908

Browse files
committed
Disable missed meal detection if there are user-entered and/or calibration points
1 parent d7e107d commit d1f6908

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

Loop/Managers/LoopDataManager.swift

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,8 @@ extension LoopDataManager {
874874
logger.default("Loop ended")
875875
notify(forChange: .loopFinished)
876876

877-
let carbEffectStart = now().addingTimeInterval(-MissedMealSettings.maxRecency)
878-
carbStore.getGlucoseEffects(start: carbEffectStart, end: now(), effectVelocities: insulinCounteractionEffects) {[weak self] result in
877+
let samplesStart = now().addingTimeInterval(-MissedMealSettings.maxRecency)
878+
carbStore.getGlucoseEffects(start: samplesStart, end: now(), effectVelocities: insulinCounteractionEffects) {[weak self] result in
879879
guard
880880
let self = self,
881881
case .success((_, let carbEffects)) = result
@@ -885,15 +885,28 @@ extension LoopDataManager {
885885
}
886886
return
887887
}
888-
889-
self.mealDetectionManager.generateMissedMealNotificationIfNeeded(
890-
insulinCounteractionEffects: self.insulinCounteractionEffects,
891-
carbEffects: carbEffects,
892-
pendingAutobolusUnits: self.recommendedAutomaticDose?.recommendation.bolusUnits,
893-
bolusDurationEstimator: { [unowned self] bolusAmount in
894-
return self.delegate?.loopDataManager(self, estimateBolusDuration: bolusAmount)
888+
889+
glucoseStore.getGlucoseSamples(start: samplesStart, end: now()) {[weak self] result in
890+
guard
891+
let self = self,
892+
case .success(let glucoseSamples) = result
893+
else {
894+
if case .failure(let error) = result {
895+
self?.logger.error("Failed to fetch glucose samples to check for missed meal: %{public}@", String(describing: error))
896+
}
897+
return
895898
}
896-
)
899+
900+
self.mealDetectionManager.generateMissedMealNotificationIfNeeded(
901+
glucoseSamples: glucoseSamples,
902+
insulinCounteractionEffects: self.insulinCounteractionEffects,
903+
carbEffects: carbEffects,
904+
pendingAutobolusUnits: self.recommendedAutomaticDose?.recommendation.bolusUnits,
905+
bolusDurationEstimator: { [unowned self] bolusAmount in
906+
return self.delegate?.loopDataManager(self, estimateBolusDuration: bolusAmount)
907+
}
908+
)
909+
}
897910
}
898911

899912
// 5 second delay to allow stores to cache data before it is read by widget

Loop/Managers/Missed Meal Detection/MealDetectionManager.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,22 @@ class MealDetectionManager {
6464
}
6565

6666
// MARK: Meal Detection
67-
func hasMissedMeal(insulinCounteractionEffects: [GlucoseEffectVelocity], carbEffects: [GlucoseEffect], completion: @escaping (MissedMealStatus) -> Void) {
67+
func hasMissedMeal(glucoseSamples: [some GlucoseSampleValue], insulinCounteractionEffects: [GlucoseEffectVelocity], carbEffects: [GlucoseEffect], completion: @escaping (MissedMealStatus) -> Void) {
6868
let delta = TimeInterval(minutes: 5)
6969

7070
let intervalStart = currentDate(timeIntervalSinceNow: -MissedMealSettings.maxRecency)
7171
let intervalEnd = currentDate(timeIntervalSinceNow: -MissedMealSettings.minRecency)
7272
let now = self.currentDate
73-
73+
74+
let filteredGlucoseValues = glucoseSamples.filter { intervalStart <= $0.startDate && $0.startDate <= now }
75+
76+
/// Only try to detect if there's a missed meal if there are no calibration/user-entered BGs,
77+
/// since these can cause large jumps
78+
guard !filteredGlucoseValues.containsUserEntered() else {
79+
completion(.noMissedMeal)
80+
return
81+
}
82+
7483
let filteredCarbEffects = carbEffects.filterDateRange(intervalStart, now)
7584

7685
/// Compute how much of the ICE effect we can't explain via our entered carbs
@@ -213,12 +222,13 @@ class MealDetectionManager {
213222
/// - pendingAutobolusUnits: any autobolus units that are still being delivered. Used to delay the missed meal notification to avoid notifying during an autobolus.
214223
/// - bolusDurationEstimator: estimator of bolus duration that takes the units of the bolus as an input. Used to delay the missed meal notification to avoid notifying during an autobolus.
215224
func generateMissedMealNotificationIfNeeded(
225+
glucoseSamples: [some GlucoseSampleValue],
216226
insulinCounteractionEffects: [GlucoseEffectVelocity],
217227
carbEffects: [GlucoseEffect],
218228
pendingAutobolusUnits: Double? = nil,
219229
bolusDurationEstimator: @escaping (Double) -> TimeInterval?
220230
) {
221-
hasMissedMeal(insulinCounteractionEffects: insulinCounteractionEffects, carbEffects: carbEffects) {[weak self] status in
231+
hasMissedMeal(glucoseSamples: glucoseSamples, insulinCounteractionEffects: insulinCounteractionEffects, carbEffects: carbEffects) {[weak self] status in
222232
self?.manageMealNotifications(for: status, pendingAutobolusUnits: pendingAutobolusUnits, bolusDurationEstimator: bolusDurationEstimator)
223233
}
224234
}
@@ -294,3 +304,11 @@ class MealDetectionManager {
294304
completionHandler(report.joined(separator: "\n"))
295305
}
296306
}
307+
308+
fileprivate extension BidirectionalCollection where Element: GlucoseSampleValue, Index == Int {
309+
/// Returns whether there are any user-entered or calibration points
310+
/// Runtime: O(n)
311+
func containsUserEntered() -> Bool {
312+
return !isCalibrated() || filter({ $0.wasUserEntered }).count != 0
313+
}
314+
}

0 commit comments

Comments
 (0)