Przeglądaj źródła

Merge pull request #447 from nightscout/fix-bolus-calc-limits

Use Simulated Determination for Bolus Calculator Limits
Deniz Cengiz 1 rok temu
rodzic
commit
fa48a4b88f

+ 19 - 0
Model/Helper/Determination+helper.swift

@@ -11,6 +11,25 @@ extension OrefDetermination {
     }
 }
 
+extension Determination {
+    var minPredBGFromReason: Decimal? {
+        // Split reason into parts by semicolon and get first part
+        let reasonParts = reason.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []
+
+        // Find the part that contains "minPredBG"
+        if let minPredBGPart = reasonParts.first(where: { $0.contains("minPredBG") }) {
+            // Extract the number after "minPredBG"
+            let components = minPredBGPart.components(separatedBy: "minPredBG ")
+            if let valueComponent = components.dropFirst().first {
+                // Get everything after "minPredBG " and convert to Decimal
+                let valueString = valueComponent.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.-").inverted)
+                return Decimal(string: valueString)
+            }
+        }
+        return nil
+    }
+}
+
 extension OrefDetermination {
     var reasonParts: [String] {
         reason?.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []

+ 15 - 2
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -371,10 +371,16 @@ extension Treatments {
 
         /// Calculate insulin recommendation
         func calculateInsulin() async -> Decimal {
+            // Safely get minPredBG on main thread
+            let localMinPredBG = await MainActor.run {
+                minPredBG
+            }
+
             let result = await bolusCalculationManager.handleBolusCalculation(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrectionFactor,
-                useSuperBolus: useSuperBolus
+                useSuperBolus: useSuperBolus,
+                minPredBG: localMinPredBG
             )
 
             // Update state properties with calculation results on main thread
@@ -743,7 +749,6 @@ extension Treatments.StateModel {
 
             let determination = await determinationFetchContext.perform {
                 let determinationObject = determinationObjects.first
-                let eventualBG = determinationObject?.eventualBG?.intValue
 
                 let forecastsSet = determinationObject?.forecasts ?? []
                 let predictions = Predictions(
@@ -820,11 +825,19 @@ extension Treatments.StateModel {
         debug(.bolusState, "updateForecasts fired")
         if let forecastData = forecastData {
             simulatedDetermination = forecastData
+            debugPrint("\(DebuggingIdentifiers.failed) minPredBG: \(minPredBG)")
         } else {
             simulatedDetermination = await Task { [self] in
                 debug(.bolusState, "calling simulateDetermineBasal to get forecast data")
                 return await apsManager.simulateDetermineBasal(simulatedCarbsAmount: carbs, simulatedBolusAmount: amount)
             }.value
+
+            // Update evBG and minPredBG from simulated determination
+            if let simDetermination = simulatedDetermination {
+                evBG = Decimal(simDetermination.eventualBG ?? 0)
+                minPredBG = simDetermination.minPredBGFromReason ?? 0
+                debugPrint("\(DebuggingIdentifiers.inProgress) minPredBG: \(minPredBG)")
+            }
         }
 
         predictionsForChart = simulatedDetermination?.predictions

+ 1 - 1
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -73,8 +73,8 @@ extension Treatments {
             debounce?.cancel()
             debounce = DispatchWorkItem { [self] in
                 Task {
-                    state.insulinCalculated = await state.calculateInsulin()
                     await state.updateForecasts()
+                    state.insulinCalculated = await state.calculateInsulin()
                 }
             }
             if let debounce = debounce {

+ 31 - 6
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -4,7 +4,8 @@ import Swinject
 
 protocol BolusCalculationManager {
     func calculateInsulin(input: CalculationInput) async -> CalculationResult
-    func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool) async -> CalculationResult
+    func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool, minPredBG: Decimal?) async
+        -> CalculationResult
 }
 
 final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
@@ -280,7 +281,8 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     private func prepareCalculationInput(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
-        useSuperBolus: Bool
+        useSuperBolus: Bool,
+        minPredBG: Decimal?
     ) async throws -> CalculationInput {
         do {
             // Get settings
@@ -296,7 +298,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             let currentISF = await getCurrentSettingValue(for: .isf)
 
             // Get max IOB and max COB
-
             let preferences = await getPreferences()
             let maxIOB = preferences.maxIOB
             let maxCOB = preferences.maxCOB
@@ -347,7 +348,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 maxBolus: maxBolus,
                 maxIOB: maxIOB,
                 maxCOB: maxCOB,
-                minPredBG: bolusVars.minPredBG
+                minPredBG: minPredBG ?? bolusVars.minPredBG
             )
         } catch {
             debug(
@@ -365,45 +366,56 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     func calculateInsulin(input: CalculationInput) async -> CalculationResult {
         // insulin needed for the current blood glucose
         let targetDifference = input.currentBG - input.target
+        debug(.default, "Target difference: \(targetDifference)")
 
         let targetDifferenceInsulin = targetDifference / input.isf
+        debug(.default, "Target difference insulin: \(targetDifferenceInsulin)")
 
         // more or less insulin because of bg trend in the last 15 minutes
         let fifteenMinutesInsulin = input.deltaBG / input.isf
+        debug(.default, "15min insulin: \(fifteenMinutesInsulin)")
 
         // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
         let wholeCob = min(Decimal(input.cob) + input.carbs, input.maxCOB)
         let wholeCobInsulin = wholeCob / input.carbRatio
+        debug(.default, "Whole COB: \(wholeCob), COB insulin: \(wholeCobInsulin)")
 
         // determine how much the calculator reduces/ increases the bolus because of IOB
         let iobInsulinReduction = (-1) * input.iob
+        debug(.default, "IOB reduction: \(iobInsulinReduction)")
 
         // adding everything together
         // add a calc for the case that no fifteenMinInsulin is available
         let wholeCalc: Decimal
         if input.deltaBG != 0 {
             wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinutesInsulin)
+            debug(.default, "Whole calc (with delta): \(wholeCalc)")
         } else {
             // add (rare) case that no glucose value is available -> maybe display warning?
             // if no bg is available, ?? sets its value to 0
             if input.currentBG == 0 {
                 wholeCalc = (iobInsulinReduction + wholeCobInsulin)
+                debug(.default, "Whole calc (no BG): \(wholeCalc)")
             } else {
                 wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
+                debug(.default, "Whole calc (no delta): \(wholeCalc)")
             }
         }
 
         // apply custom factor at the end of the calculations
         // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
         var factoredInsulin = wholeCalc
+        debug(.default, "Initial factored insulin: \(factoredInsulin)")
 
         // Apply Recommended Bolus Percentage (input.fraction) and if selected apply Fatty Meal Bolus Percentage (input.fattyMealFactor)
         // If factoredInsulin is negative, though, don't apply either
         if factoredInsulin > 0 {
             factoredInsulin *= input.fraction
+            debug(.default, "After fraction (\(input.fraction)): \(factoredInsulin)")
 
             if input.useFattyMealCorrectionFactor {
                 factoredInsulin *= input.fattyMealFactor
+                debug(.default, "After fatty meal factor (\(input.fattyMealFactor)): \(factoredInsulin)")
             }
         }
 
@@ -412,24 +424,34 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         if input.useSuperBolus {
             superBolusInsulin = input.sweetMealFactor * input.basal
             factoredInsulin += superBolusInsulin
+            debug(.default, "After super bolus (\(superBolusInsulin)): \(factoredInsulin)")
         }
 
         // the final result for recommended insulin amount
         var insulinCalculated: Decimal
         let isLoopStale = Date().timeIntervalSince(apsManager.lastLoopDate) > 15 * 60
+        debug(.default, "Loop stale: \(isLoopStale), currentBG: \(input.currentBG), minPredBG: \(input.minPredBG)")
 
         // don't recommend insulin when current glucose or minPredBG is < 54 or last sucessful loop was over 15 minutes ago
         if input.currentBG < 54 || input.minPredBG < 54 || isLoopStale {
             insulinCalculated = 0
+            debug(.default, "Insulin set to 0 due to safety check - BG < 54 or stale loop")
         } else {
             // no negative insulinCalculated
             insulinCalculated = max(factoredInsulin, 0)
+            debug(.default, "After max(0): \(insulinCalculated)")
+
             // don't exceed maxBolus
             insulinCalculated = min(insulinCalculated, input.maxBolus)
+            debug(.default, "After maxBolus (\(input.maxBolus)): \(insulinCalculated)")
+
             // don't exceed maxIOB
             insulinCalculated = min(insulinCalculated, input.maxIOB - input.iob)
+            debug(.default, "After maxIOB check (\(input.maxIOB) - \(input.iob)): \(insulinCalculated)")
+
             // round calculated recommendation to allowed bolus increment
             insulinCalculated = apsManager.roundBolus(amount: insulinCalculated)
+            debug(.default, "Final rounded insulin: \(insulinCalculated)")
         }
 
         return CalculationResult(
@@ -452,17 +474,20 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     ///   - carbs: Amount of carbohydrates to be consumed
     ///   - useFattyMealCorrection: Whether to apply fatty meal correction
     ///   - useSuperBolus: Whether to use super bolus calculation
+    ///   - minPredBG: Minimum Predicted Glucose determined by Oref
     /// - Returns: CalculationResult containing the calculated insulin dose and details
     func handleBolusCalculation(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
-        useSuperBolus: Bool
+        useSuperBolus: Bool,
+        minPredBG: Decimal? = nil
     ) async -> CalculationResult {
         do {
             let input = try await prepareCalculationInput(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrection,
-                useSuperBolus: useSuperBolus
+                useSuperBolus: useSuperBolus,
+                minPredBG: minPredBG
             )
             let result = await calculateInsulin(input: input)
             return result

+ 2 - 1
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -588,7 +588,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                     let result = await bolusCalculationManager.handleBolusCalculation(
                         carbs: Decimal(carbs),
                         useFattyMealCorrection: false,
-                        useSuperBolus: false
+                        useSuperBolus: false,
+                        minPredBG: 60 // TODO:
                     )
 
                     // Send recommendation back to watch