Explorar o código

Aggregate TDD weighted average and prefetch carb entries

Two separate hotspots surfaced after the pump-path fixes:

TDDStorage.calculateWeightedAverage fetched every TDDStored row from
the last 10 days (~1000+ rows per determineBasal) only to sum totals
and count records. Replace the full-object load with two SUM + COUNT
aggregate fetches (recent 2h, historical 10d) via NSExpression, so
the query returns a single row instead of materializing the entire
table.

HomeStateModel setupCarbsArray / setupFPUsArray used the same
cross-context materialization pattern as the insulin and
determinations arrays. Add a SELF IN prefetch hop on viewContext
before getNSManagedObject so the per-ID existingObject lookups read
from the row cache instead of firing one Z_PK select per entry.
Marvin Polscheit hai 1 mes
pai
achega
0c700442b3

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

@@ -192,14 +192,14 @@ final class BaseAPSManager: APSManager, Injectable {
             .store(in: &lifetime)
 
         deviceDataManager.scheduledBasal
-            .receive(on: processQueue)
+            .receive(on: DispatchQueue.main)
             .sink { scheduledBasal in
                 self.isScheduledBasal = scheduledBasal
             }
             .store(in: &lifetime)
 
         deviceDataManager.suspended
-            .receive(on: processQueue)
+            .receive(on: DispatchQueue.main)
             .sink { suspended in
                 self.isSuspended = suspended
             }

+ 40 - 22
Trio/Sources/APS/Storage/TDDStorage.swift

@@ -567,41 +567,29 @@ final class BaseTDDStorage: TDDStorage, Injectable {
     /// - Returns: A weighted average of TDD as Decimal, or nil if insufficient data
     /// - Note: The weight percentage can be configured in preferences. Default is 0.65 (65% recent, 35% historical)
     private func calculateWeightedAverage() async throws -> Decimal? {
-        // Fetch data from Core Data
         let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
         let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
 
-        let predicate = NSPredicate(format: "date >= %@", tenDaysAgo as NSDate)
-
         let context = makeContext()
         context.name = "calculateWeightedAverage"
 
-        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: TDDStored.self,
-            onContext: context,
-            predicate: predicate,
-            key: "date",
-            ascending: false
-        )
-        return await context.perform { () -> Decimal? in
-            guard let results = results as? [TDDStored], !results.isEmpty else { return 0 }
+        return try await context.perform { () -> Decimal? in
+            let recent = try Self.aggregateTDD(from: twoHoursAgo, in: context)
+            let historical = try Self.aggregateTDD(from: tenDaysAgo, in: context)
 
-            // Calculate recent (2h) average
-            let recentResults = results.filter { $0.date?.timeIntervalSince(twoHoursAgo) ?? 0 > 0 }
-            let recentTotal = recentResults.compactMap { $0.total?.decimalValue }.reduce(0, +)
-            let recentCount = max(Decimal(recentResults.count), 1)
-            let averageTDDLastTwoHours = recentTotal / recentCount
+            // Extract into locals so SwiftFormat's isEmpty rule doesn't
+            // mis-rewrite the tuple member access into `!tuple.isEmpty`
+            let historicalCount = historical.count
+            let recentCount = recent.count
+            guard historicalCount > 0 else { return 0 }
 
-            // Calculate 10-day average
-            let totalTDD = results.compactMap { $0.total?.decimalValue }.reduce(0, +)
-            let totalCount = max(Decimal(results.count), 1)
-            let averageTDDLastTenDays = totalTDD / totalCount
+            let averageTDDLastTwoHours = recent.total / max(Decimal(recentCount), 1)
+            let averageTDDLastTenDays = historical.total / Decimal(historicalCount)
 
             // Get weight percentage from preferences (default 0.65 if not set)
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
             let weightPercentage = userPreferences?.weightPercentage ?? Decimal(0.65) // why is this 1 as default in trio-oref??
 
-            // Calculate weighted average using the formula:
             // weightedTDD = (weightPercentage × recent_average) + ((1 - weightPercentage) × historical_average)
             let weightedTDD = weightPercentage * averageTDDLastTwoHours +
                 (1 - weightPercentage) * averageTDDLastTenDays
@@ -610,6 +598,36 @@ final class BaseTDDStorage: TDDStorage, Injectable {
         }
     }
 
+    /// Runs a SUM(total) + COUNT aggregate on TDDStored for records with date >= `from`,
+    /// avoiding materializing hundreds of rows just to add them up.
+    private static func aggregateTDD(
+        from: Date,
+        in context: NSManagedObjectContext
+    ) throws -> (total: Decimal, count: Int) {
+        let request = NSFetchRequest<NSDictionary>(entityName: "TDDStored")
+        request.resultType = .dictionaryResultType
+        request.predicate = NSPredicate(format: "date >= %@ AND total != nil", from as NSDate)
+
+        let sumExp = NSExpressionDescription()
+        sumExp.name = "sumTotal"
+        sumExp.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "total")])
+        sumExp.expressionResultType = .decimalAttributeType
+
+        let countExp = NSExpressionDescription()
+        countExp.name = "countTotal"
+        countExp.expression = NSExpression(forFunction: "count:", arguments: [NSExpression(forKeyPath: "total")])
+        countExp.expressionResultType = .integer64AttributeType
+
+        request.propertiesToFetch = [sumExp, countExp]
+
+        guard let row = try context.fetch(request).first else {
+            return (0, 0)
+        }
+        let sum = (row["sumTotal"] as? NSDecimalNumber)?.decimalValue ?? 0
+        let count = (row["countTotal"] as? NSNumber)?.intValue ?? 0
+        return (sum, count)
+    }
+
     /// Checks if there is enough Total Daily Dose (TDD) data collected over the past 7 days.
     ///
     /// This function performs a count fetch for TDDStored records in Core Data where:

