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

Merge pull request #618 from nightscout/check-more-properties-determineBasal-swift

Ensure consistency for determineBasal `result` strings
Deniz Cengiz 4 месяцев назад
Родитель
Сommit
5aa2d05deb

+ 37 - 9
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DetermineBasal+Helpers.swift

@@ -184,32 +184,34 @@ extension DeterminationGenerator {
         return (sensitivity / sensitivityRatio).jsRounded(scale: 1)
     }
 
+    /// Checks if current temp basal matches last temp from IOB data.
+    /// Returns nil if check passes, or the failure reason string if it fails.
     static func checkCurrentTempBasalRateSafety(
         currentTemp: TempBasal,
         lastTempTarget: IobResult.LastTemp?,
         currentTime: Date
-    ) -> Bool {
+    ) -> String? {
         guard let lastTemp = lastTempTarget, let lastTempDate = lastTemp.timestamp,
-              let lastTempDuration = lastTemp.duration else { return true }
+              let lastTempDuration = lastTemp.duration else { return nil }
         // TODO: throw error for malformed IobResult? Can this be malformed?
 
         let lastTempAge = Int((currentTime.timeIntervalSince(lastTempDate) / 60).rounded()) // in minutes
 //        let tempModulus = Int(lastTempAge + currentTemp.duration) % 30 // only used in JS as output; will leave it here for now
 
-        if currentTemp.rate != lastTemp.rate, lastTempAge > 10, currentTemp.duration > 0 {
-            // Rates dont match and temp is old: cancel temp
-            return false
+        if let lastTempRate = lastTemp.rate, currentTemp.rate != lastTempRate, lastTempAge > 10, currentTemp.duration > 0 {
+            // Rates don't match and temp is old: cancel temp
+            return "Warning: currenttemp rate \(currentTemp.rate) != lastTemp rate \(lastTempRate) from pumphistory; canceling temp"
         }
         if currentTemp.duration > 0 {
-            let lastTempEnded = lastTempAge - Int(lastTempDuration) // TODO: check if this comes in minutes
+            let lastTempEnded = Decimal(lastTempAge) - lastTempDuration
 
             if lastTempEnded > 5, lastTempAge > 10 {
                 // Last temp ended long ago but temp is running: cancel temp
-                return false
+                return "Warning: currenttemp running but lastTemp from pumphistory ended \(lastTempEnded.jsRounded(scale: 2))m ago; canceling temp"
             }
         }
 
-        return true
+        return nil
     }
 
     /// Adjust glucose targets (min, max, target) based on autosens and/or noise.
@@ -293,7 +295,7 @@ extension Profile {
     /// Calculates the profile ISF at this point in time and applies any overrides to it
     /// This is `sensitivity` in JS
     func profileSensitivity(at: Date, trioCustomOrefVaribales: TrioCustomOrefVariables) -> Decimal {
-        let sensitivity = sensitivityFor(time: at)
+        let sensitivity = sens ?? sensitivityFor(time: at)
         return trioCustomOrefVaribales.override(sensitivity: sensitivity)
     }
 }
@@ -317,3 +319,29 @@ extension TrioCustomOrefVariables {
         }
     }
 }
+
+extension Date {
+    /// Formats date like JavaScript's Date.toString()
+    /// Example: "Sat Nov 22 2025 14:22:58 GMT-0700 (Mountain Standard Time)"
+    func jsDateString() -> String {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "EEE MMM dd yyyy HH:mm:ss"
+        formatter.locale = Locale(identifier: "en_US_POSIX")
+
+        let dateString = formatter.string(from: self)
+
+        // Get GMT offset string like "GMT-0700"
+        let seconds = TimeZone.current.secondsFromGMT(for: self)
+        let hours = abs(seconds) / 3600
+        let minutes = (abs(seconds) % 3600) / 60
+        let sign = seconds >= 0 ? "+" : "-"
+        let gmtOffset = String(format: "GMT%@%02d%02d", sign, hours, minutes)
+
+        // Get timezone name like "Mountain Standard Time" or "Mountain Daylight Time"
+        let style: TimeZone.NameStyle = TimeZone.current.isDaylightSavingTime(for: self) ? .daylightSaving : .standard
+        let timezoneName = TimeZone.current.localizedName(for: style, locale: Locale(identifier: "en_US")) ?? TimeZone.current
+            .identifier
+
+        return "\(dateString) \(gmtOffset) (\(timezoneName))"
+    }
+}

