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

check in OpenAPS when enacting presets

move HBP and Sens calculations to Helper file
Robert 5 месяцев назад
Родитель
Сommit
57e1455c93

+ 20 - 16
Trio.xcodeproj/project.pbxproj

@@ -266,6 +266,7 @@
 		491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */; };
 		491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */; };
 		491D6FC02D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */; };
+		49239B432EEA27AD00469145 /* TempTargetCalculations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49239B422EEA27AD00469145 /* TempTargetCalculations.swift */; };
 		49B9B57F2D5768D2009C6B59 /* AdjustmentStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
@@ -1092,6 +1093,7 @@
 		491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
+		49239B422EEA27AD00469145 /* TempTargetCalculations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetCalculations.swift; sourceTree = "<group>"; };
 		49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentStored+Helper.swift"; sourceTree = "<group>"; };
 		4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorRootView.swift; sourceTree = "<group>"; };
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
@@ -2418,44 +2420,45 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
-				DDD5889C2DDDC9A900C8848D /* TimeAgoFormatter.swift */,
-				DD82D4B72DCAB2BA00BAFC77 /* PropertyPersistentFlags.swift */,
-				DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */,
-				BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */,
+				E06B9119275B5EEA003C04B6 /* Array+Extension.swift */,
 				DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */,
+				DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */,
+				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
+				BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */,
 				CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */,
-				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
-				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
+				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */,
+				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
+				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
 				3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */,
 				38C4D33625E9A1A200D30B77 /* DispatchQueue+Extensions.swift */,
 				389487392614928B004DF424 /* DispatchTimer.swift */,
+				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				3811DE5425C9D4D500A708ED /* Formatters.swift */,
 				38FEF412273B317A00574A46 /* HKUnit.swift */,
 				38B4F3AE25E2979F00E76A18 /* IndexedCollection.swift */,
 				389A571F26079BAA00BC102F /* Interpolation.swift */,
 				388E5A5B25B6F0770019842D /* JSON.swift */,
 				38A00B2225FC2B55006BC0B0 /* LRUCache.swift */,
+				582DF9782C8CE1E5001F516D /* MainChartHelper.swift */,
 				38FCF3D525E8FDF40078B0D1 /* MD5.swift */,
 				38E98A2C25F52DC400C0CED0 /* NSLocking+Extensions.swift */,
 				38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */,
 				3811DE5725C9D4D500A708ED /* ProgressBar.swift */,
+				DD82D4B72DCAB2BA00BAFC77 /* PropertyPersistentFlags.swift */,
+				3811DEE325CA063400A708ED /* PropertyWrappers */,
 				3811DE5525C9D4D500A708ED /* Publisher.swift */,
+				DD6B7CB12C7B6F0800B75029 /* Rounding.swift */,
+				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
 				38E98A3625F5509500C0CED0 /* String+Extensions.swift */,
-				3811DEE325CA063400A708ED /* PropertyWrappers */,
-				E06B9119275B5EEA003C04B6 /* Array+Extension.swift */,
+				49239B422EEA27AD00469145 /* TempTargetCalculations.swift */,
+				DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */,
+				DDD5889C2DDDC9A900C8848D /* TimeAgoFormatter.swift */,
+				BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */,
 				CEB434E428B8FF5D00B70274 /* UIColor.swift */,
-				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
-				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */,
-				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
-				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
-				DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */,
-				DD6B7CB12C7B6F0800B75029 /* Rounding.swift */,
-				582DF9782C8CE1E5001F516D /* MainChartHelper.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -4270,6 +4273,7 @@
 				DDF691012DA2CA11008BF16C /* AppDiagnosticsDataFlow.swift in Sources */,
 				DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */,
 				DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */,
+				49239B432EEA27AD00469145 /* TempTargetCalculations.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				DDD6D4D32CDE90720029439A /* EstimatedA1cDisplayUnit.swift in Sources */,
 				DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */,

