瀏覽代碼

fixes for thread safety, fix predictions not showing up ... wip

polscm32 aka Marvout 1 年之前
父節點
當前提交
0ec2a8870f

+ 1 - 1
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -39,7 +39,7 @@
       },
       },
       {
       {
         "package": "SwiftCharts",
         "package": "SwiftCharts",
-        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git",
+        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
         "state": {
         "state": {
           "branch": "master",
           "branch": "master",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",

+ 0 - 1
FreeAPS/Sources/APS/APSManager.swift

@@ -750,7 +750,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
                 }
 
 
                 Task.detached(priority: .low) {
                 Task.detached(priority: .low) {
-                    await self.nightscout.uploadStatus()
                     await self.statistics()
                     await self.statistics()
                 }
                 }
             } else {
             } else {

+ 131 - 26
FreeAPS/Sources/APS/Storage/DeterminationStorage.swift

@@ -4,8 +4,9 @@ import Swinject
 
 
 protocol DeterminationStorage {
 protocol DeterminationStorage {
     func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID]
     func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID]
-    func getForecasts(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) -> [Forecast]
-    func getForecastValues(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) -> [ForecastValue]
+    func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
+    func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
+    func parseOrefDetermination(_ determinationIds: [NSManagedObjectID]) async -> Determination?
 }
 }
 
 
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
@@ -30,37 +31,141 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getForecasts(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) -> [Forecast] {
-        do {
-            guard let determination = try context.existingObject(with: determinationID) as? OrefDetermination,
-                  let forecastSet = determination.forecasts,
-                  let forecasts = Array(forecastSet) as? [Forecast]
-            else {
+    func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID] {
+        await context.perform {
+            do {
+                guard let determination = try context.existingObject(with: determinationID) as? OrefDetermination,
+                      let forecastSet = determination.forecasts
+                else {
+                    return []
+                }
+                let forecasts = Array(forecastSet)
+                return forecasts.map(\.objectID)
+            } catch {
+                debugPrint(
+                    "Failed \(DebuggingIdentifiers.failed) to fetch Forecast IDs for OrefDetermination with ID \(determinationID): \(error.localizedDescription)"
+                )
                 return []
                 return []
             }
             }
-            return forecasts
-        } catch {
-            debugPrint(
-                "Failed \(DebuggingIdentifiers.failed) to fetch Forecasts OrefDetermination with ID \(determinationID): \(error.localizedDescription)"
-            )
-            return []
         }
         }
     }
     }
 
 
