Преглед изворни кода

Merge branch 'dev' of github.com:nightscout/Trio-dev into onboarding-fixes

Deniz Cengiz пре 1 година
родитељ
комит
f79edd5562

+ 19 - 0
Model/Helper/Determination+helper.swift

@@ -11,6 +11,25 @@ extension OrefDetermination {
     }
 }
 
+extension Determination {
+    var minPredBGFromReason: Decimal? {
+        // Split reason into parts by semicolon and get first part
+        let reasonParts = reason.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []
+
+        // Find the part that contains "minPredBG"
+        if let minPredBGPart = reasonParts.first(where: { $0.contains("minPredBG") }) {
+            // Extract the number after "minPredBG"
+            let components = minPredBGPart.components(separatedBy: "minPredBG ")
+            if let valueComponent = components.dropFirst().first {
+                // Get everything after "minPredBG " and convert to Decimal
+                let valueString = valueComponent.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.-").inverted)
+                return Decimal(string: valueString)
+            }
+        }
+        return nil
+    }
+}
+
 extension OrefDetermination {
     var reasonParts: [String] {
         reason?.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []

+ 49 - 43
PRIVACY_POLICY.md

@@ -10,43 +10,50 @@ data.
 
 ## Information We Collect
 
-### Crash Reporting (Opt-In by default, with ability to Opt-Out)
+### What We Do NOT Collect
+
+For complete transparency, we want to clarify that Trio does not collect:
+- Blood glucose (BG) readings
+- Treatment data
+- Total daily doses (TDD)
+- Any health-related statistics or personal medical information
+- Personal identifiable information such as name, address, or email
 
-Our App uses Google Firebase Crashlytics to collect crash reports. You
-will be asked to opt in to crash reporting when you first use Trio,
-and you can change this setting at any time.
+### Crash Reporting (Opt-In by default, with ability to Opt-Out)
 
-For users who use Trio without going through the onboarding process,
-we opt them in to crash reporting by default, but you can opt out at
-any time.
+Trio uses Google Firebase Crashlytics to collect crash reports. During
+the initial app setup (onboarding process), you will be asked to opt
+in to crash reporting. The onboarding process is the series of screens
+you see when first launching Trio that helps you set up the app.
 
-The following information may be sent to Crashlytics when the App
-crashes:
+The following information may be sent to Crashlytics when Trio crashes:
 
-- Time and date of the crash
-- Device state at the time of the crash
-- Stack trace information
-- Device model and OS version
-- A generated unique identifier (not personally identifiable)
+- Time and date of the crash (example: "Trio crashed on April 6, 2025 at 2:15 PM")
+- Device state at the time of the crash (example: "Trio was in the foreground" or "Battery level was 42%")
+- Stack trace information (technical information showing which line of code failed)
+- Device model and OS version (example: "iPhone 14 Pro running iOS 17.4.1")
+- A generated unique identifier (a random code like "A7B2C9D3" that doesn't identify you personally)
 
 ### Debug Symbols (dSYMs)
 
-As an open source project, our build scripts upload debug symbols
-(dSYMs) to Google's servers. We use these files to give us
-deobfuscated and human-readable crash reports, and contain mapping
-information that helps us interpret crash reports. dSYM files only
-contain code-related mapping information to decode a stack-trace into
-a readable format, such as function names, class names, method names,
-and line numbers. They are used to create human-readable crash reports
-to help us understand crashes. These files do not contain any personal
-information about you or your device usage.
+When we build the Trio app, we create special files called debug
+symbols (dSYMs) that help us read crash reports. Think of these like a
+decoder ring for crashes:
+
+Without dSYMs, a crash might look like: "Error at memory address
+0x1234ABCD" With dSYMs, we can see: "Error in function
+'calculateInsulin' at line 157"
+
+These files only contain code-related information that helps us
+understand where crashes happen. They contain no personal information
+about you or how you use Trio.
 
 ## How We Use Your Information
 
 We use anonymous crash report information exclusively to:
 
 - Identify and fix bugs and crashes
-- Improve the App's stability
+- Improve Trio's stability
 
 We do not use this information for any other purpose, such as
 analytics, marketing, or user profiling.
@@ -64,38 +71,36 @@ documentation.
 ### Open Source Contributors
 
 As an open source project, crash reports and debugging information may
-be visible to project contributors who help maintain and improve the
-App. All contributors are expected to adhere to this privacy policy
+be visible to project contributors who help maintain and improve
+Trio. All contributors are expected to adhere to this privacy policy
 and handle any data responsibly.
 
-## Opting Out
+## Opting Out and Data Retention
 
 You can opt out of crash reporting at any time through the Trio
 settings. If you opt out:
 
-- No crash data will be collected or sent to us
-- Previously collected crash data will still be retained as described below
+- No new crash data will be collected or sent to us
+- Previously collected crash data will still be retained for approximately 90 days
 
 To avoid sending dSYMs to Crashlytics, you can delete the Trio target
 Build Phase script, titled "Copy dSYMs to Crashlytics".
 
-## Data Retention
-
-Crash data and associated debugging information are retained only as
-long as necessary to analyze and fix issues. Typically, this is for a
-period of 90 days.
-
 ## Your Rights
 
-You have certain rights regarding your personal information,
-including:
+You have certain rights regarding your information, including:
 
-- The right to access the information we have about you
+- The right to opt-out of crash reporting
 - The right to request deletion of your data
-- The right to opt-out of crash reporting (as described above)
 
-To exercise these rights, please contact us using the information
-provided below.
+To opt-out of crash reporting, please see the section above for
+details about how to configure Trio to not record crash reports.
+
+The information we store is anonymous, so we are unable to look up
+information for a particular individual. However, our general data
+retention policy ensures that data older than 90 days is deleted,
+enabling us to accommodate data deletion requests by design despite
+having anonymous data.
 
 ## Changes to This Privacy Policy
 
@@ -106,8 +111,9 @@ updating the "Last Updated" date.
 ## Contact Us
 
 If you have any questions about this Privacy Policy, please contact us
-on [Discord](http://discord.diy-trio.org/).
+on [Discord](http://discord.diy-trio.org/) or send us an email at
+trio.diy.diabetes@gmail.com.
 
 ## Last Updated
 
-April 6th, 2025
+April 15, 2025

+ 15 - 2
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -371,10 +371,16 @@ extension Treatments {
 
         /// Calculate insulin recommendation
         func calculateInsulin() async -> Decimal {
+            // Safely get minPredBG on main thread
+            let localMinPredBG = await MainActor.run {
+                minPredBG
+            }
+
             let result = await bolusCalculationManager.handleBolusCalculation(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrectionFactor,
-                useSuperBolus: useSuperBolus
+                useSuperBolus: useSuperBolus,
+                minPredBG: localMinPredBG
             )
 
             // Update state properties with calculation results on main thread
@@ -743,7 +749,6 @@ extension Treatments.StateModel {
 
             let determination = await determinationFetchContext.perform {
                 let determinationObject = determinationObjects.first
-                let eventualBG = determinationObject?.eventualBG?.intValue
 
                 let forecastsSet = determinationObject?.forecasts ?? []
                 let predictions = Predictions(
@@ -820,11 +825,19 @@ extension Treatments.StateModel {
         debug(.bolusState, "updateForecasts fired")
         if let forecastData = forecastData {
             simulatedDetermination = forecastData
+            debugPrint("\(DebuggingIdentifiers.failed) minPredBG: \(minPredBG)")
         } else {
             simulatedDetermination = await Task { [self] in
                 debug(.bolusState, "calling simulateDetermineBasal to get forecast data")
                 return await apsManager.simulateDetermineBasal(simulatedCarbsAmount: carbs, simulatedBolusAmount: amount)
             }.value
+
+            // Update evBG and minPredBG from simulated determination
+            if let simDetermination = simulatedDetermination {
+                evBG = Decimal(simDetermination.eventualBG ?? 0)
+                minPredBG = simDetermination.minPredBGFromReason ?? 0
+                debugPrint("\(DebuggingIdentifiers.inProgress) minPredBG: \(minPredBG)")
+            }
         }
 
         predictionsForChart = simulatedDetermination?.predictions

+ 1 - 1
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -73,8 +73,8 @@ extension Treatments {
             debounce?.cancel()
             debounce = DispatchWorkItem { [self] in
                 Task {
-                    state.insulinCalculated = await state.calculateInsulin()
                     await state.updateForecasts()
+                    state.insulinCalculated = await state.calculateInsulin()
                 }
             }
             if let debounce = debounce {

+ 31 - 6
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -4,7 +4,8 @@ import Swinject
 
 protocol BolusCalculationManager {
     func calculateInsulin(input: CalculationInput) async -> CalculationResult
-    func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool) async -> CalculationResult
+    func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool, minPredBG: Decimal?) async
+        -> CalculationResult
 }
 
 final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
@@ -280,7 +281,8 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     private func prepareCalculationInput(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
-        useSuperBolus: Bool
+        useSuperBolus: Bool,
+        minPredBG: Decimal?
     ) async throws -> CalculationInput {
         do {
             // Get settings
@@ -296,7 +298,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             let currentISF = await getCurrentSettingValue(for: .isf)
 
             // Get max IOB and max COB
-
             let preferences = await getPreferences()
             let maxIOB = preferences.maxIOB
             let maxCOB = preferences.maxCOB
@@ -347,7 +348,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 maxBolus: maxBolus,
                 maxIOB: maxIOB,
                 maxCOB: maxCOB,
-                minPredBG: bolusVars.minPredBG
+                minPredBG: minPredBG ?? bolusVars.minPredBG
             )
         } catch {
             debug(
@@ -365,45 +366,56 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     func calculateInsulin(input: CalculationInput) async -> CalculationResult {
         // insulin needed for the current blood glucose
         let targetDifference = input.currentBG - input.target
+        debug(.default, "Target difference: \(targetDifference)")
 
         let targetDifferenceInsulin = targetDifference / input.isf
+        debug(.default, "Target difference insulin: \(targetDifferenceInsulin)")
 
         // more or less insulin because of bg trend in the last 15 minutes
         let fifteenMinutesInsulin = input.deltaBG / input.isf
+        debug(.default, "15min insulin: \(fifteenMinutesInsulin)")
 
         // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
         let wholeCob = min(Decimal(input.cob) + input.carbs, input.maxCOB)
         let wholeCobInsulin = wholeCob / input.carbRatio
+        debug(.default, "Whole COB: \(wholeCob), COB insulin: \(wholeCobInsulin)")
 
         // determine how much the calculator reduces/ increases the bolus because of IOB
         let iobInsulinReduction = (-1) * input.iob
+        debug(.default, "IOB reduction: \(iobInsulinReduction)")
 
         // adding everything together
         // add a calc for the case that no fifteenMinInsulin is available
         let wholeCalc: Decimal
         if input.deltaBG != 0 {
             wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinutesInsulin)
+            debug(.default, "Whole calc (with delta): \(wholeCalc)")
         } else {
             // add (rare) case that no glucose value is available -> maybe display warning?
             // if no bg is available, ?? sets its value to 0
             if input.currentBG == 0 {
                 wholeCalc = (iobInsulinReduction + wholeCobInsulin)
+                debug(.default, "Whole calc (no BG): \(wholeCalc)")
             } else {
                 wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
+                debug(.default, "Whole calc (no delta): \(wholeCalc)")
             }
         }
 
         // apply custom factor at the end of the calculations
         // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
         var factoredInsulin = wholeCalc
+        debug(.default, "Initial factored insulin: \(factoredInsulin)")
 
         // Apply Recommended Bolus Percentage (input.fraction) and if selected apply Fatty Meal Bolus Percentage (input.fattyMealFactor)
         // If factoredInsulin is negative, though, don't apply either
         if factoredInsulin > 0 {
             factoredInsulin *= input.fraction
+            debug(.default, "After fraction (\(input.fraction)): \(factoredInsulin)")
 
             if input.useFattyMealCorrectionFactor {
                 factoredInsulin *= input.fattyMealFactor
+                debug(.default, "After fatty meal factor (\(input.fattyMealFactor)): \(factoredInsulin)")
             }
         }
 
@@ -412,24 +424,34 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         if input.useSuperBolus {
             superBolusInsulin = input.sweetMealFactor * input.basal
             factoredInsulin += superBolusInsulin
+            debug(.default, "After super bolus (\(superBolusInsulin)): \(factoredInsulin)")
         }
 
         // the final result for recommended insulin amount
         var insulinCalculated: Decimal
         let isLoopStale = Date().timeIntervalSince(apsManager.lastLoopDate) > 15 * 60
+        debug(.default, "Loop stale: \(isLoopStale), currentBG: \(input.currentBG), minPredBG: \(input.minPredBG)")
 
         // don't recommend insulin when current glucose or minPredBG is < 54 or last sucessful loop was over 15 minutes ago
         if input.currentBG < 54 || input.minPredBG < 54 || isLoopStale {
             insulinCalculated = 0
+            debug(.default, "Insulin set to 0 due to safety check - BG < 54 or stale loop")
         } else {
             // no negative insulinCalculated
             insulinCalculated = max(factoredInsulin, 0)
+            debug(.default, "After max(0): \(insulinCalculated)")
+
             // don't exceed maxBolus
             insulinCalculated = min(insulinCalculated, input.maxBolus)
+            debug(.default, "After maxBolus (\(input.maxBolus)): \(insulinCalculated)")
+
             // don't exceed maxIOB
             insulinCalculated = min(insulinCalculated, input.maxIOB - input.iob)
+            debug(.default, "After maxIOB check (\(input.maxIOB) - \(input.iob)): \(insulinCalculated)")
+
             // round calculated recommendation to allowed bolus increment
             insulinCalculated = apsManager.roundBolus(amount: insulinCalculated)
+            debug(.default, "Final rounded insulin: \(insulinCalculated)")
         }
 
         return CalculationResult(
@@ -452,17 +474,20 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     ///   - carbs: Amount of carbohydrates to be consumed
     ///   - useFattyMealCorrection: Whether to apply fatty meal correction
     ///   - useSuperBolus: Whether to use super bolus calculation
+    ///   - minPredBG: Minimum Predicted Glucose determined by Oref
     /// - Returns: CalculationResult containing the calculated insulin dose and details
     func handleBolusCalculation(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
-        useSuperBolus: Bool
+        useSuperBolus: Bool,
+        minPredBG: Decimal? = nil
     ) async -> CalculationResult {
         do {
             let input = try await prepareCalculationInput(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrection,
-                useSuperBolus: useSuperBolus
+                useSuperBolus: useSuperBolus,
+                minPredBG: minPredBG
             )
             let result = await calculateInsulin(input: input)
             return result

+ 2 - 1
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -588,7 +588,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                     let result = await bolusCalculationManager.handleBolusCalculation(
                         carbs: Decimal(carbs),
                         useFattyMealCorrection: false,
-                        useSuperBolus: false
+                        useSuperBolus: false,
+                        minPredBG: 60 // TODO:
                     )
 
                     // Send recommendation back to watch