+ 24 - 0
Trio/Sources/Modules/Home/HomeStateModel+Setup/CarbSetup.swift

@@ -6,6 +6,18 @@ extension Home.StateModel {
         Task {
             do {
                 let ids = try await self.fetchCarbs()
+
+                // Prefetch into viewContext with one IN-query so the subsequent
+                // per-ID materialization avoids N+1 Z_PK selects.
+                if !ids.isEmpty {
+                    await viewContext.perform {
+                        let prefetchRequest = NSFetchRequest<CarbEntryStored>(entityName: "CarbEntryStored")
+                        prefetchRequest.predicate = NSPredicate(format: "SELF IN %@", ids)
+                        prefetchRequest.returnsObjectsAsFaults = false
+                        _ = try? self.viewContext.fetch(prefetchRequest)
+                    }
+                }
+
                 let carbObjects: [CarbEntryStored] = try await CoreDataStack.shared
                     .getNSManagedObject(with: ids, context: viewContext)
                 await updateCarbsArray(with: carbObjects)
@@ -45,6 +57,18 @@ extension Home.StateModel {
         Task {
             do {
                 let ids = try await self.fetchFPUs()
+
+                // Prefetch into viewContext with one IN-query so the subsequent
+                // per-ID materialization avoids N+1 Z_PK selects.
+                if !ids.isEmpty {
+                    await viewContext.perform {
+                        let prefetchRequest = NSFetchRequest<CarbEntryStored>(entityName: "CarbEntryStored")
+                        prefetchRequest.predicate = NSPredicate(format: "SELF IN %@", ids)
+                        prefetchRequest.returnsObjectsAsFaults = false
+                        _ = try? self.viewContext.fetch(prefetchRequest)
+                    }
+                }
+
                 let fpuObjects: [CarbEntryStored] = try await CoreDataStack.shared
                     .getNSManagedObject(with: ids, context: viewContext)
                 await updateFPUsArray(with: fpuObjects)