Просмотр исходного кода

Respect Max IOB and Max COB in Bolus Rec. Calculation

Also remove max bolus cap from bolus calculation until the end
Mike Plante 1 год назад
Родитель
Сommit
e65f399f07

+ 6 - 0
Trio/Sources/APS/APSManager.swift

@@ -24,6 +24,7 @@ protocol APSManager {
     func determineBasalSync() async
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
+    func roundBolusNoCap(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus(_ callback: ((Bool, String) -> Void)?) async
 }
@@ -432,6 +433,11 @@ final class BaseAPSManager: APSManager, Injectable {
         return min(rounded, maxBolus)
     }
 
+    func roundBolusNoCap(amount: Decimal) -> Decimal {
+        guard let pump = pumpManager else { return amount }
+        return Decimal(pump.roundToSupportedBolusVolume(units: Double(amount)))
+    }
+
     private var bolusReporter: DoseProgressReporter?
 
     func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async {

+ 6 - 0
Trio/Sources/Modules/Treatments/TreatmentsProvider.swift

@@ -33,5 +33,11 @@ extension Treatments {
                     sensitivities: []
                 )
         }
+
+        func getPreferences() async -> Preferences {
+            await storage.retrieveAsync(OpenAPS.Settings.preferences, as: Preferences.self)
+                ?? Preferences(from: OpenAPS.defaults(for: OpenAPS.Settings.preferences))
+                ?? Preferences(maxIOB: 0, maxCOB: 120)
+        }
     }
 }

+ 14 - 0
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -31,6 +31,8 @@ extension Treatments {
         var threshold: Decimal = 0
         var maxBolus: Decimal = 0
         var maxExternal: Decimal { maxBolus * 3 }
+        var maxIOB: Decimal = 0
+        var maxCOB: Decimal = 0
         var errorString: Decimal = 0
         var evBG: Decimal = 0
         var insulin: Decimal = 0
@@ -247,6 +249,18 @@ extension Treatments {
                         self.maxBolus = getMaxBolus
                     }
                 }
+                group.addTask {
+                    let getMaxIOB = await self.provider.getPreferences().maxIOB
+                    await MainActor.run {
+                        self.maxIOB = getMaxIOB
+                    }
+                }
+                group.addTask {
+                    let getMaxCOB = await self.provider.getPreferences().maxCOB
+                    await MainActor.run {
+                        self.maxCOB = getMaxCOB
+                    }
+                }
             }
         }
 

+ 64 - 21
Trio/Sources/Modules/Treatments/View/PopupView.swift

@@ -230,15 +230,29 @@ struct PopupView: View {
 
     var calcCOBRow: some View {
         GridRow(alignment: .center) {
-            HStack {
-                Text("COB:").foregroundColor(.secondary)
-                Text(
-                    state.wholeCob
-                        .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) +
-                        NSLocalizedString(" g", comment: "grams")
-                )
+            // Left column using ZStack to overlay Max COB
+            ZStack(alignment: .leading) {
+                // Main COB content
+                HStack {
+                    Text("COB:").foregroundColor(.secondary)
+                    Text(
+                        state.wholeCob
+                            .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) +
+                            NSLocalizedString(" g", comment: "grams")
+                    )
+                }
+
+                // Max COB overlay positioned below
+                if state.wholeCob >= state.maxCOB {
+                    Text("Max COB")
+                        .foregroundColor(Color.loopRed)
+                        .font(.caption)
+                        .offset(y: 16) // Adjust this value to position the text correctly
+                }
             }
+            .frame(height: 20) // Fixed height for main content only
 
+            // Middle column
             Text(
                 state.wholeCob
                     .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits)))
@@ -250,13 +264,15 @@ struct PopupView: View {
             .foregroundColor(.secondary)
             .gridColumnAlignment(.leading)
 
+            // Right column
             HStack {
                 Text(
                     self.insulinFormatter(state.wholeCobInsulin)
                 )
                 Text("U").foregroundColor(.secondary)
-            }.fontWeight(.bold)
-                .gridColumnAlignment(.trailing)
+            }
+            .fontWeight(.bold)
+            .gridColumnAlignment(.trailing)
         }
     }
 