-    func getForecastValues(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) -> [ForecastValue] {
-        do {
-            guard let forecast = try context.existingObject(with: forecastID) as? Forecast,
-                  let forecastValueSet = forecast.forecastValues,
-                  let forecastValues = Array(forecastValueSet) as? [ForecastValue]
-            else {
+    func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID] {
+        await context.perform {
+            do {
+                guard let forecast = try context.existingObject(with: forecastID) as? Forecast,
+                      let forecastValueSet = forecast.forecastValues
+                else {
+                    return []
+                }
+                let forecastValues = forecastValueSet.sorted(by: { $0.index < $1.index })
+                return forecastValues.map(\.objectID)
+            } catch {
+                debugPrint(
+                    "Failed \(DebuggingIdentifiers.failed) to fetch Forecast Value IDs with ID \(forecastID): \(error.localizedDescription)"
+                )
                 return []
                 return []
             }
             }
-            return forecastValues.sorted(by: { $0.index < $1.index })
-        } catch {
-            debugPrint(
-                "Failed \(DebuggingIdentifiers.failed) to fetch Forecast Values with ID \(forecastID): \(error.localizedDescription)"
-            )
-            return []
+        }
+    }
+
+    // Convert NSDecimalNumber to Decimal
+    func decimal(from nsDecimalNumber: NSDecimalNumber?) -> Decimal {
+        nsDecimalNumber?.decimalValue ?? 0.0
+    }
+
+    // Convert NSSet to array of Ints for Predictions
+    func parseForecastValues(ofType _: String, from determinationID: NSManagedObjectID) async -> [Int]? {
+        let forecastIDs = await getForecastIDs(for: determinationID, in: backgroundContext)
+
+        var forecastValuesList: [Int] = []
+
+        for forecastID in forecastIDs {
+            let forecastValueIDs = await getForecastValueIDs(for: forecastID, in: backgroundContext)
+
+            await backgroundContext.perform {
+                for forecastValueID in forecastValueIDs {
+                    if let forecastValue = try? self.backgroundContext.existingObject(with: forecastValueID) as? ForecastValue {
+                        let forecastValueInt = Int(forecastValue.value)
+                        forecastValuesList.append(forecastValueInt)
+                    }
+                }
+            }
+        }
+
+        return forecastValuesList
+    }
+
+    func parseOrefDetermination(_ determinationIds: [NSManagedObjectID]) async -> Determination? {
+        var result: Determination?
+
+        guard let determinationId = determinationIds.first else {
+            print("No determination ID found.")
+            return nil
+        }
+
+        print("Using context: \(backgroundContext)")
+        print("Determination ID: \(determinationId)")
+
+        let predictions = Predictions(
+            iob: await parseForecastValues(ofType: "iob", from: determinationId),
+            zt: await parseForecastValues(ofType: "zt", from: determinationId),
+            cob: await parseForecastValues(ofType: "cob", from: determinationId),
+            uam: await parseForecastValues(ofType: "uam", from: determinationId)
+        )
+
+        return await backgroundContext.perform {
+            do {
+                let orefDetermination = try self.backgroundContext.existingObject(with: determinationId) as? OrefDetermination
+
+                // Log the type of the fetched object
+                print("Fetched object type: \(type(of: orefDetermination))")
+                print("Fetched object description: \(orefDetermination)")
+
+                // Check if the fetched object is of the expected type
+                if let orefDetermination = orefDetermination {
+                    print("Successfully cast to OrefDetermination")
+                    let forecastSet = orefDetermination.forecasts
+                    print("Fetched forecast set: \(forecastSet)")
+
+                    result = Determination(
+                        reason: orefDetermination.reason ?? "",
+                        units: orefDetermination.smbToDeliver as Decimal?,
+                        insulinReq: self.decimal(from: orefDetermination.insulinReq),
+                        eventualBG: orefDetermination.eventualBG as? Int,
+                        sensitivityRatio: self.decimal(from: orefDetermination.sensitivityRatio),
+                        rate: self.decimal(from: orefDetermination.rate),
+                        duration: self.decimal(from: orefDetermination.duration),
+                        iob: self.decimal(from: orefDetermination.iob),
+                        cob: orefDetermination.cob != 0 ? Decimal(orefDetermination.cob) : nil,
+                        predictions: predictions,
+                        deliverAt: orefDetermination.deliverAt,
+                        carbsReq: orefDetermination.carbsRequired != 0 ? Decimal(orefDetermination.carbsRequired) : nil,
+                        temp: TempType(rawValue: orefDetermination.temp ?? "absolute"),
+                        bg: self.decimal(from: orefDetermination.glucose),
+                        reservoir: self.decimal(from: orefDetermination.reservoir),
+                        isf: self.decimal(from: orefDetermination.insulinSensitivity),
+                        timestamp: orefDetermination.timestamp,
+                        tdd: self.decimal(from: orefDetermination.totalDailyDose),
+                        insulin: nil,
+                        current_target: self.decimal(from: orefDetermination.currentTarget),
+                        insulinForManualBolus: self.decimal(from: orefDetermination.insulinForManualBolus),
+                        manualBolusErrorString: self.decimal(from: orefDetermination.manualBolusErrorString),
+                        minDelta: self.decimal(from: orefDetermination.minDelta),
+                        expectedDelta: self.decimal(from: orefDetermination.expectedDelta),
+                        minGuardBG: nil,
+                        minPredBG: nil,
+                        threshold: self.decimal(from: orefDetermination.threshold),
+                        carbRatio: self.decimal(from: orefDetermination.carbRatio)
+                    )
+                } else {
+                    print("Fetched object is not of type OrefDetermination")
+                }
+            } catch {
+                print("Failed to fetch managed object: \(error)")
+            }
+
+            return result
         }
         }
     }
     }
 }
 }

