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

Fix ISF rounding drift on Nightscout import (#1179) + tighten mmol/L conversion factor
- Drop correctUnitParsingOffsets() from the NS profile import
- Adapt conversion factor in GlucoseUnits.exchangeRate from 0.0555 to 0.055495 (≈ 1 / 18.0182)
- Replace the three duplicated 0.0555 literals

Deniz Cengiz 6 дней назад
Родитель
Сommit
294b9293f2

+ 1 - 1
Trio/Sources/APS/APSManager.swift

@@ -894,7 +894,7 @@ final class BaseAPSManager: APSManager, Injectable {
             sd = sqrt(sumOfSquares / Double(countReadings))
             sd = sqrt(sumOfSquares / Double(countReadings))
             cv = sd / Double(glucoseAverage) * 100
             cv = sd / Double(glucoseAverage) * 100
         }
         }
-        let conversionFactor = 0.0555
+        let conversionFactor = Double(truncating: GlucoseUnits.exchangeRate as NSDecimalNumber)
         let units = settingsManager.settings.units
         let units = settingsManager.settings.units
 
 
         var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
         var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)

+ 6 - 1
Trio/Sources/Models/BloodGlucose.swift

@@ -180,7 +180,12 @@ enum GlucoseUnits: String, JSON, Equatable, CaseIterable, Identifiable {
     case mgdL = "mg/dL"
     case mgdL = "mg/dL"
     case mmolL = "mmol/L"
     case mmolL = "mmol/L"
 
 
-    static let exchangeRate: Decimal = 0.0555
+    /// mg/dL ↔ mmol/L conversion factor. Glucose has a molar mass of
+    /// 180.156 g/mol, so 1 mmol/L = 18.0182 mg/dL; the reciprocal
+    /// 1 / 18.0182 ≈ 0.055495 is the correct mg/dL → mmol/L multiplier.
+    /// (Earlier code used 0.0555 — a 3-sig-fig approximation that drifted
+    /// noticeably on round-trip conversions, e.g. ISF imports from NS.)
+    static let exchangeRate: Decimal = 0.055495
 
 
     var id: String { rawValue }
     var id: String { rawValue }
 }
 }

+ 3 - 8
Trio/Sources/Modules/Onboarding/OnboardingStateModel+Nightscout.swift