+ 66 - 19
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DetermineBasalGenerator.swift

@@ -84,13 +84,11 @@ enum DeterminationGenerator {
         guard let lastTempTarget = iobData.first?.lastTemp else {
             throw DeterminationError.missingIob
         }
-        if !checkCurrentTempBasalRateSafety(
+        if let reason = checkCurrentTempBasalRateSafety(
             currentTemp: currentTemp,
             lastTempTarget: lastTempTarget,
             currentTime: currentTime
         ) {
-            let reason =
-                "Safety check: currentTemp does not match lastTemp in IOB or lastTemp ended long ago; canceling temp basal."
             return Determination(
                 id: UUID(),
                 reason: reason,
@@ -231,6 +229,7 @@ enum DeterminationGenerator {
                             currentIob *
                                 min(
                                     profile.profileSensitivity(at: currentTime, trioCustomOrefVaribales: trioCustomOrefVariables),
+
                                     adjustedSensitivity
                                 )
                         )
@@ -273,6 +272,51 @@ enum DeterminationGenerator {
             glucoseImpact: currentGlucoseImpact
         )
 
+        // Build isfReason: "Autosens ratio: X, ISF: Y→Z"
+        let originalSensitivity = profile.profileSensitivity(at: currentTime, trioCustomOrefVaribales: trioCustomOrefVariables)
+        let isfReason =
+            "Autosens ratio: \(sensitivityRatio.jsRounded(scale: 2)), ISF: \(originalSensitivity.jsRounded())→\(adjustedSensitivity.jsRounded())"
+
+        // Build targetLog: "X" or "X→Y" or "X→Y→Z" if target was adjusted
+        let profileTarget = profile.profileTarget(trioCustomOrefVariables: trioCustomOrefVariables) ?? 100
+        let overrideTarget = trioCustomOrefVariables.overrideTarget
+        let targetLog: String
+        if adjustedGlucoseTargets.targetGlucose != profileTarget {
+            // Include overrideTarget in the middle if it's set and different from final target
+            if overrideTarget != 0, overrideTarget != 6, overrideTarget != adjustedGlucoseTargets.targetGlucose {
+                targetLog =
+                    "\(profileTarget.jsRounded())→\(overrideTarget.jsRounded())→\(adjustedGlucoseTargets.targetGlucose.jsRounded())"
+            } else {
+                targetLog = "\(profileTarget.jsRounded())→\(adjustedGlucoseTargets.targetGlucose.jsRounded())"
+            }
+        } else {
+            targetLog = "\(adjustedGlucoseTargets.targetGlucose.jsRounded())"
+        }
+
+        // Build tddReason: ", Dynamic ISF: On, Sigmoid function, AF: X, Basal ratio: Y, SMB Ratio: Z"
+        var tddReason = ""
+        if let dynamicIsfResult = dynamicIsfResult {
+            tddReason = ", Dynamic ISF: On"
+            if preferences.sigmoid {
+                tddReason += ", Sigmoid function"
+            } else {
+                tddReason += ", Logarithmic formula"
+            }
+            if let limitValue = dynamicIsfResult.limitValue {
+                tddReason +=
+                    ", Autosens/Dynamic Limit: \(limitValue) (\(dynamicIsfResult.uncappedRatio.jsRounded(scale: 2)))"
+            }
+            let af = preferences.sigmoid ? preferences.adjustmentFactorSigmoid : preferences.adjustmentFactor
+            tddReason += ", AF: \(af)"
+            if profile.tddAdjBasal {
+                tddReason += ", Basal ratio: \(dynamicIsfResult.tddRatio)"
+            }
+        }
+        // SMB Ratio is added if not default (0.5)
+        if profile.smbDeliveryRatio != 0.5 {
+            tddReason += ", SMB Ratio: \(min(profile.smbDeliveryRatio, 1))"
+        }
+
         let dosingInputs = DosingEngine.prepareDosingInputs(
             profile: profile,
             mealData: mealData,
@@ -284,9 +328,9 @@ enum DeterminationGenerator {
             currentBasal: profile.currentBasal ?? profile.basalFor(time: currentTime),
             overrideFactor: trioCustomOrefVariables.overrideFactor(),
             adjustedSensitivity: adjustedSensitivity,
-            isfReason: "", // Placeholder
-            tddReason: "", // Placeholder
-            targetLog: "" // Placeholder
+            isfReason: isfReason,
+            tddReason: tddReason,
+            targetLog: targetLog
         )
 
         let smbDecision = try DosingEngine.makeSMBDosingDecision(
@@ -306,6 +350,10 @@ enum DeterminationGenerator {
         if let smbReason = smbDecision.reason {
             reason += smbReason
         }
+        // Add carbs message after smbReason to match JS order
+        if let carbsReq = dosingInputs.carbsRequired {
+            reason += "\(carbsReq.carbs) add'l carbs req w/in \(carbsReq.minutes)m; "
+        }
 
         var determination = Determination(
             id: UUID(),
@@ -332,7 +380,7 @@ enum DeterminationGenerator {
             isf: nil,
             timestamp: currentTime,
             tdd: nil,
-            current_target: nil,
+            current_target: adjustedGlucoseTargets.targetGlucose,
             minDelta: nil,
             expectedDelta: expectedDelta,
             minGuardBG: smbDecision.minGuardGlucose ?? forecastResult.minGuardGlucose,
@@ -563,13 +611,12 @@ enum DeterminationGenerator {
         if glucose <= 10 || glucose == 38 || noise >= 3 {
             reason = "CGM is calibrating, in ??? state, or noise is high"
         }
-        // minAgo (BG age) > 12 or < -5 = old/future BG
+        // minAgo (BG age) > 12 or < -5 = old/future BG - can overwrite calibration reason (matches JS)
         if minAgo > 12 || minAgo < -5 {
             reason =
-                "If current system time \(currentTime) is correct, then BG data is too old. The last BG data was read \(minAgo) min ago at \(bgTime)"
-        }
-        // CGM data unchanged (flat)
-        if shortAvgDelta == 0 && longAvgDelta == 0 {
+                "If current system time \(currentTime.jsDateString()) is correct, then BG data is too old. The last BG data was read \(minAgo.jsRounded(scale: 1))m ago at \(bgTime.jsDateString())"
+        } else if shortAvgDelta == 0 && longAvgDelta == 0 {
+            // CGM data unchanged (flat) - only checked if BG is not too old
             if glucoseStatus.lastCalIndex != nil, glucoseStatus.lastCalIndex! < 3 {
                 reason = "CGM was just calibrated"
             } else {
@@ -591,7 +638,7 @@ enum DeterminationGenerator {
         if currentTemp.rate >= basal { // high temp is running
             // Replace high temp with neutral temp at scheduled basal rate for 30min
             let reasonWithAction = reason +
-                ". Replacing high temp basal of \(currentTemp.rate)U/hr with neutral temp of \(basal)U/hr"
+                ". Replacing high temp basal of \(currentTemp.rate) with neutral temp of \(basal)"
             return Determination(
                 id: UUID(),
                 reason: reasonWithAction,
@@ -612,7 +659,7 @@ enum DeterminationGenerator {
                 isf: profile.sens,
                 timestamp: currentTime,
                 tdd: nil,
-                current_target: profile.targetBg,
+                current_target: nil,
                 minDelta: minDelta,
                 expectedDelta: nil,
                 minGuardBG: nil,
@@ -623,7 +670,7 @@ enum DeterminationGenerator {
             )
         } else if currentTemp.rate == 0, currentTemp.duration > 30 {
             // Shorten long zero temp to 30m
-            let reasonWithAction = reason + ". Shortening \(currentTemp.duration)m long zero temp to 30m."
+            let reasonWithAction = reason + ". Shortening \(currentTemp.duration)m long zero temp to 30m. "
             return Determination(
                 id: UUID(),
                 reason: reasonWithAction,
@@ -644,7 +691,7 @@ enum DeterminationGenerator {
                 isf: profile.sens,
                 timestamp: currentTime,
                 tdd: nil,
-                current_target: profile.targetBg,
+                current_target: nil,
                 minDelta: minDelta,
                 expectedDelta: nil,
                 minGuardBG: nil,
@@ -655,7 +702,7 @@ enum DeterminationGenerator {
             )
         } else {
             // Do nothing (temp already safe)
-            let reasonWithAction = reason + ". Temp \(currentTemp.rate) <= current basal \(basal)U/hr; doing nothing."
+            let reasonWithAction = reason + ". Temp \(currentTemp.rate) <= current basal \(basal)U/hr; doing nothing. "
             return Determination(
                 id: UUID(),
                 reason: reasonWithAction,
@@ -668,7 +715,7 @@ enum DeterminationGenerator {
                 iob: nil,
                 cob: nil,
                 predictions: nil,
-                deliverAt: currentTime,
+                deliverAt: nil,
                 carbsReq: nil,
                 temp: currentTemp.temp,
                 bg: nil,
@@ -676,7 +723,7 @@ enum DeterminationGenerator {
                 isf: profile.sens,
                 timestamp: currentTime,
                 tdd: nil,
-                current_target: profile.targetBg,
+                current_target: nil,
                 minDelta: minDelta,
                 expectedDelta: nil,
                 minGuardBG: nil,

+ 24 - 13
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DosingEngine.swift

@@ -161,12 +161,12 @@ enum DosingEngine {
         tddReason: String,
         targetLog: String // This is a pre-formatted string from the JS
     ) -> DosingInputs {
-        let lastIOBpredBG = forecast.iob.last ?? 0
-        let lastCOBpredBG = forecast.cob?.last
-        let lastUAMpredBG = forecast.uam?.last
+        let lastIOBpredBG = (forecast.iob.last ?? 0).jsRounded()
+        let lastCOBpredBG = forecast.cob?.last?.jsRounded()
+        let lastUAMpredBG = forecast.uam?.last?.jsRounded()
 
         var reason =
-            "\(isfReason), COB: \(mealData.mealCOB), Dev: \(deviation), BGI: \(glucoseImpact), CR: \(forecast.adjustedCarbRatio), Target: \(targetLog), minPredBG \(forecast.minForecastedGlucose), minGuardBG \(forecast.minGuardGlucose), IOBpredBG \(lastIOBpredBG)"
+            "\(isfReason), COB: \(mealData.mealCOB), Dev: \(deviation.jsRounded()), BGI: \(glucoseImpact.jsRounded()), CR: \(forecast.adjustedCarbRatio.jsRounded(scale: 1)), Target: \(targetLog), minPredBG \(forecast.minForecastedGlucose.jsRounded()), minGuardBG \(forecast.minGuardGlucose.jsRounded()), IOBpredBG \(lastIOBpredBG)"
 
         if let lastCOB = lastCOBpredBG {
             reason += ", COBpredBG \(lastCOB)"
@@ -194,7 +194,7 @@ enum DosingEngine {
 
         var carbsRequired: (carbs: Decimal, minutes: Decimal)?
         if carbsRequiredResult.carbs >= profile.carbsReqThreshold, carbsRequiredResult.minutes <= 45 {
-            reason += "\(carbsRequiredResult.carbs) add'l carbs req w/in \(carbsRequiredResult.minutes)m; "
+            // Note: carbs message is added in DetermineBasalGenerator after smbReason to match JS order
             carbsRequired = carbsRequiredResult
         }
 
@@ -297,7 +297,7 @@ enum DosingEngine {
         } else if currentGlucose < threshold || minGuardGlucose < threshold {
             let minGuardGlucoseString = String(describing: convertGlucose(profile: profile, glucose: minGuardGlucose))
             let thresholdString = String(describing: convertGlucose(profile: profile, glucose: threshold))
-            newDetermination.reason += "minGuardBG \(minGuardGlucoseString) < \(thresholdString)"
+            newDetermination.reason += "minGuardBG \(minGuardGlucoseString)<\(thresholdString)"
 
             let glucoseUndershoot = targetGlucose - minGuardGlucose
             if minGuardGlucose < threshold {
@@ -421,9 +421,10 @@ enum DosingEngine {
                     .reason +=
                     ", but Delta \(convertGlucose(profile: profile, glucose: glucoseStatus.delta)) > expectedDelta \(convertGlucose(profile: profile, glucose: expectedDelta))"
             } else {
+                let minDeltaFormatted = String(format: "%.2f", Double(truncating: minDelta.jsRounded(scale: 2) as NSNumber))
                 newDetermination
                     .reason +=
-                    ", but Min. Delta \(minDelta.jsRounded(scale: 2)) > Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    ", but Min. Delta \(minDeltaFormatted) > Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
             }
 
             let roundedBasal = TempBasalFunctions.roundBasal(profile: profile, basalRate: basal)
@@ -463,8 +464,9 @@ enum DosingEngine {
         let minInsulinRequired = min(insulinRequired, naiveInsulinRequired)
 
         if insulinScheduled < minInsulinRequired - basal * 0.3 {
+            let rateFormatted = String(format: "%.2f", Double(truncating: currentTemp.rate.jsRounded(scale: 2) as NSNumber))
             newDetermination
-                .reason += ", \(currentTemp.duration)m@\(currentTemp.rate.jsRounded(scale: 2)) is a lot less than needed. "
+                .reason += ", \(currentTemp.duration)m@\(rateFormatted) is a lot less than needed. "
             let finalDetermination = try TempBasalFunctions.setTempBasal(
                 rate: rate,
                 duration: 30,
@@ -549,9 +551,10 @@ enum DosingEngine {
                     .reason +=
                     "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) > \(convertGlucose(profile: profile, glucose: minGlucose)) but Delta \(convertGlucose(profile: profile, glucose: glucoseStatus.delta)) < Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
             } else {
+                let minDeltaFormatted = String(format: "%.2f", Double(truncating: minDelta.jsRounded(scale: 2) as NSNumber))
                 newDetermination
                     .reason +=
-                    "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) > \(convertGlucose(profile: profile, glucose: minGlucose)) but Min. Delta \(minDelta.jsRounded(scale: 2)) < Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) > \(convertGlucose(profile: profile, glucose: minGlucose)) but Min. Delta \(minDeltaFormatted) < Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
             }
 
             let roundedBasal = TempBasalFunctions.roundBasal(profile: profile, basalRate: basal)
@@ -825,8 +828,15 @@ enum DosingEngine {
         }
 
         if let lastBolusAge {
-            let nextBolusMinutes = smbInterval - lastBolusAge
-            let nextBolusSeconds = (Int(smbInterval - lastBolusAge) * 60) % 60
+            // BUG: JS rounds minutes independently from seconds, causing double-counting when
+            // minutes rounds up. E.g., 0.6 min = 36 sec, but JS outputs "1m 36s" (96 sec).
+            // Correct logic would be:
+            //   let totalSeconds = Int(((smbInterval - lastBolusAge) * 60).jsRounded())
+            //   let nextBolusMinutes = totalSeconds / 60
+            //   let nextBolusSeconds = totalSeconds % 60
+            // Keeping JS behavior for now to match outputs.
+            let nextBolusMinutes = (smbInterval - lastBolusAge).jsRounded()
+            let nextBolusSeconds = Int(((smbInterval - lastBolusAge) * 60).jsRounded()) % 60
 
             if lastBolusAge > smbInterval {
                 if microBolus > 0 {
@@ -864,14 +874,15 @@ enum DosingEngine {
         let maxSafeBasal = try TempBasalFunctions.getMaxSafeBasalRate(profile: profile)
 
         if rate > maxSafeBasal {
-            newDetermination.reason += "adj. req. rate: \(rate) to maxSafeBasal: \(maxSafeBasal), "
+            newDetermination.reason += "adj. req. rate: \(rate) to maxSafeBasal: \(maxSafeBasal.jsRounded(scale: 2)), "
             rate = TempBasalFunctions.roundBasal(profile: profile, basalRate: maxSafeBasal)
         }
 
         let insulinScheduled = Decimal(currentTemp.duration) * (currentTemp.rate - basal) / 60
         if insulinScheduled >= insulinRequired * 2 {
+            let rateFormatted = String(format: "%.2f", Double(truncating: currentTemp.rate.jsRounded(scale: 2) as NSNumber))
             newDetermination.reason +=
-                "\(currentTemp.duration)m@\(currentTemp.rate.jsRounded(scale: 2)) > 2 * insulinReq. Setting temp basal of \(rate)U/hr. "
+                "\(currentTemp.duration)m@\(rateFormatted) > 2 * insulinReq. Setting temp basal of \(rate)U/hr. "
             let finalDetermination = try TempBasalFunctions.setTempBasal(
                 rate: rate,
                 duration: 30,

+ 17 - 2
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DynamicISF.swift

@@ -8,6 +8,10 @@ struct DynamicISFResult {
     let tddRatio: Decimal
     /// The calculated insulin factor (120 - peak time), used in the logarithmic formula.
     let insulinFactor: Decimal
+    /// The ratio before clamping was applied.
+    let uncappedRatio: Decimal
+    /// The limit value if the ratio was clamped, nil otherwise.
+    let limitValue: Decimal?
 }
 
 enum DynamicISF {
@@ -89,10 +93,21 @@ enum DynamicISF {
             newRatio = sensitivity * preferences.adjustmentFactor * tdd * (Decimal.log((bg / insulinFactor) + 1) / 1800)
         }
 
+        let clampedRatio = newRatio.clamp(lowerBound: minLimit, upperBound: maxLimit)
+        let limitValue: Decimal? = if newRatio > maxLimit {
+            maxLimit
+        } else if newRatio < minLimit {
+            minLimit
+        } else {
+            nil
+        }
+
         return DynamicISFResult(
-            ratio: newRatio.clamp(lowerBound: minLimit, upperBound: maxLimit),
+            ratio: clampedRatio,
             tddRatio: clampedTddRatio,
-            insulinFactor: insulinFactor
+            insulinFactor: insulinFactor,
+            uncappedRatio: newRatio,
+            limitValue: limitValue
         )
     }
 }

+ 3 - 3
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/TempBasalFunctions.swift

@@ -89,18 +89,18 @@ enum TempBasalFunctions {
                 if currentTemp.duration > 0 {
                     determination
                         .reason = determination.reason +
-                        "Suggested rate is same as profile rate, a temp basal is active, canceling current temp"
+                        ". Suggested rate is same as profile rate, a temp basal is active, canceling current temp"
                     determination.duration = 0
                     determination.rate = 0
                     return determination
                 } else {
                     determination
                         .reason = determination.reason +
-                        "Suggested rate is same as profile rate, no temp basal is active, doing nothing"
+                        ". Suggested rate is same as profile rate, no temp basal is active, doing nothing"
                     return determination
                 }
             } else {
-                determination.reason = determination.reason + "Setting neutral temp basal of \(profile.currentBasal ?? 0)U/hr"
+                determination.reason = determination.reason + ". Setting neutral temp basal of \(profile.currentBasal ?? 0)U/hr"
                 determination.duration = duration
                 determination.rate = suggestedRate
                 return determination

+ 2 - 10
Trio/Sources/APS/OpenAPSSwift/Logging/OrefFunction.swift

@@ -43,24 +43,16 @@ enum OrefFunction: String, Codable {
         case .autosens:
             return Set(["deviationsUnsorted", "debugInfo"])
         case .determineBasal:
-            // FIXME: Adjust as we go
+            // We ignore some properties that aren't used downstream
             return Set([
-                // Final dosing calculation
-                // "units",
-                // "insulinReq",
-                // "rate",
-                // "duration",
-                // Not calculating yet
+                // Not used, ignore
                 "id",
-                "deliverAt",
                 "temp",
                 "reservoir",
                 "ISF",
-                "current_target",
                 "TDD",
                 "minDelta",
                 "received",
-                "reason",
                 // intentionally removed from Swift, but in JS
                 "insulinForManualBolus",
                 "manualBolusErrorString",

+ 2 - 3
TrioTests/OpenAPSSwiftTests/DetermineBasalEarlyExitTests.swift

@@ -393,7 +393,7 @@ import Testing
         #expect(
             result?
                 .reason ==
-                "Safety check: currentTemp does not match lastTemp in IOB or lastTemp ended long ago; canceling temp basal."
+                "Warning: currenttemp rate 1.5 != lastTemp rate 1 from pumphistory; canceling temp"
         )
     }
 
@@ -445,8 +445,7 @@ import Testing
         // Note: In swift we use a different reason then JS
         #expect(
             result?
-                .reason ==
-                "Safety check: currentTemp does not match lastTemp in IOB or lastTemp ended long ago; canceling temp basal."
+                .reason == "Warning: currenttemp running but lastTemp from pumphistory ended 10m ago; canceling temp"
         )
     }
 

+ 1 - 1
TrioTests/OpenAPSSwiftTests/SetTempBasalTests.swift

@@ -298,6 +298,6 @@ import Testing
 
         #expect(requestedTemp.rate == 0.8)
         #expect(requestedTemp.duration == 30)
-        #expect(requestedTemp.reason == "Setting neutral temp basal of 0.8U/hr")
+        #expect(requestedTemp.reason == ". Setting neutral temp basal of 0.8U/hr")
     }
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
TrioTests/OpenAPSSwiftTests/javascript/bundle/determine-basal.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
TrioTests/OpenAPSSwiftTests/javascript/bundle/meal.js