+ 38 - 11
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -79,6 +79,8 @@ extension Home {
         @Published var overrides: [OverrideStored] = []
         @Published var overrides: [OverrideStored] = []
         @Published var overrideRunStored: [OverrideRunStored] = []
         @Published var overrideRunStored: [OverrideRunStored] = []
         @Published var isOverrideCancelled: Bool = false
         @Published var isOverrideCancelled: Bool = false
+        @Published var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
+
         let context = CoreDataStack.shared.newTaskContext()
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
 
@@ -91,6 +93,9 @@ extension Home {
             setupCarbsArray()
             setupCarbsArray()
             setupFPUsArray()
             setupFPUsArray()
             setupDeterminationsArray()
             setupDeterminationsArray()
+            Task {
+                await updateForecastData()
+            }
             setupInsulinArray()
             setupInsulinArray()
             setupLastBolus()
             setupLastBolus()
             setupBatteryArray()
             setupBatteryArray()
@@ -519,7 +524,10 @@ extension Home.StateModel {
                 self.setupManualGlucoseArray()
                 self.setupManualGlucoseArray()
             }
             }
             if !determinationUpdates.isEmpty {
             if !determinationUpdates.isEmpty {
-                self.setupDeterminationsArray()
+                Task {
+                    self.setupDeterminationsArray()
+                    await self.updateForecastData()
+                }
             }
             }
             if !carbUpdates.isEmpty {
             if !carbUpdates.isEmpty {
                 self.setupCarbsArray()
                 self.setupCarbsArray()
@@ -959,19 +967,38 @@ extension Home.StateModel {
 // MARK: Extension for Main Chart to draw Forecasts
 // MARK: Extension for Main Chart to draw Forecasts
 
 
 extension Home.StateModel {
 extension Home.StateModel {
-    func preprocessForecastData() -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] {
-        determinationsFromPersistence
-            .compactMap { determination -> NSManagedObjectID? in
-                determination.objectID
+    func preprocessForecastData() async -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] {
+        guard let id = determinationsFromPersistence.first?.objectID else {
+            return []
+        }
+
+        // Get forecast and forecast values
+        let forecastIDs = await determinationStorage.getForecastIDs(for: id, in: context)
+        var result: [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] = []
+
+        for forecastID in forecastIDs {
+            // Get the forecast value IDs for the given forecast ID
+            let forecastValueIDs = await determinationStorage.getForecastValueIDs(for: forecastID, in: context)
+            let uuid = UUID()
+            result.append((id: uuid, forecastID: forecastID, forecastValueIDs: forecastValueIDs))
+        }
+
+        return result
+    }
+
+    @MainActor func updateForecastData() async {
+        let forecastData = await preprocessForecastData()
+
+        preprocessedData = forecastData.reduce(into: []) { result, data in
+            guard let forecast = try? viewContext.existingObject(with: data.forecastID) as? Forecast else {
+                return
             }
             }
-            .flatMap { determinationID -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] in
-                let forecasts = determinationStorage.getForecasts(for: determinationID, in: viewContext)
 
 
-                return forecasts.flatMap { forecast in
-                    determinationStorage.getForecastValues(for: forecast.objectID, in: viewContext).map { forecastValue in
-                        (id: UUID(), forecast: forecast, forecastValue: forecastValue)
-                    }
+            for forecastValueID in data.forecastValueIDs {
+                if let forecastValue = try? viewContext.existingObject(with: forecastValueID) as? ForecastValue {
+                    result.append((id: data.id, forecast: forecast, forecastValue: forecastValue))
                 }
                 }
             }
             }
+        }
     }
     }
 }
 }

+ 1 - 3
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -448,9 +448,7 @@ extension MainChartView {
     }
     }
 
 
     private func drawForecasts() -> some ChartContent {
     private func drawForecasts() -> some ChartContent {
-        let preprocessedData = state.preprocessForecastData()
-
-        return ForEach(preprocessedData, id: \.id) { tuple in
+        ForEach(state.preprocessedData, id: \.id) { tuple in
             let forecastValue = tuple.forecastValue
             let forecastValue = tuple.forecastValue
             let forecast = tuple.forecast
             let forecast = tuple.forecast
 
 

+ 17 - 104
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -66,8 +66,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         return NightscoutAPI(url: url, secret: secret)
         return NightscoutAPI(url: url, secret: secret)
     }
     }
 
 
-    private let context = CoreDataStack.shared.newTaskContext()
-
     private var lastEnactedDetermination: OrefDetermination?
     private var lastEnactedDetermination: OrefDetermination?
     private var lastSuggestedDetermination: OrefDetermination?
     private var lastSuggestedDetermination: OrefDetermination?
 
 
@@ -305,9 +303,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
     }
 
 
     private func fetchBattery() -> Battery {
     private func fetchBattery() -> Battery {
-        context.performAndWait {
+        backgroundContext.performAndWait {
             do {
             do {
-                let results = try context.fetch(OpenAPS_Battery.fetch(NSPredicate.predicateFor30MinAgo))
+                let results = try backgroundContext.fetch(OpenAPS_Battery.fetch(NSPredicate.predicateFor30MinAgo))
                 if let last = results.first {
                 if let last = results.first {
                     let percent: Int? = Int(last.percent)
                     let percent: Int? = Int(last.percent)
                     let voltage: Decimal? = last.voltage as Decimal?
                     let voltage: Decimal? = last.voltage as Decimal?
@@ -336,107 +334,22 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func parseOrefDetermination(_ determinationIds: [NSManagedObjectID]) -> Determination? {
-        // Convert NSDecimalNumber to Decimal
-        func decimal(from nsDecimalNumber: NSDecimalNumber?) -> Decimal {
-            nsDecimalNumber?.decimalValue ?? 0.0
-        }
-
-        // Convert NSSet to array of Ints for Predictions
-        func parseForecastValues(ofType _: String, from orefDetermination: OrefDetermination) -> [Int]? {
-            let determinationID = orefDetermination.objectID
-            let forecasts = determinationStorage.getForecasts(for: determinationID, in: context)
-
-            return forecasts.flatMap { forecast in
-                determinationStorage.getForecastValues(for: forecast.objectID, in: context).map { forecastValue in
-                    Int(forecastValue.value)
-                }
-            }
-        }
-
-        var result: Determination?
-
-        guard let determinationId = determinationIds.first else {
-            print("No determination ID found.")
-            return nil
-        }
-
-        print("Using context: \(backgroundContext)")
-        print("Determination ID: \(determinationId)")
-
-        do {
-            let orefDetermination = try backgroundContext.existingObject(with: determinationId)
-
-            // Log the type of the fetched object
-            print("Fetched object type: \(type(of: orefDetermination))")
-            print("Fetched object description: \(orefDetermination)")
-
-            // Check if the fetched object is of the expected type
-            if let orefDetermination = orefDetermination as? OrefDetermination {
-                print("Successfully cast to OrefDetermination")
-                let forecastSet = orefDetermination.forecasts
-                print("Fetched forecast set: \(forecastSet)")
-
-                let predictions = Predictions(
-                    iob: parseForecastValues(ofType: "iob", from: orefDetermination),
-                    zt: parseForecastValues(ofType: "zt", from: orefDetermination),
-                    cob: parseForecastValues(ofType: "cob", from: orefDetermination),
-                    uam: parseForecastValues(ofType: "uam", from: orefDetermination)
-                )
-
-                result = Determination(
-                    reason: orefDetermination.reason ?? "",
-                    units: orefDetermination.smbToDeliver as Decimal?,
-                    insulinReq: decimal(from: orefDetermination.insulinReq),
-                    eventualBG: orefDetermination.eventualBG as? Int,
-                    sensitivityRatio: decimal(from: orefDetermination.sensitivityRatio),
-                    rate: decimal(from: orefDetermination.rate),
-                    duration: decimal(from: orefDetermination.duration),
-                    iob: decimal(from: orefDetermination.iob),
-                    cob: orefDetermination.cob != 0 ? Decimal(orefDetermination.cob) : nil,
-                    predictions: predictions,
-                    deliverAt: orefDetermination.deliverAt,
-                    carbsReq: orefDetermination.carbsRequired != 0 ? Decimal(orefDetermination.carbsRequired) : nil,
-                    temp: TempType(rawValue: orefDetermination.temp ?? "absolute"),
-                    bg: decimal(from: orefDetermination.glucose),
-                    reservoir: decimal(from: orefDetermination.reservoir),
-                    isf: decimal(from: orefDetermination.insulinSensitivity),
-                    timestamp: orefDetermination.timestamp,
-                    tdd: decimal(from: orefDetermination.totalDailyDose),
-                    insulin: nil,
-                    current_target: decimal(from: orefDetermination.currentTarget),
-                    insulinForManualBolus: decimal(from: orefDetermination.insulinForManualBolus),
-                    manualBolusErrorString: decimal(from: orefDetermination.manualBolusErrorString),
-                    minDelta: decimal(from: orefDetermination.minDelta),
-                    expectedDelta: decimal(from: orefDetermination.expectedDelta),
-                    minGuardBG: nil,
-                    minPredBG: nil,
-                    threshold: decimal(from: orefDetermination.threshold),
-                    carbRatio: decimal(from: orefDetermination.carbRatio)
-                )
-            } else {
-                print("Fetched object is not of type OrefDetermination")
-            }
-        } catch {
-            print("Failed to fetch managed object: \(error)")
-        }
-
-        return result
-    }
-
     func uploadStatus() async {
     func uploadStatus() async {
-        var enacted: Determination?
-        var suggested: Determination?
-//        let enacted = parseOrefDetermination(
-//            await determinationStorage
-//                .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
-//        )
-//
-//        let suggested = parseOrefDetermination(await determinationStorage.fetchLastDeterminationObjectID(predicate: NSPredicate(
-//            format: "(enacted == %@ OR enacted == NULL) AND deliverAt >= %@",
-//            false as NSNumber,
-//            Date.halfHourAgo as NSDate
-//        )))
+//        var enacted: Determination?
+//        var suggested: Determination?
+        let enacted = await determinationStorage.parseOrefDetermination(
+            await determinationStorage
+                .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
+        )
+
+        let suggested = await determinationStorage.parseOrefDetermination(
+            await determinationStorage
+                .fetchLastDeterminationObjectID(predicate: NSPredicate(
+                    format: "(enacted == %@ OR enacted == NULL) AND deliverAt >= %@",
+                    false as NSNumber,
+                    Date.halfHourAgo as NSDate
+                ))
+        )
 
 
         let iob = storage.retrieve(OpenAPS.Monitor.iob, as: [IOBEntry].self)
         let iob = storage.retrieve(OpenAPS.Monitor.iob, as: [IOBEntry].self)