+ 17 - 6
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -534,15 +534,26 @@ final class OpenAPS {
 
         // Check for active Temp Targets and adjust HBT if necessary
         try await context.perform {
-            // Check if a Temp Target is active and if its HBT differs from user preferences
+            // Check if a Temp Target is active
             if let activeTempTarget = try self.fetchActiveTempTargets().first,
                activeTempTarget.enabled,
-               let activeHBT = activeTempTarget.halfBasalTarget?.decimalValue,
-               activeHBT != defaultHalfBasalTarget
+               let targetValue = activeTempTarget.target?.decimalValue
             {
-                // Overwrite the HBT in preferences
-                adjustedPreferences.halfBasalExerciseTarget = activeHBT
-                debug(.openAPS, "Updated halfBasalExerciseTarget to active Temp Target value: \(activeHBT)")
+                // Compute effective HBT - handles both custom HBT and standard TT (where HBT might need adjustment)
+                let effectiveHBT = TempTargetCalculations.computeEffectiveHBT(
+                    storedHBT: activeTempTarget.halfBasalTarget?.decimalValue,
+                    settingHalfBasalTarget: defaultHalfBasalTarget,
+                    target: targetValue,
+                    autosensMax: preferences.autosensMax
+                )
+
+                if let effectiveHBT, effectiveHBT != defaultHalfBasalTarget {
+                    adjustedPreferences.halfBasalExerciseTarget = effectiveHBT
+                    debug(
+                        .openAPS,
+                        "checkStandardTT: Updated halfBasalExerciseTarget to \(effectiveHBT) for target \(targetValue) (stored HBT: \(String(describing: activeTempTarget.halfBasalTarget?.decimalValue)), settings HBT: \(defaultHalfBasalTarget))"
+                    )
+                }
             }
             // Overwrite the lowTTlowersSens if autosensMax does not support it
             if preferences.lowTemptargetLowersSensitivity, preferences.autosensMax <= 1 {

+ 132 - 0
Trio/Sources/Helpers/TempTargetCalculations.swift

@@ -0,0 +1,132 @@
+import Foundation
+
+/// Helper functions for TempTarget sensitivity calculations.
+/// These are used across the app (UI, OpenAPS) to ensure consistent behavior.
+enum TempTargetCalculations {
+    /// The minimum allowed sensitivity ratio for TempTargets (15%)
+    static let minSensitivityRatioTT: Double = 15
+
+    /// The normal target glucose value used as reference (100 mg/dL)
+    static let normalTarget: Decimal = 100
+
+    /// Computes the raw (unclamped) adjusted percentage for a given HBT and target.
+    /// - Parameters:
+    ///   - halfBasalTarget: The half basal target value
+    ///   - target: The target glucose value
+    ///   - autosensMax: The maximum autosens multiplier from settings
+    /// - Returns: The raw percentage (not clamped to minSensitivityRatioTT)
+    static func computeRawAdjustedPercentage(
+        halfBasalTarget: Decimal,
+        target: Decimal,
+        autosensMax: Decimal
+    ) -> Double {
+        let deviationFromNormal = halfBasalTarget - normalTarget
+        let adjustmentFactor = deviationFromNormal + (target - normalTarget)
+        let adjustmentRatio: Decimal = (deviationFromNormal * adjustmentFactor <= 0)
+            ? autosensMax
+            : deviationFromNormal / adjustmentFactor
+        return Double(min(adjustmentRatio, autosensMax) * 100)
+    }
+
+    /// Computes the adjusted percentage (clamped to minSensitivityRatioTT).
+    /// - Parameters:
+    ///   - halfBasalTarget: The half basal target value
+    ///   - target: The target glucose value
+    ///   - autosensMax: The maximum autosens multiplier from settings
+    /// - Returns: The clamped percentage (minimum is minSensitivityRatioTT)
+    static func computeAdjustedPercentage(
+        halfBasalTarget: Decimal,
+        target: Decimal,
+        autosensMax: Decimal
+    ) -> Double {
+        let rawPercentage = computeRawAdjustedPercentage(
+            halfBasalTarget: halfBasalTarget,
+            target: target,
+            autosensMax: autosensMax
+        )
+        return rawPercentage < minSensitivityRatioTT ? minSensitivityRatioTT : rawPercentage
+    }
+
+    /// Computes the half-basal target needed to achieve a given percentage.
+    /// - Parameters:
+    ///   - target: The target glucose value
+    ///   - percentage: The desired sensitivity percentage
+    /// - Returns: The half basal target value that yields the given percentage
+    static func computeHalfBasalTarget(
+        target: Decimal,
+        percentage: Double
+    ) -> Double {
+        var adjustmentPercentage = percentage
+        if adjustmentPercentage < minSensitivityRatioTT {
+            adjustmentPercentage = minSensitivityRatioTT
+        }
+        let adjustmentRatio = Decimal(adjustmentPercentage / 100)
+        var halfBasalTargetValue: Decimal = 160 // default
+        if adjustmentRatio != 1 {
+            halfBasalTargetValue = ((2 * adjustmentRatio * normalTarget) - normalTarget - (adjustmentRatio * target)) /
+                (adjustmentRatio - 1)
+        }
+        return round(Double(halfBasalTargetValue))
+    }
+
+    /// Checks if the settings HBT would result in a percentage at or below the minimum,
+    /// and if so, returns an adjusted HBT that yields exactly the minimum percentage.
+    /// - Parameters:
+    ///   - settingHalfBasalTarget: The HBT from user settings
+    ///   - target: The target glucose value
+    ///   - autosensMax: The maximum autosens multiplier from settings
+    /// - Returns: A tuple containing (percentage, halfBasalTarget) where halfBasalTarget is nil if settings HBT is OK
+    static func computeStandardPercentageAndHBT(
+        settingHalfBasalTarget: Decimal,
+        target: Decimal,
+        autosensMax: Decimal
+    ) -> (percentage: Double, halfBasalTarget: Decimal?) {
+        let rawPercentage = computeRawAdjustedPercentage(
+            halfBasalTarget: settingHalfBasalTarget,
+            target: target,
+            autosensMax: autosensMax
+        )
+        let clampedPercentage = rawPercentage < minSensitivityRatioTT ? minSensitivityRatioTT : rawPercentage
+
+        // If raw percentage is at or below minimum, we need to calculate an adjusted HBT
+        if rawPercentage <= minSensitivityRatioTT {
+            let adjustedHBT = Decimal(computeHalfBasalTarget(
+                target: target,
+                percentage: minSensitivityRatioTT
+            ))
+            return (minSensitivityRatioTT, adjustedHBT)
+        } else {
+            return (clampedPercentage, nil)
+        }
+    }
+
+    /// Determines the effective HBT to use for a TempTarget.
+    /// If the stored HBT is nil (standard TT) and using settings HBT would result in < 15%,
+    /// calculates an adjusted HBT. Otherwise returns the stored HBT or nil.
+    /// - Parameters:
+    ///   - storedHBT: The HBT stored with the TempTarget (nil for standard TT)
+    ///   - settingHalfBasalTarget: The HBT from user settings
+    ///   - target: The target glucose value
+    ///   - autosensMax: The maximum autosens multiplier from settings
+    /// - Returns: The effective HBT to use, or nil if settings HBT should be used as-is
+    static func computeEffectiveHBT(
+        storedHBT: Decimal?,
+        settingHalfBasalTarget: Decimal,
+        target: Decimal,
+        autosensMax: Decimal
+    ) -> Decimal? {
+        // If TempTarget has a stored HBT, use it directly
+        if let storedHBT {
+            return storedHBT
+        }
+
+        // For standard TT (no stored HBT), check if we need to adjust
+        let (_, adjustedHBT) = computeStandardPercentageAndHBT(
+            settingHalfBasalTarget: settingHalfBasalTarget,
+            target: target,
+            autosensMax: autosensMax
+        )
+
+        return adjustedHBT
+    }
+}

+ 60 - 13
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift

@@ -389,15 +389,12 @@ extension Adjustments.StateModel {
         usingTarget initialTarget: Decimal? = nil,
         usingPercentage initialPercentage: Double? = nil
     ) -> Double {
-        let adjustmentPercentage = initialPercentage ?? percentage
-        let adjustmentRatio = Decimal(adjustmentPercentage / 100)
         let tempTargetValue: Decimal = initialTarget ?? tempTargetTarget
-        var halfBasalTargetValue = halfBasalTarget
-        if adjustmentRatio != 1 {
-            halfBasalTargetValue = ((2 * adjustmentRatio * normalTarget) - normalTarget - (adjustmentRatio * tempTargetValue)) /
-                (adjustmentRatio - 1)
-        }
-        return round(Double(halfBasalTargetValue))
+        let adjustmentPercentage = initialPercentage ?? percentage
+        return TempTargetCalculations.computeHalfBasalTarget(
+            target: tempTargetValue,
+            percentage: adjustmentPercentage
+        )
     }
 
     /// Determines if sensitivity adjustment is enabled based on target.
@@ -425,6 +422,19 @@ extension Adjustments.StateModel {
         return maxSens
     }
 
+    /// Computes the raw (unclamped) adjusted percentage for a given HBT and target.
+    /// This is used to check if the percentage would be below minSensitivityRatioTT.
+    func computeRawAdjustedPercentage(
+        usingHBT halfBasalTargetValue: Decimal,
+        usingTarget calcTarget: Decimal
+    ) -> Double {
+        TempTargetCalculations.computeRawAdjustedPercentage(
+            halfBasalTarget: halfBasalTargetValue,
+            target: calcTarget,
+            autosensMax: autosensMax
+        )
+    }
+
     /// Computes the adjusted percentage for the slider.
     func computeAdjustedPercentage(
         usingHBT initialHalfBasalTarget: Decimal? = nil,
@@ -432,13 +442,50 @@ extension Adjustments.StateModel {
     ) -> Double {
         let halfBasalTargetValue = initialHalfBasalTarget ?? halfBasalTarget
         let calcTarget = initialTarget ?? tempTargetTarget
-        let deviationFromNormal = halfBasalTargetValue - normalTarget
+        return TempTargetCalculations.computeAdjustedPercentage(
+            halfBasalTarget: halfBasalTargetValue,
+            target: calcTarget,
+            autosensMax: autosensMax
+        )
+    }
+
+    /// Computes the standard percentage and adjusted HBT for a given target.
+    /// If the raw percentage (using settingHalfBasalTarget) would be at or below minSensitivityRatioTT (15%),
+    /// returns an adjusted HBT that yields the minimum percentage instead.
+    /// - Parameter target: The target glucose value
+    /// - Returns: A tuple containing (percentage, halfBasalTarget) where halfBasalTarget is nil if standard HBT can be used
+    func computeStandardPercentageAndHBT(usingTarget target: Decimal) -> (percentage: Double, halfBasalTarget: Decimal?) {
+        let result = TempTargetCalculations.computeStandardPercentageAndHBT(
+            settingHalfBasalTarget: settingHalfBasalTarget,
+            target: target,
+            autosensMax: autosensMax
+        )
 
-        let adjustmentFactor = deviationFromNormal + (calcTarget - normalTarget)
-        let adjustmentRatio: Decimal = (deviationFromNormal * adjustmentFactor <= 0) ? autosensMax : deviationFromNormal /
-            adjustmentFactor
+        // Use raw percentage for debug logging
+        let rawPercentage = TempTargetCalculations.computeRawAdjustedPercentage(
+            halfBasalTarget: settingHalfBasalTarget,
+            target: target,
+            autosensMax: autosensMax
+        )
+
+        debug(
+            .default,
+            "checkStandardTT: target=\(target), settingHBT=\(settingHalfBasalTarget), rawPercentage=\(rawPercentage), percentage=\(result.percentage), minSensitivityRatioTT=\(minSensitivityRatioTT)"
+        )
+
+        if let adjustedHBT = result.halfBasalTarget {
+            debug(
+                .default,
+                "checkStandardTT: rawPercentage <= minSensitivityRatioTT, returning adjustedHBT=\(adjustedHBT), percentage=\(result.percentage)"
+            )
+        } else {
+            debug(
+                .default,
+                "checkStandardTT: rawPercentage > minSensitivityRatioTT, returning nil HBT, percentage=\(result.percentage)"
+            )
+        }
 
-        return Double(min(adjustmentRatio, autosensMax) * 100).rounded()
+        return result
     }
 }