Bladeren bron

shift work off the main thread...wip

polscm32 1 jaar geleden
bovenliggende
commit
9de859e86d

+ 88 - 70
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -105,7 +105,7 @@ extension Bolus {
 
         let now = Date.now
 
-        let context = CoreDataStack.shared.persistentContainer.viewContext
+        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
         let backgroundContext = CoreDataStack.shared.newTaskContext()
 
         private var coreDataObserver: CoreDataObserver?
@@ -159,7 +159,7 @@ extension Bolus {
 
         // MARK: - Basal
 
-        func getCurrentBasal() {
+        func getCurrentBasal() async {
             let basalEntries = provider.getProfile()
             let now = Date()
             let calendar = Calendar.current
@@ -173,11 +173,11 @@ extension Bolus {
                     continue
                 }
 
-                // Combine the current date with the time from entry.start
+                let entryComponents = calendar.dateComponents([.hour, .minute, .second], from: entryTime)
                 let entryStartTime = calendar.date(
-                    bySettingHour: calendar.component(.hour, from: entryTime),
-                    minute: calendar.component(.minute, from: entryTime),
-                    second: calendar.component(.second, from: entryTime),
+                    bySettingHour: entryComponents.hour!,
+                    minute: entryComponents.minute!,
+                    second: entryComponents.second!,
                     of: now
                 )!
 
@@ -185,20 +185,21 @@ extension Bolus {
                 if index < basalEntries.count - 1,
                    let nextEntryTime = dateFormatter.date(from: basalEntries[index + 1].start)
                 {
-                    let nextEntryStartTime = calendar.date(
-                        bySettingHour: calendar.component(.hour, from: nextEntryTime),
-                        minute: calendar.component(.minute, from: nextEntryTime),
-                        second: calendar.component(.second, from: nextEntryTime),
+                    let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
+                    entryEndTime = calendar.date(
+                        bySettingHour: nextEntryComponents.hour!,
+                        minute: nextEntryComponents.minute!,
+                        second: nextEntryComponents.second!,
                         of: now
                     )!
-                    entryEndTime = nextEntryStartTime
                 } else {
-                    // If it's the last entry, use the same start time plus one day as the end time
                     entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
                 }
 
                 if now >= entryStartTime, now < entryEndTime {
-                    currentBasal = entry.rate
+                    await MainActor.run {
+                        currentBasal = entry.rate
+                    }
                     break
                 }
             }
@@ -333,24 +334,26 @@ extension Bolus {
         }
 
         private func savePumpInsulin(amount _: Decimal) {
-            context.perform {
+            backgroundContext.perform {
                 // create pump event
-                let newPumpEvent = PumpEventStored(context: self.context)
+                let newPumpEvent = PumpEventStored(context: self.backgroundContext)
                 newPumpEvent.timestamp = Date()
                 newPumpEvent.type = PumpEvent.bolus.rawValue
 
                 // create bolus entry and specify relationship to pump event
-                let newBolusEntry = BolusStored(context: self.context)
+                let newBolusEntry = BolusStored(context: self.backgroundContext)
                 newBolusEntry.pumpEvent = newPumpEvent
                 newBolusEntry.amount = self.amount as NSDecimalNumber
                 newBolusEntry.isExternal = false
                 newBolusEntry.isSMB = false
 
                 do {
-                    guard self.context.hasChanges else { return }
-                    try self.context.save()
-                } catch {
-                    print(error.localizedDescription)
+                    guard self.backgroundContext.hasChanges else { return }
+                    try self.backgroundContext.save()
+                } catch let error as NSError {
+                    debugPrint(
+                        "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Bolus with error: \(error.userInfo)"
+                    )
                 }
             }
         }
@@ -421,11 +424,11 @@ extension Bolus {
 
         func deletePreset() {
             if selection != nil {
-                context.delete(selection!)
+                viewContext.delete(selection!)
 
                 do {
-                    guard context.hasChanges else { return }
-                    try context.save()
+                    guard viewContext.hasChanges else { return }
+                    try viewContext.save()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -473,9 +476,10 @@ extension Bolus {
             var protein_: Decimal = 0.0
             var presetArray = [MealPresetStored]()
 
-            context.performAndWait {
+            // TODO: purge Jons code
+            viewContext.performAndWait {
                 let requestPresets = MealPresetStored.fetchRequest() as NSFetchRequest<MealPresetStored>
-                try? presetArray = context.fetch(requestPresets)
+                try? presetArray = viewContext.fetch(requestPresets)
             }
             var waitersNotepad = [String]()
             var stringValue = ""
@@ -589,7 +593,8 @@ extension Bolus.StateModel {
     private func setupGlucoseArray() {
         Task {
             let ids = await self.fetchGlucose()
-            await updateGlucoseArray(with: ids)
+            let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateGlucoseArray(with: glucoseObjects)
         }
     }
 
@@ -608,24 +613,15 @@ extension Bolus.StateModel {
         }
     }
 
-    @MainActor private func updateGlucoseArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let glucoseObjects = try IDs.compactMap { id in
-                try context.existingObject(with: id) as? GlucoseStored
-            }
-            glucoseFromPersistence = glucoseObjects
+    @MainActor private func updateGlucoseArray(with objects: [GlucoseStored]) {
+        glucoseFromPersistence = objects
 
-            let lastGlucose = glucoseFromPersistence.first?.glucose ?? 0
-            let thirdLastGlucose = glucoseFromPersistence.dropFirst(2).first?.glucose ?? 0
-            let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose)
+        let lastGlucose = glucoseFromPersistence.first?.glucose ?? 0
+        let thirdLastGlucose = glucoseFromPersistence.dropFirst(2).first?.glucose ?? 0
+        let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose)
 
-            currentBG = Decimal(lastGlucose)
-            deltaBG = delta
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
-            )
-        }
+        currentBG = Decimal(lastGlucose)
+        deltaBG = delta
     }
 
     // Determinations
@@ -634,41 +630,63 @@ extension Bolus.StateModel {
             let ids = await determinationStorage.fetchLastDeterminationObjectID(
                 predicate: NSPredicate.predicateFor30MinAgoForDetermination
             )
-            await updateDeterminationsArray(with: ids)
-            await updateForecasts()
+            let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
+                .getNSManagedObject(with: ids, context: viewContext)
+
+            async let updateDetermination: () = updateDeterminationsArray(with: determinationObjects)
+            async let getCurrentBasal: () = getCurrentBasal()
+            async let updateForecasts: () = updateForecasts()
+
+            await updateForecasts
+            await getCurrentBasal
+            await updateDetermination
         }
     }
 
-    @MainActor private func updateDeterminationsArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let determinationObjects = try IDs.compactMap { id in
-                try context.existingObject(with: id) as? OrefDetermination
-            }
-            guard let mostRecentDetermination = determinationObjects.first else { return }
-            determination = determinationObjects
-
-            // setup vars for bolus calculation
-            insulinRequired = (mostRecentDetermination.insulinReq ?? 0) as Decimal
-            evBG = (mostRecentDetermination.eventualBG ?? 0) as Decimal
-            insulin = (mostRecentDetermination.insulinForManualBolus ?? 0) as Decimal
-            target = (mostRecentDetermination.currentTarget ?? 100) as Decimal
-            isf = (mostRecentDetermination.insulinSensitivity ?? 0) as Decimal
-            cob = mostRecentDetermination.cob as Int16
-            iob = (mostRecentDetermination.iob ?? 0) as Decimal
-            basal = (mostRecentDetermination.tempBasal ?? 0) as Decimal
-            carbRatio = (mostRecentDetermination.carbRatio ?? 0) as Decimal
-
-            getCurrentBasal()
-            insulinCalculated = calculateInsulin()
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the determinations array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateDeterminationsArray(with objects: [OrefDetermination]) {
+        guard let mostRecentDetermination = objects.first else { return }
+        determination = objects
+
+        // setup vars for bolus calculation
+        insulinRequired = (mostRecentDetermination.insulinReq ?? 0) as Decimal
+        evBG = (mostRecentDetermination.eventualBG ?? 0) as Decimal
+        insulin = (mostRecentDetermination.insulinForManualBolus ?? 0) as Decimal
+        target = (mostRecentDetermination.currentTarget ?? 100) as Decimal
+        isf = (mostRecentDetermination.insulinSensitivity ?? 0) as Decimal
+        cob = mostRecentDetermination.cob as Int16
+        iob = (mostRecentDetermination.iob ?? 0) as Decimal
+        basal = (mostRecentDetermination.tempBasal ?? 0) as Decimal
+        carbRatio = (mostRecentDetermination.carbRatio ?? 0) as Decimal
+        insulinCalculated = calculateInsulin()
     }
 }
 
 extension Bolus.StateModel {
+    func calculateForecasts(predictions: Predictions?) -> ([Int], [Int]) {
+        let iob: [Int] = predictions?.iob ?? []
+        let zt: [Int] = predictions?.zt ?? []
+        let cob: [Int] = predictions?.cob ?? []
+        let uam: [Int] = predictions?.uam ?? []
+
+        // Filter out the empty arrays and find the maximum length of the remaining arrays
+        let nonEmptyArrays: [[Int]] = [iob, zt, cob, uam].filter { !$0.isEmpty }
+        guard !nonEmptyArrays.isEmpty, let maxCount = nonEmptyArrays.map(\.count).max(), maxCount > 0 else {
+            return ([], [])
+        }
+
+        let minForecast = (0 ..< maxCount).map { index -> Int in
+            let valuesAtCurrentIndex = nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }
+            return valuesAtCurrentIndex.min() ?? 0
+        }
+
+        let maxForecast = (0 ..< maxCount).map { index -> Int in
+            let valuesAtCurrentIndex = nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }
+            return valuesAtCurrentIndex.max() ?? 0
+        }
+
+        return (minForecast, maxForecast)
+    }
+
     @MainActor func updateForecasts() async {
         simulatedDetermination = await apsManager.simulateDetermineBasal(carbs: carbs, iob: amount)
         predictionsForChart = simulatedDetermination?.predictions

+ 70 - 138
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -566,7 +566,8 @@ extension Home.StateModel {
     private func setupGlucoseArray() {
         Task {
             let ids = await self.fetchGlucose()
-            await updateGlucoseArray(with: ids)
+            let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateGlucoseArray(with: glucoseObjects)
         }
     }
 
@@ -585,24 +586,17 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateGlucoseArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let glucoseObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? GlucoseStored
-            }
-            glucoseFromPersistence = glucoseObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateGlucoseArray(with objects: [GlucoseStored]) {
+        glucoseFromPersistence = objects
     }
 
     // Setup Manual Glucose
     private func setupManualGlucoseArray() {
         Task {
             let ids = await self.fetchManualGlucose()
-            await updateManualGlucoseArray(with: ids)
+            let manualGlucoseObjects: [GlucoseStored] = await CoreDataStack.shared
+                .getNSManagedObject(with: ids, context: viewContext)
+            await updateManualGlucoseArray(with: manualGlucoseObjects)
         }
     }
 
@@ -621,24 +615,16 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateManualGlucoseArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let manualGlucoseObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? GlucoseStored
-            }
-            manualGlucoseFromPersistence = manualGlucoseObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the manual glucose array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateManualGlucoseArray(with objects: [GlucoseStored]) {
+        manualGlucoseFromPersistence = objects
     }
 
     // Setup Carbs
     private func setupCarbsArray() {
         Task {
             let ids = await self.fetchCarbs()
-            await updateCarbsArray(with: ids)
+            let carbObjects: [CarbEntryStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateCarbsArray(with: carbObjects)
         }
     }
 
@@ -656,24 +642,16 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateCarbsArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let carbObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? CarbEntryStored
-            }
-            carbsFromPersistence = carbObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the carbs array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateCarbsArray(with objects: [CarbEntryStored]) {
+        carbsFromPersistence = objects
     }
 
     // Setup FPUs
     private func setupFPUsArray() {
         Task {
             let ids = await self.fetchFPUs()
-            await updateFPUsArray(with: ids)
+            let fpuObjects: [CarbEntryStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateFPUsArray(with: fpuObjects)
         }
     }
 
@@ -691,17 +669,8 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateFPUsArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let fpuObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? CarbEntryStored
-            }
-            fpusFromPersistence = fpuObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the fpus array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateFPUsArray(with objects: [CarbEntryStored]) {
+        fpusFromPersistence = objects
     }
 
     // Custom fetch to more efficiently filter only for cob and iob
@@ -724,6 +693,7 @@ extension Home.StateModel {
     // Setup Determinations
     private func setupDeterminationsArray() {
         Task {
+            // Get the NSManagedObjectIDs
             async let enactedObjectIDs = determinationStorage
                 .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
             async let enactedAndNonEnactedObjectIDs = fetchCobAndIob()
@@ -731,14 +701,10 @@ extension Home.StateModel {
             let enactedIDs = await enactedObjectIDs
             let enactedAndNonEnactedIDs = await enactedAndNonEnactedObjectIDs
 
-            async let updateEnacted: () = updateDeterminationsArray(with: enactedIDs, keyPath: \.determinationsFromPersistence)
-            async let updateEnactedAndNonEnacted: () = updateDeterminationsArray(
-                with: enactedAndNonEnactedIDs,
-                keyPath: \.enactedAndNonEnactedDeterminations
-            )
+            // Get the NSManagedObjects and return them on the Main Thread
+            await updateDeterminationsArray(with: enactedIDs, keyPath: \.determinationsFromPersistence)
+            await updateDeterminationsArray(with: enactedAndNonEnactedIDs, keyPath: \.enactedAndNonEnactedDeterminations)
 
-            await updateEnacted
-            await updateEnactedAndNonEnacted
             await updateForecastData()
         }
     }
@@ -746,24 +712,21 @@ extension Home.StateModel {
     @MainActor private func updateDeterminationsArray(
         with IDs: [NSManagedObjectID],
         keyPath: ReferenceWritableKeyPath<Home.StateModel, [OrefDetermination]>
-    ) {
-        do {
-            let determinationObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? OrefDetermination
-            }
-            self[keyPath: keyPath] = determinationObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the determinations array: \(error.localizedDescription)"
-            )
-        }
+    ) async {
+        // Fetch the objects off the main thread
+        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
+            .getNSManagedObject(with: IDs, context: viewContext)
+
+        // Update the array on the main thread
+        self[keyPath: keyPath] = determinationObjects
     }
 
     // Setup Insulin
     private func setupInsulinArray() {
         Task {
             let ids = await self.fetchInsulin()
-            await updateInsulinArray(with: ids)
+            let insulinObjects: [PumpEventStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateInsulinArray(with: insulinObjects)
         }
     }
 
@@ -781,31 +744,21 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateInsulinArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let insulinObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? PumpEventStored
-            }
-            insulinFromPersistence = insulinObjects
-
-            // filter tempbasals
-            manualTempBasal = apsManager.isManualTempBasal
-            tempBasals = insulinFromPersistence.filter({ $0.tempBasal != nil })
-
-            // suspension and resume events
-            suspensions = insulinFromPersistence
-                .filter({ $0.type == EventType.pumpSuspend.rawValue || $0.type == EventType.pumpResume.rawValue })
-            let lastSuspension = suspensions.last
+    @MainActor private func updateInsulinArray(with insulinObjects: [PumpEventStored]) {
+        insulinFromPersistence = insulinObjects
 
-            pumpSuspended = tempBasals.last?.timestamp ?? Date() > lastSuspension?.timestamp ?? .distantPast && lastSuspension?
-                .type == EventType.pumpSuspend
-                .rawValue
+        // Filter tempbasals
+        manualTempBasal = apsManager.isManualTempBasal
+        tempBasals = insulinFromPersistence.filter({ $0.tempBasal != nil })
 
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the insulin array: \(error.localizedDescription)"
-            )
+        // Suspension and resume events
+        suspensions = insulinFromPersistence.filter {
+            $0.type == EventType.pumpSuspend.rawValue || $0.type == EventType.pumpResume.rawValue
         }
+        let lastSuspension = suspensions.last
+
+        pumpSuspended = tempBasals.last?.timestamp ?? Date() > lastSuspension?.timestamp ?? .distantPast && lastSuspension?
+            .type == EventType.pumpSuspend.rawValue
     }
 
     // Setup Last Bolus to display the bolus progress bar
@@ -846,7 +799,8 @@ extension Home.StateModel {
     private func setupBatteryArray() {
         Task {
             let ids = await self.fetchBattery()
-            await updateBatteryArray(with: ids)
+            let batteryObjects: [OpenAPS_Battery] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateBatteryArray(with: batteryObjects)
         }
     }
 
@@ -864,17 +818,8 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateBatteryArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let batteryObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? OpenAPS_Battery
-            }
-            batteryFromPersistence = batteryObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the battery array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateBatteryArray(with objects: [OpenAPS_Battery]) {
+        batteryFromPersistence = objects
     }
 }
 
@@ -883,7 +828,8 @@ extension Home.StateModel {
     private func setupOverrides() {
         Task {
             let ids = await self.fetchOverrides()
-            await updateOverrideArray(with: ids)
+            let overrideObjects: [OverrideStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
+            await updateOverrideArray(with: overrideObjects)
         }
     }
 
@@ -901,18 +847,8 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateOverrideArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let overrideObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? OverrideStored
-            }
-
-            overrides = overrideObjects
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the override array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateOverrideArray(with objects: [OverrideStored]) {
+        overrides = objects
     }
 
     @MainActor func calculateDuration(override: OverrideStored) -> TimeInterval {
@@ -933,7 +869,9 @@ extension Home.StateModel {
     private func setupOverrideRunStored() {
         Task {
             let ids = await self.fetchOverrideRunStored()
-            await updateOverrideRunStoredArray(with: ids)
+            let overrideRunObjects: [OverrideRunStored] = await CoreDataStack.shared
+                .getNSManagedObject(with: ids, context: viewContext)
+            await updateOverrideRunStoredArray(with: overrideRunObjects)
         }
     }
 
@@ -952,19 +890,8 @@ extension Home.StateModel {
         }
     }
 
-    @MainActor private func updateOverrideRunStoredArray(with IDs: [NSManagedObjectID]) {
-        do {
-            let overrideObjects = try IDs.compactMap { id in
-                try viewContext.existingObject(with: id) as? OverrideRunStored
-            }
-
-            overrideRunStored = overrideObjects
-            debugPrint("expiredOverrides: \(DebuggingIdentifiers.inProgress) \(overrideRunStored)")
-        } catch {
-            debugPrint(
-                "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the Override Run Stored array: \(error.localizedDescription)"
-            )
-        }
+    @MainActor private func updateOverrideRunStoredArray(with objects: [OverrideRunStored]) {
+        overrideRunStored = objects
     }
 
     @MainActor func saveToOverrideRunStored(withID id: NSManagedObjectID) async {
@@ -988,23 +915,26 @@ extension Home.StateModel {
     }
 }
 
-// MARK: Extension for Main Chart to draw Forecasts
-
 extension Home.StateModel {
     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))
+        await withTaskGroup(of: (UUID, NSManagedObjectID, [NSManagedObjectID]).self) { group in
+            for forecastID in forecastIDs {
+                group.addTask {
+                    let forecastValueIDs = await self.determinationStorage.getForecastValueIDs(for: forecastID, in: self.context)
+                    return (UUID(), forecastID, forecastValueIDs)
+                }
+            }
+
+            for await (uuid, forecastID, forecastValueIDs) in group {
+                result.append((id: uuid, forecastID: forecastID, forecastValueIDs: forecastValueIDs))
+            }
         }
 
         return result
@@ -1052,12 +982,14 @@ extension Home.StateModel {
         let maxCount = min(36, allForecastValues.map(\.count).max() ?? 0)
 
         minForecast = (0 ..< maxCount).map { index -> Int in
-            let valuesAtCurrentIndex = allForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }
+            let valuesAtCurrentIndex = allForecastValues
+                .compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }
             return valuesAtCurrentIndex.min() ?? 0
         }
 
         maxForecast = (0 ..< maxCount).map { index -> Int in
-            let valuesAtCurrentIndex = allForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }
+            let valuesAtCurrentIndex = allForecastValues
+                .compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }
             return valuesAtCurrentIndex.max() ?? 0
         }
     }

+ 20 - 0
Model/CoreDataStack.swift

@@ -399,6 +399,26 @@ extension CoreDataStack {
             }
         }
     }
+
+    // Get NSManagedObject
+    func getNSManagedObject<T: NSManagedObject>(
+        with ids: [NSManagedObjectID],
+        context: NSManagedObjectContext
+    ) async -> [T] {
+        await Task { () -> [T] in
+            var objects = [T]()
+            do {
+                for id in ids {
+                    if let object = try context.existingObject(with: id) as? T {
+                        objects.append(object)
+                    }
+                }
+            } catch {
+                debugPrint("Failed to fetch objects: \(error.localizedDescription)")
+            }
+            return objects
+        }.value
+    }
 }
 
 // MARK: - Save