Quellcode durchsuchen

Fix uam and smb minutes calculation

Sam King vor 5 Monaten
Ursprung
Commit
6bb45dabe2

+ 1 - 1
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DetermineBasalGenerator.swift

@@ -476,6 +476,7 @@ enum DeterminationGenerator {
             currentGlucose: currentGlucose,
             threshold: threshold,
             profile: profile,
+            trioCustomOrefVariables: trioCustomOrefVariables,
             mealData: mealData,
             iobData: iobData,
             currentTime: currentTime,
@@ -483,7 +484,6 @@ enum DeterminationGenerator {
             naiveEventualGlucose: naiveEventualGlucose,
             minIOBForecastedGlucose: forecastResult.minIOBForecastedGlucose,
             adjustedSensitivity: adjustedSensitivity,
-            overrideFactor: trioCustomOrefVariables.overrideFactor(),
             adjustedCarbRatio: forecastResult.adjustedCarbRatio,
             basal: basal,
             determination: determination

+ 45 - 9
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DosingEngine.swift

@@ -693,6 +693,40 @@ enum DosingEngine {
         return (insulinRequired, newDetermination)
     }
 
+    /// Determines the maxBolus possible for a Super Micro Bolus (SMB)
+    static func determineMaxBolus(
+        currentBasal: Decimal,
+        currentIob: Decimal,
+        adjustedCarbRatio: Decimal,
+        mealData: ComputedCarbs,
+        profile: Profile,
+        trioCustomOrefVariables: TrioCustomOrefVariables
+    ) -> Decimal {
+        let mealInsulinRequired = (mealData.mealCOB / adjustedCarbRatio).jsRounded(scale: 3)
+        let overrideFactor = trioCustomOrefVariables.overrideFactor()
+
+        var smbMinutesSetting = profile.maxSMBBasalMinutes
+        if trioCustomOrefVariables.useOverride, trioCustomOrefVariables.advancedSettings {
+            smbMinutesSetting = trioCustomOrefVariables.smbMinutes
+        }
+
+        var uamMinutesSetting = profile.maxUAMSMBBasalMinutes
+        if trioCustomOrefVariables.useOverride, trioCustomOrefVariables.advancedSettings {
+            uamMinutesSetting = trioCustomOrefVariables.uamMinutes
+        }
+
+        if currentIob > mealInsulinRequired, currentIob > 0 {
+            if uamMinutesSetting > 0 {
+                return (currentBasal * overrideFactor * uamMinutesSetting / 60).jsRounded(scale: 1)
+            } else {
+                // Note: It should be impossible to have uamMinutesSetting of 0 so this shouldn't execute
+                return (currentBasal * overrideFactor * 30 / 60).jsRounded(scale: 1)
+            }
+        } else {
+            return (currentBasal * overrideFactor * smbMinutesSetting / 60).jsRounded(scale: 1)
+        }
+    }
+
     /// Determines if a Super Micro Bolus (SMB) should be delivered and calculates its size and associated temp basal.
     ///
     /// - Returns: A tuple containing:
@@ -705,6 +739,7 @@ enum DosingEngine {
         currentGlucose: Decimal,
         threshold: Decimal,
         profile: Profile,
+        trioCustomOrefVariables: TrioCustomOrefVariables,
         mealData: ComputedCarbs,
         iobData: [IobResult],
         currentTime: Date,
@@ -712,7 +747,6 @@ enum DosingEngine {
         naiveEventualGlucose: Decimal,
         minIOBForecastedGlucose: Decimal,
         adjustedSensitivity: Decimal,
-        overrideFactor: Decimal,
         adjustedCarbRatio: Decimal,
         basal: Decimal,
         determination: Determination
@@ -731,13 +765,14 @@ enum DosingEngine {
             return (false, newDetermination)
         }
 
-        let mealInsulinRequired = (mealData.mealCOB / adjustedCarbRatio).jsRounded(scale: 3)
-
-        let maxBolusCondition: Bool = currentIob > mealInsulinRequired && currentIob > 0
-        let maxBolus: Decimal = (
-            maxBolusCondition ? (currentBasal * overrideFactor * profile.maxUAMSMBBasalMinutes / 60) :
-                (currentBasal * overrideFactor * profile.maxSMBBasalMinutes / 60)
-        ).jsRounded(scale: 1)
+        let maxBolus = determineMaxBolus(
+            currentBasal: currentBasal,
+            currentIob: currentIob,
+            adjustedCarbRatio: adjustedCarbRatio,
+            mealData: mealData,
+            profile: profile,
+            trioCustomOrefVariables: trioCustomOrefVariables
+        )
 
         let smbDeliveryRatio = min(profile.smbDeliveryRatio, 1)
         let roundSmbTo = 1 / profile.bolusIncrement
@@ -746,7 +781,8 @@ enum DosingEngine {
 
         let worstCaseInsulinRequired = (targetGlucose - (naiveEventualGlucose + minIOBForecastedGlucose) / 2) /
             adjustedSensitivity
-        var durationRequired = (60 * worstCaseInsulinRequired / currentBasal * overrideFactor).jsRounded()
+        var durationRequired = (60 * worstCaseInsulinRequired / currentBasal * trioCustomOrefVariables.overrideFactor())
+            .jsRounded()
 
         // if insulinRequired > 0 but not enough for a microBolus, don't set an SMB zero temp
         if insulinRequired > 0, microBolus < profile.bolusIncrement {

+ 256 - 11
TrioTests/OpenAPSSwiftTests/DetermineBasalAggressiveDosingTests.swift

@@ -172,7 +172,7 @@ import Testing
             lastCarbTime: 0
         ),
         iobData: [IobResult],
-        overrideFactor: Decimal = 1.0,
+        trioCustomOrefVariables: TrioCustomOrefVariables? = nil,
         adjustedCarbRatio: Decimal = 10,
         basal: Decimal = 1.0,
         naiveEventualGlucose: Decimal = 120,
@@ -208,6 +208,34 @@ import Testing
             received: nil
         )
 
+        var finalTrioCustomOrefVariables: TrioCustomOrefVariables
+        if let customOrefVars = trioCustomOrefVariables {
+            finalTrioCustomOrefVariables = customOrefVars
+        } else {
+            finalTrioCustomOrefVariables = TrioCustomOrefVariables(
+                average_total_data: 0,
+                weightedAverage: 0,
+                currentTDD: 0,
+                past2hoursAverage: 0,
+                date: Date(),
+                overridePercentage: 100,
+                useOverride: false,
+                duration: 0,
+                unlimited: false,
+                overrideTarget: 0,
+                smbIsOff: false,
+                advancedSettings: false,
+                isfAndCr: false,
+                isf: false,
+                cr: false,
+                smbIsScheduledOff: false,
+                start: 0,
+                end: 0,
+                smbMinutes: 30,
+                uamMinutes: 30
+            )
+        }
+
         return try DosingEngine.determineSMBDelivery(
             insulinRequired: insulinRequired,
             microBolusAllowed: microBolusAllowed,
@@ -215,6 +243,7 @@ import Testing
             currentGlucose: currentGlucose,
             threshold: threshold,
             profile: profile,
+            trioCustomOrefVariables: finalTrioCustomOrefVariables,
             mealData: mealData,
             iobData: iobData,
             currentTime: Date(),
@@ -222,7 +251,6 @@ import Testing
             naiveEventualGlucose: naiveEventualGlucose,
             minIOBForecastedGlucose: minIOBForecastedGlucose,
             adjustedSensitivity: profile.sens ?? 40,
-            overrideFactor: overrideFactor,
             adjustedCarbRatio: adjustedCarbRatio,
             basal: basal,
             determination: determination
@@ -322,30 +350,114 @@ import Testing
         // insulinReq = 3.0
         // smb = min(3.0 * 0.5, 1.0) = min(1.5, 1.0) = 1.0
 
+        var customOrefVars = TrioCustomOrefVariables(
+            average_total_data: 0,
+            weightedAverage: 0,
+            currentTDD: 0,
+            past2hoursAverage: 0,
+            date: Date(),
+            overridePercentage: 200, // 2.0 * 100
+            useOverride: true,
+            duration: 0,
+            unlimited: false,
+            overrideTarget: 0,
+            smbIsOff: false,
+            advancedSettings: false,
+            isfAndCr: false,
+            isf: false,
+            cr: false,
+            smbIsScheduledOff: false,
+            start: 0,
+            end: 0,
+            smbMinutes: 30,
+            uamMinutes: 30
+        )
+
         let result = try callDetermineSMBDelivery(
             insulinRequired: 3.0,
             profile: profile,
             iobData: iobData,
-            overrideFactor: 2.0
+            trioCustomOrefVariables: customOrefVars
         )
 
         #expect(result.determination.units == 1.0)
     }
 
-    @Test("should use UAM max minutes when appropriate") func testUAMMaxMinutes() throws {
+    @Test(
+        "should override smbMinutes and uamMinutes when useOverride and advancedSettings are true"
+    ) func testOverrideSmbUamMinutes() throws {
         var profile = Profile()
         profile.currentBasal = 1.0
         profile.maxSMBBasalMinutes = 30 // 0.5U
-        profile.maxUAMSMBBasalMinutes = 60 // 1.0U
+        profile.maxUAMSMBBasalMinutes = 30 // 0.5U
         profile.smbDeliveryRatio = 0.5
         profile.bolusIncrement = 0.1
         profile.sens = 40
         profile.targetBg = 100
 
         let now = Date()
-        let lastBolusTime = UInt64(now.addingTimeInterval(-600).timeIntervalSince1970 * 1000) // 10 minutes ago
+        let lastBolusTime = UInt64(now.addingTimeInterval(-600).timeIntervalSince1970 * 1000)
+
+        // Case 1: Regular SMB (IOB <= mealInsulinReq)
+        // insulinReq = 3.0
+        // maxBolus should be 1.0 (60 mins override) instead of 0.5 (30 mins profile)
+        // smb = min(3.0 * 0.5, 1.0) = 1.0
+
+        var customOrefVars = TrioCustomOrefVariables(
+            average_total_data: 0,
+            weightedAverage: 0,
+            currentTDD: 0,
+            past2hoursAverage: 0,
+            date: Date(),
+            overridePercentage: 100,
+            useOverride: true,
+            duration: 0,
+            unlimited: false,
+            overrideTarget: 0,
+            smbIsOff: false,
+            advancedSettings: true,
+            isfAndCr: false,
+            isf: false,
+            cr: false,
+            smbIsScheduledOff: false,
+            start: 0,
+            end: 0,
+            smbMinutes: 60,
+            uamMinutes: 60
+        )
+
+        let dummyIobWithZeroTemp = IobResult.IobWithZeroTemp(
+            iob: 0,
+            activity: 0,
+            basaliob: 0,
+            bolusiob: 0,
+            netbasalinsulin: 0,
+            bolusinsulin: 0,
+            time: Date()
+        )
+        let iobData = [IobResult(
+            iob: 0,
+            activity: 0,
+            basaliob: 0,
+            bolusiob: 0,
+            netbasalinsulin: 0,
+            bolusinsulin: 0,
+            time: Date(),
+            iobWithZeroTemp: dummyIobWithZeroTemp,
+            lastBolusTime: lastBolusTime,
+            lastTemp: nil
+        )]
 
-        // IOB > mealInsulinReq
+        let result = try callDetermineSMBDelivery(
+            insulinRequired: 3.0,
+            profile: profile,
+            iobData: iobData,
+            trioCustomOrefVariables: customOrefVars
+        )
+
+        #expect(result.determination.units == 1.0)
+
+        // Case 2: UAM SMB (IOB > mealInsulinReq)
         // mealCOB = 10, CR = 10 => mealInsulinReq = 1.0
         // iob = 1.5
         let mealData = ComputedCarbs(
@@ -359,6 +471,67 @@ import Testing
             allDeviations: [],
             lastCarbTime: 0
         )
+        let uamIobData = [IobResult(
+            iob: 1.5,
+            activity: 0,
+            basaliob: 0,
+            bolusiob: 0,
+            netbasalinsulin: 0,
+            bolusinsulin: 0,
+            time: Date(),
+            iobWithZeroTemp: dummyIobWithZeroTemp,
+            lastBolusTime: lastBolusTime,
+            lastTemp: nil
+        )]
+
+        let uamResult = try callDetermineSMBDelivery(
+            insulinRequired: 3.0,
+            profile: profile,
+            mealData: mealData,
+            iobData: uamIobData,
+            trioCustomOrefVariables: customOrefVars
+        )
+
+        #expect(uamResult.determination.units == 1.0)
+    }
+
+    @Test(
+        "should not override smbMinutes and uamMinutes when advancedSettings is false"
+    ) func testNoOverrideWhenAdvancedSettingsFalse() throws {
+        var profile = Profile()
+        profile.currentBasal = 1.0
+        profile.maxSMBBasalMinutes = 30 // 0.5U
+        profile.smbDeliveryRatio = 0.5
+        profile.bolusIncrement = 0.1
+        profile.sens = 40
+        profile.targetBg = 100
+
+        let now = Date()
+        let lastBolusTime = UInt64(now.addingTimeInterval(-600).timeIntervalSince1970 * 1000)
+
+        var customOrefVars = TrioCustomOrefVariables(
+            average_total_data: 0,
+            weightedAverage: 0,
+            currentTDD: 0,
+            past2hoursAverage: 0,
+            date: Date(),
+            overridePercentage: 100,
+            useOverride: true,
+            duration: 0,
+            unlimited: false,
+            overrideTarget: 0,
+            smbIsOff: false,
+            advancedSettings: false,
+            isfAndCr: false,
+            isf: false,
+            cr: false,
+            smbIsScheduledOff: false,
+            start: 0,
+            end: 0,
+            smbMinutes: 60,
+            uamMinutes: 60
+        )
+
         let dummyIobWithZeroTemp = IobResult.IobWithZeroTemp(
             iob: 0,
             activity: 0,
@@ -369,7 +542,7 @@ import Testing
             time: Date()
         )
         let iobData = [IobResult(
-            iob: 1.5,
+            iob: 0,
             activity: 0,
             basaliob: 0,
             bolusiob: 0,
@@ -382,13 +555,85 @@ import Testing
         )]
 
         // insulinReq = 3.0
-        // smb = min(3.0 * 0.5, 1.0) = 1.0 (uses UAM limit)
+        // maxBolus should be 0.5 (30 mins from profile), ignoring override 60 because advancedSettings is false
+        // smb = min(1.5, 0.5) = 0.5
 
         let result = try callDetermineSMBDelivery(
             insulinRequired: 3.0,
             profile: profile,
-            mealData: mealData,
-            iobData: iobData
+            iobData: iobData,
+            trioCustomOrefVariables: customOrefVars
+        )
+
+        #expect(result.determination.units == 0.5)
+    }
+
+    @Test("should use overridePercentage from custom vars if provided") func testOverridePercentageFromCustomVars() throws {
+        var profile = Profile()
+        profile.currentBasal = 1.0
+        profile.maxSMBBasalMinutes = 30 // 0.5U
+        profile.smbDeliveryRatio = 0.5
+        profile.bolusIncrement = 0.1
+        profile.sens = 40
+        profile.targetBg = 100
+
+        let now = Date()
+        let lastBolusTime = UInt64(now.addingTimeInterval(-600).timeIntervalSince1970 * 1000)
+
+        var customOrefVars = TrioCustomOrefVariables(
+            average_total_data: 0,
+            weightedAverage: 0,
+            currentTDD: 0,
+            past2hoursAverage: 0,
+            date: Date(),
+            overridePercentage: 200, // 200%
+            useOverride: true,
+            duration: 0,
+            unlimited: false,
+            overrideTarget: 0,
+            smbIsOff: false,
+            advancedSettings: false,
+            isfAndCr: false,
+            isf: false,
+            cr: false,
+            smbIsScheduledOff: false,
+            start: 0,
+            end: 0,
+            smbMinutes: 30,
+            uamMinutes: 30
+        )
+
+        let dummyIobWithZeroTemp = IobResult.IobWithZeroTemp(
+            iob: 0,
+            activity: 0,
+            basaliob: 0,
+            bolusiob: 0,
+            netbasalinsulin: 0,
+            bolusinsulin: 0,
+            time: Date()
+        )
+        let iobData = [IobResult(
+            iob: 0,
+            activity: 0,
+            basaliob: 0,
+            bolusiob: 0,
+            netbasalinsulin: 0,
+            bolusinsulin: 0,
+            time: Date(),
+            iobWithZeroTemp: dummyIobWithZeroTemp,
+            lastBolusTime: lastBolusTime,
+            lastTemp: nil
+        )]
+
+        // maxBolus = 1.0 * 2.0 * 30/60 = 1.0
+        // insulinReq = 3.0
+        // smb = min(3.0 * 0.5, 1.0) = 1.0
+
+        let result = try callDetermineSMBDelivery(
+            insulinRequired: 3.0,
+            profile: profile,
+            iobData: iobData,
+            trioCustomOrefVariables: customOrefVars
         )
 
         #expect(result.determination.units == 1.0)