|
|
@@ -1,15 +1,5 @@
|
|
|
import Foundation
|
|
|
|
|
|
-struct DeterminationInputs {
|
|
|
- let profile: Profile
|
|
|
- let currentTemp: TempBasal
|
|
|
- let iobData: IobResult
|
|
|
- let autosensData: Autosens
|
|
|
- let mealData: ComputedCarbs?
|
|
|
- let reservoirData: Reservoir
|
|
|
- let currentTime: Date
|
|
|
-}
|
|
|
-
|
|
|
protocol OverrideHandler {
|
|
|
func overrideProfileParameters(profile: Profile, override: Override?) throws -> Profile
|
|
|
|
|
|
@@ -17,17 +7,18 @@ protocol OverrideHandler {
|
|
|
/// This could also possibly be handled via an extension of our existing `ProfileGenerator` (?)
|
|
|
}
|
|
|
|
|
|
-struct DeterminationGenerator {
|
|
|
- let profileGenerator: ProfileGenerator
|
|
|
- let iobGenerator: IobGenerator
|
|
|
- let autosensGenerator: AutosensGenerator
|
|
|
- let mealProcessor: MealTotal
|
|
|
-
|
|
|
+enum DeterminationGenerator {
|
|
|
// override data can just be fetched from the DB
|
|
|
// handling via overrideManager ?
|
|
|
|
|
|
- func generate(
|
|
|
- request _: DeterminationInputs
|
|
|
+ static func generate(
|
|
|
+ profile _: Profile,
|
|
|
+ currentTemp _: TempBasal,
|
|
|
+ iobData _: IobResult?,
|
|
|
+ mealData _: ComputedCarbs?,
|
|
|
+ autosensData _: Autosens,
|
|
|
+ reservoirData _: Reservoir,
|
|
|
+ currentTime _: Date
|
|
|
) throws -> Determination? {
|
|
|
// FIXME: implement... (return type will not be Optional; just to shut up the compiler)
|
|
|
|
|
|
@@ -66,17 +57,16 @@ struct DeterminationGenerator {
|
|
|
/// 7. Ignore Forecast & but guard-BG
|
|
|
/// 8. Compute carbsReq → we could move this to MEAL
|
|
|
/// 9. Decide temp basal → we could do a tempBasalGenerator ?
|
|
|
-
|
|
|
|
|
|
// TODO: how to handle output?
|
|
|
// TODO: how to handle logging?
|
|
|
-
|
|
|
+
|
|
|
nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
extension DeterminationGenerator {
|
|
|
- func calculateExpectedDelta(
|
|
|
+ public static func calculateExpectedDelta(
|
|
|
targetGlucose: Decimal,
|
|
|
eventualGlucose: Decimal,
|
|
|
glucoseImpact: Decimal
|
|
|
@@ -85,21 +75,121 @@ extension DeterminationGenerator {
|
|
|
// adjusted by the rate at which glucose would need to rise/fall
|
|
|
// to move eventual glucose to target over a 2 hr window
|
|
|
// TODO: expects that glucose can only be available in 5min chunks. do we need to change this handling?
|
|
|
-
|
|
|
+
|
|
|
let fiveMinuteBlocks = (2 * 60) / 5
|
|
|
let delta = targetGlucose - eventualGlucose
|
|
|
return glucoseImpact + Decimal(Int(delta) / fiveMinuteBlocks).rounded(toPlaces: 1)
|
|
|
-
|
|
|
}
|
|
|
-
|
|
|
- func isSMBEnabled(
|
|
|
- glucose _: BloodGlucose,
|
|
|
- profile _: Profile,
|
|
|
+
|
|
|
+ /// Determines whether SMBs are enabled based on profile settings,
|
|
|
+ /// computed meal data, CGM conditions, and any active overrides.
|
|
|
+ ///
|
|
|
+ /// Mirrors the JavaScript oref's `enable_smb()` logic.
|
|
|
+ ///
|
|
|
+ /// - Parameters:
|
|
|
+ /// - glucose: The latest blood glucose reading.
|
|
|
+ /// - profile: The user profile containing SMB preferences and temp-target flags.
|
|
|
+ /// - autosens: The autosens data (not used in this logic).
|
|
|
+ /// - mealData: Computed carbs-on-board and related meal information.
|
|
|
+ /// - override: An optional override controlling SMB scheduling and hard-off flags.
|
|
|
+ /// - shouldProtectDueToHIGH: `true` if CGM indicates a HIGH reading requiring SMB disable.
|
|
|
+ /// - currentTime: The current system time for scheduled-off evaluation.
|
|
|
+ /// - Returns: `true` if SMBs should be enabled, `false` otherwise.
|
|
|
+ public static func isSMBEnabled(
|
|
|
+ glucose: BloodGlucose,
|
|
|
+ profile: Profile,
|
|
|
autosens _: Autosens,
|
|
|
- date _: Date
|
|
|
+ mealData: ComputedCarbs?,
|
|
|
+ override: Override?,
|
|
|
+ shouldProtectDueToHIGH: Bool,
|
|
|
+ currentTime: Date
|
|
|
) -> Bool {
|
|
|
- // TODO: handle oref JS's enable_smb() logic
|
|
|
-
|
|
|
- true
|
|
|
+ if let override = override {
|
|
|
+ if override.smbIsScheduledOff {
|
|
|
+ let startHour = override.start
|
|
|
+ let endHour = override.end
|
|
|
+ let hour = Calendar.current.component(.hour, from: currentTime)
|
|
|
+
|
|
|
+ // disable SMB during the scheduled-off window [start, end)
|
|
|
+ if startHour < endHour {
|
|
|
+ if hour >= Int(startHour), hour < Int(endHour) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // disable SMB if window wraps midnight
|
|
|
+ else if startHour > endHour {
|
|
|
+ if hour >= Int(startHour) || hour < Int(endHour) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // special cases: off all day or single-hour off
|
|
|
+ else {
|
|
|
+ if startHour == 0, endHour == 0 {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if hour == Int(startHour) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if override.smbIsOff {
|
|
|
+ // hard-off override disables SMB entirely
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if let hasActiveTempTarget = profile.temptargetSet, hasActiveTempTarget {
|
|
|
+ // disable SMB when a high temp target is active and not allowed
|
|
|
+ if !profile.allowSMBWithHighTemptarget,
|
|
|
+ let targetGlucose = profile.targetBg,
|
|
|
+ targetGlucose > 100
|
|
|
+ {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // enable SMB when a low temp target is active
|
|
|
+ if profile.enableSMBWithTemptarget,
|
|
|
+ let targetGlucose = profile.targetBg,
|
|
|
+ targetGlucose < 100
|
|
|
+ {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // disable SMB for invalid CGM readings (HIGH)
|
|
|
+ if shouldProtectDueToHIGH {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // enable SMB unconditionally if always-on preference is set
|
|
|
+ if profile.enableSMBAlways {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // enable SMB when carbs-on-board (COB) exists
|
|
|
+ if profile.enableSMBWithCOB,
|
|
|
+ let cob = mealData?.mealCOB,
|
|
|
+ cob > 0
|
|
|
+ {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // enable SMB for the full post-carb window
|
|
|
+ if profile.enableSMBAfterCarbs,
|
|
|
+ let carbs = mealData?.carbs,
|
|
|
+ carbs > 0
|
|
|
+ {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // enable SMB when BG exceeds the high-BG threshold
|
|
|
+ if profile.enableSMBHighBg,
|
|
|
+ let glucoseVal = glucose.glucose ?? glucose.sgv,
|
|
|
+ glucoseVal >= Int(profile.enableSMBHighBgTarget)
|
|
|
+ {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // no enable condition met → disable SMB
|
|
|
+ return false
|
|
|
}
|
|
|
}
|