@@ -396,20 +412,26 @@ struct PopupView: View {
         GridRow(alignment: .bottom) {
             if state.useFattyMealCorrectionFactor {
                 Group {
-                    Text("Factor x Fatty Meal Factor x Full Bolus")
-                        .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
-                        +
-                        Text(state.wholeCalc > state.maxBolus ? " ≈ Max Bolus" : "").foregroundColor(Color.loopRed)
+                    getFormulaText("Factor x Fatty Meal Factor x Full Bolus", colorScheme: colorScheme) +
+                        getCappedText(
+                            wholeCalc: state.wholeCalc,
+                            maxBolus: state.maxBolus,
+                            maxIOB: state.maxIOB,
+                            iob: state.iob
+                        )
                 }
                 .font(.caption)
                 .gridCellAnchor(.center)
                 .gridCellColumns(3)
             } else if state.useSuperBolus {
                 Group {
-                    Text("(Factor x Full Bolus) + Super Bolus")
-                        .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
-                        +
-                        Text(state.wholeCalc > state.maxBolus ? " ≈ Max Bolus" : "").foregroundColor(Color.loopRed)
+                    getFormulaText("(Factor x Full Bolus) + Super Bolus", colorScheme: colorScheme) +
+                        getCappedText(
+                            wholeCalc: state.wholeCalc,
+                            maxBolus: state.maxBolus,
+                            maxIOB: state.maxIOB,
+                            iob: state.iob
+                        )
                 }
                 .font(.caption)
                 .gridCellAnchor(.center)
@@ -417,10 +439,13 @@ struct PopupView: View {
             } else {
                 Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
                 Group {
-                    Text("Factor x Full Bolus")
-                        .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
-                        +
-                        Text(state.wholeCalc > state.maxBolus ? " ≈ Max Bolus" : "").foregroundColor(Color.loopRed)
+                    getFormulaText("Factor x Full Bolus", colorScheme: colorScheme) +
+                        getCappedText(
+                            wholeCalc: state.wholeCalc,
+                            maxBolus: state.maxBolus,
+                            maxIOB: state.maxIOB,
+                            iob: state.iob
+                        )
                 }
                 .font(.caption)
                 .padding(.top, 5)
@@ -467,3 +492,21 @@ struct PopupView: View {
         }
     }
 }
+
+extension View {
+    // Function to generate the warning text for max bolus/IOB
+    func getCappedText(wholeCalc: Decimal, maxBolus: Decimal, maxIOB: Decimal, iob: Decimal) -> Text {
+        let limitedByMaxBolus = wholeCalc >= maxBolus && maxBolus < maxIOB - iob
+        let limitedByMaxIOB = wholeCalc >= maxIOB - iob
+        return Text(
+            limitedByMaxBolus ? " ≈ Max Bolus" :
+                limitedByMaxIOB ? " ≈ Max IOB" : ""
+        ).foregroundColor(Color.loopRed)
+    }
+
+    // Function to generate the formula text with opacity
+    func getFormulaText(_ text: String, colorScheme: ColorScheme) -> Text {
+        Text(text)
+            .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
+    }
+}

+ 22 - 5
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -189,6 +189,14 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             )
     }
 
+    /// Returns maxIOB and maxCOB from storage
+    /// - Returns:  object containing maxIOB and maxCOB limits
+    private func getPreferences() async -> Preferences {
+        await fileStorage.retrieveAsync(OpenAPS.Settings.preferences, as: Preferences.self)
+            ?? Preferences(from: OpenAPS.defaults(for: OpenAPS.Settings.preferences))
+            ?? Preferences(maxIOB: 0, maxCOB: 0)
+    }
+
     /// Fetches recent glucose readings from CoreData
     /// - Returns: Array of NSManagedObjectIDs for glucose readings
     private func fetchGlucose() async -> [NSManagedObjectID] {
@@ -279,6 +287,10 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         let currentBGTarget = await getCurrentSettingValue(for: .bgTarget)
         let currentISF = await getCurrentSettingValue(for: .isf)
 
+        // Get max IOB and max COB
+        let maxIOB = await getPreferences().maxIOB
+        let maxCOB = await getPreferences().maxCOB
+
         // Fetch glucose data
         let glucoseIds = await fetchGlucose()
         let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(
@@ -322,7 +334,9 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             sweetMealFactor: settings.sweetMealFactor,
             basal: bolusVars.basal,
             fraction: settings.fraction,
-            maxBolus: maxBolus
+            maxBolus: maxBolus,
+            maxIOB: maxIOB,
+            maxCOB: maxCOB
         )
     }
 
@@ -332,14 +346,14 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     func calculateInsulin(input: CalculationInput) async -> CalculationResult {
         // insulin needed for the current blood glucose
         let targetDifference = input.currentBG - input.target
-        let targetDifferenceInsulin = apsManager.roundBolus(amount: targetDifference / input.isf)
+        let targetDifferenceInsulin = apsManager.roundBolusNoCap(amount: targetDifference / input.isf)
 
         // more or less insulin because of bg trend in the last 15 minutes
-        let fifteenMinutesInsulin = apsManager.roundBolus(amount: input.deltaBG / input.isf)
+        let fifteenMinutesInsulin = apsManager.roundBolusNoCap(amount: input.deltaBG / input.isf)
 
         // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
-        let wholeCob = Decimal(input.cob) + input.carbs
-        let wholeCobInsulin = apsManager.roundBolus(amount: wholeCob / input.carbRatio)
+        let wholeCob = min(Decimal(input.cob) + input.carbs, input.maxCOB)
+        let wholeCobInsulin = apsManager.roundBolusNoCap(amount: wholeCob / input.carbRatio)
 
         // determine how much the calculator reduces/ increases the bolus because of IOB
         let iobInsulinReduction = (-1) * input.iob
@@ -377,6 +391,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         // display no negative insulinCalculated
         insulinCalculated = max(insulinCalculated, 0)
         insulinCalculated = min(insulinCalculated, input.maxBolus)
+        insulinCalculated = min(insulinCalculated, input.maxIOB - input.iob)
 
         // round calculated recommendation to allowed bolus increment
         insulinCalculated = apsManager.roundBolus(amount: insulinCalculated)
@@ -429,6 +444,8 @@ struct CalculationInput: Sendable {
     let basal: Decimal // Current basal rate
     let fraction: Decimal // General correction factor
     let maxBolus: Decimal // Maximum allowed bolus
+    let maxIOB: Decimal // Maximum allowed IOB to be used for rec. bolus calculation
+    let maxCOB: Decimal // Maximum allowed COB to be used for rec. bolus calculation
 }
 
 /// Results of the bolus calculation