@@ -119,8 +119,7 @@ extension Onboarding.StateModel {
             // Sensitivities
             // Sensitivities
             let sensitivities = fetchedProfile.sens.map { sensitivity in
             let sensitivities = fetchedProfile.sens.map { sensitivity in
                 InsulinSensitivityEntry(
                 InsulinSensitivityEntry(
-                    sensitivity: shouldConvertToMgdL ? correctUnitParsingOffsets(sensitivity.value.asMgdL) : sensitivity
-                        .value,
+                    sensitivity: shouldConvertToMgdL ? sensitivity.value.asMgdL : sensitivity.value,
                     offset: offset(sensitivity.time) / 60,
                     offset: offset(sensitivity.time) / 60,
                     start: sensitivity.time
                     start: sensitivity.time
                 )
                 )
@@ -143,8 +142,8 @@ extension Onboarding.StateModel {
             // Targets
             // Targets
             let targets = fetchedProfile.target_low.map { target in
             let targets = fetchedProfile.target_low.map { target in
                 BGTargetEntry(
                 BGTargetEntry(
-                    low: shouldConvertToMgdL ? correctUnitParsingOffsets(target.value.asMgdL) : target.value,
-                    high: shouldConvertToMgdL ? correctUnitParsingOffsets(target.value.asMgdL) : target.value,
+                    low: shouldConvertToMgdL ? target.value.asMgdL : target.value,
+                    high: shouldConvertToMgdL ? target.value.asMgdL : target.value,
                     start: target.time,
                     start: target.time,
                     offset: offset(target.time) / 60
                     offset: offset(target.time) / 60
                 )
                 )
@@ -234,10 +233,6 @@ extension Onboarding.StateModel {
         }
         }
     }
     }
 
 
-    fileprivate func correctUnitParsingOffsets(_ parsedValue: Decimal) -> Decimal {
-        Int(parsedValue) % 2 == 0 ? parsedValue : parsedValue + 1
-    }
-
     fileprivate func offset(_ string: String) -> Int {
     fileprivate func offset(_ string: String) -> Int {
         let hours = Int(string.prefix(2)) ?? 0
         let hours = Int(string.prefix(2)) ?? 0
         let minutes = Int(string.suffix(2)) ?? 0
         let minutes = Int(string.suffix(2)) ?? 0

+ 14 - 7
Trio/Sources/Modules/Stat/View/ChartsView.swift

@@ -15,7 +15,14 @@ struct ChartsView: View {
     @State var headline: Color = .secondary
     @State var headline: Color = .secondary
 
 
     private var conversionFactor: Decimal {
     private var conversionFactor: Decimal {
-        units == .mmolL ? 0.0555 : 1
+        units == .mmolL ? GlucoseUnits.exchangeRate : 1
+    }
+
+    /// Converts a mmol/L clinical threshold to the equivalent Int16 mg/dL bucket
+    /// used by `GlucoseStored.glucose`. Routes through the canonical `Double.asMgdL`
+    /// helper so the conversion stays consistent with the rest of the app.
+    private func thresholdMgdL(_ mmol: Double) -> Int16 {
+        Int16(truncating: mmol.asMgdL as NSDecimalNumber)
     }
     }
 
 
     var body: some View {
     var body: some View {
@@ -208,9 +215,9 @@ struct ChartsView: View {
         VStack(alignment: .leading, spacing: 15) {
         VStack(alignment: .leading, spacing: 15) {
             let mapGlucose = glucose.compactMap({ each in each.glucose })
             let mapGlucose = glucose.compactMap({ each in each.glucose })
             if !mapGlucose.isEmpty {
             if !mapGlucose.isEmpty {
-                let mapGlucoseAcuteLow = mapGlucose.filter({ $0 < Int16(3.3 / 0.0555) })
-                let mapGlucoseHigh = mapGlucose.filter({ $0 > Int16(11 / 0.0555) })
-                let mapGlucoseNormal = mapGlucose.filter({ $0 > Int16(3.8 / 0.0555) && $0 < Int16(7.9 / 0.0555) })
+                let mapGlucoseAcuteLow = mapGlucose.filter({ $0 < thresholdMgdL(3.3) })
+                let mapGlucoseHigh = mapGlucose.filter({ $0 > thresholdMgdL(11) })
+                let mapGlucoseNormal = mapGlucose.filter({ $0 > thresholdMgdL(3.8) && $0 < thresholdMgdL(7.9) })
                 HStack {
                 HStack {
                     let value = 100.0 * Double(mapGlucoseHigh.count) / Double(mapGlucose.count)
                     let value = 100.0 * Double(mapGlucoseHigh.count) / Double(mapGlucose.count)
                     Text(units == .mmolL ? ">  11  " : ">  198 ")
                     Text(units == .mmolL ? ">  11  " : ">  198 ")
@@ -246,9 +253,9 @@ struct ChartsView: View {
         HStack {
         HStack {
             let mapGlucose = glucose.compactMap({ each in each.glucose })
             let mapGlucose = glucose.compactMap({ each in each.glucose })
             if !mapGlucose.isEmpty {
             if !mapGlucose.isEmpty {
-                let mapGlucoseLow = mapGlucose.filter({ $0 < Int16(3.3 / 0.0555) })
-                let mapGlucoseNormal = mapGlucose.filter({ $0 > Int16(3.8 / 0.0555) && $0 < Int16(7.9 / 0.0555) })
-                let mapGlucoseAcuteHigh = mapGlucose.filter({ $0 > Int16(11 / 0.0555) })
+                let mapGlucoseLow = mapGlucose.filter({ $0 < thresholdMgdL(3.3) })
+                let mapGlucoseNormal = mapGlucose.filter({ $0 > thresholdMgdL(3.8) && $0 < thresholdMgdL(7.9) })
+                let mapGlucoseAcuteHigh = mapGlucose.filter({ $0 > thresholdMgdL(11) })
                 Spacer()
                 Spacer()
                 HStack {
                 HStack {
                     let value = 100.0 * Double(mapGlucoseLow.count) / Double(mapGlucose.count)
                     let value = 100.0 * Double(mapGlucoseLow.count) / Double(mapGlucose.count)