Explorar o código

Merge branch 'dan-test' into core-data_stable

polscm32 aka Marvout hai 1 ano
pai
achega
a7e6e4347c

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

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

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

@@ -724,7 +724,6 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     private func reportEnacted(wasEnacted: Bool) async {
-
         guard let determinationID = await determinationStorage
             .fetchLastDeterminationObjectID(predicate: NSPredicate.predicateFor30MinAgoForDetermination).first
         else {
@@ -751,7 +750,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
 
                 Task.detached(priority: .low) {
-                    await self.nightscout.uploadStatus()
                     await self.statistics()
                 }
             } else {

+ 7 - 0
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -389,6 +389,9 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
     func pumpManagerWillDeactivate(_: PumpManager) {
         dispatchPrecondition(condition: .onQueue(processQueue))
         pumpManager = nil
+        broadcaster.notify(PumpDeactivatedObserver.self, on: processQueue) {
+            $0.pumpDeactivatedDidChange()
+        }
     }
 
     func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
@@ -626,3 +629,7 @@ protocol PumpBatteryObserver {
 protocol PumpTimeZoneObserver {
     func pumpTimeZoneDidChange(_ timezone: TimeZone)
 }
+
+protocol PumpDeactivatedObserver {
+    func pumpDeactivatedDidChange()
+}

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

@@ -4,8 +4,9 @@ import Swinject
 
 protocol DeterminationStorage {
     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 {
@@ -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 forecasts
-        } catch {
-            debugPrint(
-                "Failed \(DebuggingIdentifiers.failed) to fetch 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 forecastValues.sorted(by: { $0.index < $1.index })
-        } catch {
-            debugPrint(
-                "Failed \(DebuggingIdentifiers.failed) to fetch Forecast 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
         }
     }
 }

+ 64 - 12
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -79,6 +79,9 @@ extension Home {
         @Published var overrides: [OverrideStored] = []
         @Published var overrideRunStored: [OverrideRunStored] = []
         @Published var isOverrideCancelled: Bool = false
+        @Published var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
+        @Published var pumpStatusHighlightMessage: String? = nil
+
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
@@ -128,6 +131,7 @@ extension Home {
             broadcaster.register(BasalProfileObserver.self, observer: self)
             broadcaster.register(TempTargetsObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
+            broadcaster.register(PumpDeactivatedObserver.self, observer: self)
 
             animatedBackground = settingsManager.settings.animatedBackground
 
@@ -189,6 +193,7 @@ extension Home {
                         self.setupPump = false
                     } else {
                         self.setupReservoir()
+                        self.displayPumpStatusHighlightMessage()
                     }
                 }
                 .store(in: &lifetime)
@@ -206,6 +211,8 @@ extension Home {
                             setupDelegate: self
                         ).asAny()
                         self.router.mainSecondaryModalView.send(view)
+                    } else if show {
+                        self.router.mainSecondaryModalView.send(self.router.view(for: .pumpConfigDirect))
                     } else {
                         self.router.mainSecondaryModalView.send(nil)
                     }
@@ -213,6 +220,22 @@ extension Home {
                 .store(in: &lifetime)
         }
 
+        /// Display the eventual status message provided by the manager of the pump
+        /// Only display if state is warning or critical message else return nil
+        private func displayPumpStatusHighlightMessage(_ didDeactivate: Bool = false) {
+            DispatchQueue.main.async { [weak self] in
+                guard let self = self else { return }
+                if let statusHighlight = self.provider.deviceManager.pumpManager?.pumpStatusHighlight,
+                   statusHighlight.state == .warning || statusHighlight.state == .critical, !didDeactivate
+                {
+                    pumpStatusHighlightMessage = (statusHighlight.state == .warning ? "⚠️\n" : "‼️\n") + statusHighlight
+                        .localizedMessage
+                } else {
+                    pumpStatusHighlightMessage = nil
+                }
+            }
+        }
+
         func runLoop() {
             provider.heartbeatNow()
         }
@@ -373,7 +396,8 @@ extension Home.StateModel:
     BasalProfileObserver,
     TempTargetsObserver,
     PumpReservoirObserver,
-    PumpTimeZoneObserver
+    PumpTimeZoneObserver,
+    PumpDeactivatedObserver
 {
     func glucoseDidUpdate(_: [BloodGlucose]) {
 //        setupGlucose()
@@ -423,6 +447,10 @@ extension Home.StateModel:
     func pumpTimeZoneDidChange(_: TimeZone) {
         setupCurrentPumpTimezone()
     }
+
+    func pumpDeactivatedDidChange() {
+        displayPumpStatusHighlightMessage(true)
+    }
 }
 
 extension Home.StateModel: CompletionDelegate {
@@ -519,7 +547,10 @@ extension Home.StateModel {
                 self.setupManualGlucoseArray()
             }
             if !determinationUpdates.isEmpty {
-                self.setupDeterminationsArray()
+                Task {
+                    self.setupDeterminationsArray()
+                    await self.updateForecastData()
+                }
             }
             if !carbUpdates.isEmpty {
                 self.setupCarbsArray()
@@ -528,6 +559,7 @@ extension Home.StateModel {
             if !insulinUpdates.isEmpty {
                 self.setupInsulinArray()
                 self.setupLastBolus()
+                self.displayPumpStatusHighlightMessage()
             }
             if !batteryUpdates.isEmpty {
                 self.setupBatteryArray()
@@ -708,6 +740,7 @@ extension Home.StateModel {
 
             await updateEnacted
             await updateEnactedAndNonEnacted
+            await updateForecastData()
         }
     }
 
@@ -959,19 +992,38 @@ extension Home.StateModel {
 // MARK: Extension for Main Chart to draw Forecasts
 
 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 {
-        let preprocessedData = state.preprocessForecastData()
-
-        return ForEach(preprocessedData, id: \.id) { tuple in
+        ForEach(state.preprocessedData, id: \.id) { tuple in
             let forecastValue = tuple.forecastValue
             let forecast = tuple.forecast
 

+ 58 - 33
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -7,6 +7,7 @@ struct PumpView: View {
     @Binding var expiresAtDate: Date?
     @Binding var timerDate: Date
     @Binding var timeZone: TimeZone?
+    @Binding var pumpStatusHighlightMessage: String?
     var battery: [OpenAPS_Battery]
 
     @Environment(\.colorScheme) var colorScheme
@@ -38,47 +39,71 @@ struct PumpView: View {
     }
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 20) {
-            if let reservoir = reservoir {
-                HStack {
-                    Image(systemName: "cross.vial.fill")
-                        .font(.system(size: 16))
-                        .foregroundColor(reservoirColor)
-                    if reservoir == 0xDEAD_BEEF {
-                        Text("50+ " + NSLocalizedString("U", comment: "Insulin unit")).font(.system(size: 15, design: .rounded))
-                    } else {
-                        Text(
-                            reservoirFormatter
-                                .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
-                        )
-                        .font(.system(size: 16, design: .rounded))
+        if let pumpStatusHighlightMessage = pumpStatusHighlightMessage { // display message instead pump info
+            VStack(alignment: .center) {
+                Text(pumpStatusHighlightMessage).font(.footnote).fontWeight(.bold)
+                    .multilineTextAlignment(.center).frame(maxWidth: /*@START_MENU_TOKEN@*/ .infinity/*@END_MENU_TOKEN@*/)
+            }.frame(width: 100)
+        } else {
+            VStack(alignment: .leading, spacing: 20) {
+                if reservoir == nil && battery.isEmpty {
+                    VStack(alignment: .center, spacing: 12) {
+                        HStack {
+                            Image(systemName: "keyboard.onehanded.left")
+                                .font(.body)
+                                .imageScale(.large)
+                        }
+                        HStack {
+                            Text("Add pump")
+                                .font(.caption)
+                                .bold()
+                        }
                     }
+                    .frame(alignment: .top)
                 }
+                if let reservoir = reservoir {
+                    HStack {
+                        Image(systemName: "cross.vial.fill")
+                            .font(.system(size: 16))
+                            .foregroundColor(reservoirColor)
+                        if reservoir == 0xDEAD_BEEF {
+                            Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
+                                .font(.system(size: 15, design: .rounded))
+                        } else {
+                            Text(
+                                reservoirFormatter
+                                    .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
+                            )
+                            .font(.system(size: 16, design: .rounded))
+                        }
+                    }
 
-                if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
-                    Image(systemName: "clock.badge.exclamationmark.fill")
-                        .font(.system(size: 16))
-                        .symbolRenderingMode(.palette)
-                        .foregroundStyle(.red, Color(.warning))
+                    if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
+                        Image(systemName: "clock.badge.exclamationmark.fill")
+                            .font(.system(size: 16))
+                            .symbolRenderingMode(.palette)
+                            .foregroundStyle(.red, Color(.warning))
+                    }
                 }
-            }
 
-            if (battery.first?.display) != nil, expiresAtDate == nil {
-                HStack {
-                    Image(systemName: "battery.100")
-                        .font(.system(size: 16))
-                        .foregroundColor(batteryColor)
-                    Text("\(Int(battery.first?.percent ?? 100)) %").font(.system(size: 16, design: .rounded))
+                if (battery.first?.display) != nil, expiresAtDate == nil {
+                    HStack {
+                        Image(systemName: "battery.100")
+                            .font(.system(size: 16))
+                            .foregroundColor(batteryColor)
+                        Text("\(Int(battery.first?.percent ?? 100)) %").font(.system(size: 16, design: .rounded))
+                    }
                 }
-            }
 
-            if let date = expiresAtDate {
-                HStack {
-                    Image(systemName: "stopwatch.fill")
-                        .font(.system(size: 16))
-                        .foregroundColor(timerColor)
+                if let date = expiresAtDate {
+                    HStack {
+                        Image(systemName: "stopwatch.fill")
+                            .font(.system(size: 16))
+                            .foregroundColor(timerColor)
 
-                    Text(remainingTimeString(time: date.timeIntervalSince(timerDate))).font(.system(size: 16, design: .rounded))
+                        Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
+                            .font(.system(size: 16, design: .rounded))
+                    }
                 }
             }
         }

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

@@ -168,7 +168,9 @@ extension Home {
                 name: $state.pumpName,
                 expiresAtDate: $state.pumpExpiresAtDate,
                 timerDate: $state.timerDate,
-                timeZone: $state.timeZone, battery: state.batteryFromPersistence
+                timeZone: $state.timeZone,
+                pumpStatusHighlightMessage: $state.pumpStatusHighlightMessage,
+                battery: state.batteryFromPersistence
             ).onTapGesture {
                 if state.pumpDisplayState != nil {
                     state.setupPump = true

+ 2 - 0
FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -4,6 +4,7 @@ import Swinject
 extension PumpConfig {
     struct RootView: BaseView {
         let resolver: Resolver
+        let displayClose: Bool
         @StateObject var state = StateModel()
 
         @Environment(\.colorScheme) var colorScheme
@@ -53,6 +54,7 @@ extension PumpConfig {
                 .onAppear(perform: configureView)
                 .navigationTitle("Pump config")
                 .navigationBarTitleDisplayMode(.automatic)
+                .navigationBarItems(leading: displayClose ? Button("Close", action: state.hideModal) : nil)
                 .sheet(isPresented: $state.setupPump) {
                     if let pumpManager = state.provider.apsManager.pumpManager {
                         PumpSettingsView(

+ 4 - 1
FreeAPS/Sources/Router/Screen.swift

@@ -8,6 +8,7 @@ enum Screen: Identifiable, Hashable {
     case configEditor(file: String)
     case nighscoutConfig
     case pumpConfig
+    case pumpConfigDirect
     case pumpSettingsEditor
     case basalProfileEditor
     case isfEditor
@@ -51,7 +52,9 @@ extension Screen {
         case .nighscoutConfig:
             NightscoutConfig.RootView(resolver: resolver)
         case .pumpConfig:
-            PumpConfig.RootView(resolver: resolver)
+            PumpConfig.RootView(resolver: resolver, displayClose: false)
+        case .pumpConfigDirect:
+            PumpConfig.RootView(resolver: resolver, displayClose: true)
         case .pumpSettingsEditor:
             PumpSettingsEditor.RootView(resolver: resolver)
         case .basalProfileEditor:

+ 16 - 151
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -24,6 +24,7 @@ protocol NightscoutManager: GlucoseSource {
 
 final class BaseNightscoutManager: NightscoutManager, Injectable {
     @Injected() private var keychain: Keychain!
+    @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var overridesStorage: OverrideStorage!
@@ -65,8 +66,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         return NightscoutAPI(url: url, secret: secret)
     }
 
-    private let context = CoreDataStack.shared.newTaskContext()
-
     private var lastEnactedDetermination: OrefDetermination?
     private var lastSuggestedDetermination: OrefDetermination?
 
@@ -304,9 +303,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
 
     private func fetchBattery() -> Battery {
-        context.performAndWait {
+        backgroundContext.performAndWait {
             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 {
                     let percent: Int? = Int(last.percent)
                     let voltage: Decimal? = last.voltage as Decimal?
@@ -335,156 +334,22 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
     }
 
-
-    private func fetchEnactedDetermination() async -> [OrefDetermination] {
-        let enactedDeterminations = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: OrefDetermination.self,
-            onContext: backgroundContext,
-            predicate: NSPredicate.enactedDetermination,
-            key: "deliverAt",
-            ascending: false,
-            fetchLimit: 1
-        )
-
-        let enactedIds = await backgroundContext.perform {
-            enactedDeterminations.map(\.objectID)
-        }
-
-        return await context.perform {
-            do {
-                let enacted = try enactedIds.compactMap { id in
-                    try self.context.existingObject(with: id) as? OrefDetermination
-                }
-
-                debugPrint(
-                    "NightscoutManager \(#function) \(DebuggingIdentifiers.succeeded) fetched ENACTED determinations: \(String(describing: enacted))"
-                )
-
-                return enacted
-            } catch {
-                debugPrint(
-                    "NightscoutManager \(#function) \(DebuggingIdentifiers.failed) error while fetching the determinations: \(error.localizedDescription)"
-                )
-                return []
-            }
-        }
-    }
-
-    private func fetchSuggestedDetermination() async -> [OrefDetermination] {
-        let suggestedDeterminations = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: OrefDetermination.self,
-            onContext: backgroundContext,
-            predicate: NSPredicate(
-                format: "(enacted == %@ OR enacted == NULL) AND deliverAt >= %@",
-                false as NSNumber,
-                Date.halfHourAgo as NSDate
-            ),
-            key: "deliverAt",
-            ascending: false,
-            fetchLimit: 1
-        )
-
-        let suggestedIds = await backgroundContext.perform {
-            suggestedDeterminations.map(\.objectID)
-        }
-
-        return await context.perform {
-            do {
-                let suggested = try suggestedIds.compactMap { id in
-                    try self.context.existingObject(with: id) as? OrefDetermination
-                }
-
-                debugPrint(
-                    "NightscoutManager \(#function) \(DebuggingIdentifiers.succeeded) fetched SUGGESTED determinations: \(String(describing: suggested))"
-                )
-
-                return suggested
-            } catch {
-                debugPrint(
-                    "NightscoutManager \(#function) \(DebuggingIdentifiers.failed) error while fetching the determinations: \(error.localizedDescription)"
-                )
-
-                return []
-            }
-        }
-    }
-
-    func parseOrefDetermination(_ orefDetermination: OrefDetermination) -> Determination {
-        // Convert NSDecimalNumber to Decimal
-        func decimal(from nsDecimalNumber: NSDecimalNumber?) -> Decimal? {
-            nsDecimalNumber?.decimalValue
-        }
-
-        // Convert NSSet to array of Ints for Predictions
-        func parseForecastValues(ofType type: String, from orefDetermination: OrefDetermination) -> [Int]? {
-            guard let forecasts = orefDetermination.forecasts?.filter({ ($0 as? Forecast)?.type == type }) as? [Forecast] else {
-                return nil
-            }
-
-            return forecasts.flatMap { forecast in
-                (forecast.forecastValues as? [ForecastValue])?.map { Int($0.value) } ?? []
-            }
-        }
-
-        // Parse Insulin data
-        let insulin = Insulin(
-            TDD: decimal(from: orefDetermination.totalDailyDose),
-            bolus: orefDetermination.bolus as Decimal?,
-            temp_basal: orefDetermination.tempBasal as Decimal?,
-            scheduled_basal: orefDetermination.scheduledBasal as Decimal?
-        )
-
-        // Parse Predictions data
-        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)
+    func uploadStatus() async {
+//        var enacted: Determination?
+//        var suggested: Determination?
+        let enacted = await determinationStorage.parseOrefDetermination(
+            await determinationStorage
+                .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
         )
 
-        return 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, // Assuming you have this property
-            tdd: decimal(from: orefDetermination.totalDailyDose),
-            insulin: insulin,
-            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)
+        let suggested = await determinationStorage.parseOrefDetermination(
+            await determinationStorage
+                .fetchLastDeterminationObjectID(predicate: NSPredicate(
+                    format: "(enacted == %@ OR enacted == NULL) AND deliverAt >= %@",
+                    false as NSNumber,
+                    Date.halfHourAgo as NSDate
+                ))
         )
-    }
-
-    func uploadStatus() async {
-        let enactedFromPersistence = await fetchEnactedDetermination()
-        let suggestedFromPersistence = await fetchSuggestedDetermination()
-
-        var enacted: Determination?
-        var suggested: Determination?
-
-        if let lastEnacted = enactedFromPersistence.first, let lastSuggested = suggestedFromPersistence.first {
-            enacted = parseOrefDetermination(lastEnacted)
-            suggested = parseOrefDetermination(lastSuggested)
-        }
 
         let iob = storage.retrieve(OpenAPS.Monitor.iob, as: [IOBEntry].self)