فهرست منبع

Merge pull request #240 from dnzxy/fix-fpu-conversion

Refactor and optimize `storeCarbs` function to limit carb equivalents of FPU conversion to 1.0 grams
bjornoleh 2 سال پیش
والد
کامیت
29eb46ea29
1فایلهای تغییر یافته به همراه116 افزوده شده و 71 حذف شده
  1. 116 71
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift

+ 116 - 71
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -27,93 +27,53 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         injectServices(resolver)
         injectServices(resolver)
     }
     }
 
 
+    /**
+     Processes and stores carbohydrate entries, including handling entries with fat and protein to calculate and distribute future carb equivalents.
+
+     - The function processes fat and protein units (FPUs) by creating carb equivalents for future use.
+     - Ensures each carb equivalent is at least 1.0 grams by adjusting the interval if necessary.
+     - Stores the actual carbohydrate entries.
+     - Saves the data to CoreData for statistical purposes.
+     - Notifies observers of the carbohydrate data update.
+
+     - Parameters:
+       - entries: An array of `CarbsEntry` objects representing the carbohydrate entries to be processed and stored.
+     */
     func storeCarbs(_ entries: [CarbsEntry]) {
     func storeCarbs(_ entries: [CarbsEntry]) {
         processQueue.sync {
         processQueue.sync {
             let file = OpenAPS.Monitor.carbHistory
             let file = OpenAPS.Monitor.carbHistory
-            var uniqEvents: [CarbsEntry] = []
-
-            let fat = entries.last?.fat ?? 0
-            let protein = entries.last?.protein ?? 0
-
-            if fat > 0 || protein > 0 {
-                // -------------------------- FPU--------------------------------------
-                let interval = settings.settings.minuteInterval // Interval betwwen carbs
-                let timeCap = settings.settings.timeCap // Max Duration
-                let adjustment = settings.settings.individualAdjustmentFactor
-                let delay = settings.settings.delay // Tme before first future carb entry
-                let kcal = protein * 4 + fat * 9
-                let carbEquivalents = (kcal / 10) * adjustment
-                let fpus = carbEquivalents / 10
-                // Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
-                var computedDuration = 0
-                switch fpus {
-                case ..<2:
-                    computedDuration = 3
-                case 2 ..< 3:
-                    computedDuration = 4
-                case 3 ..< 4:
-                    computedDuration = 5
-                default:
-                    computedDuration = timeCap
-                }
-                // Size of each created carb equivalent if 60 minutes interval
-                var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
-                // Adjust for interval setting other than 60 minutes
-                equivalent /= Decimal(60 / interval)
-                // Round to 1 fraction digit
-                // equivalent = Decimal(round(Double(equivalent * 10) / 10))
-                let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
-                equivalent = Decimal(roundedEquivalent)
-                // Number of equivalents
-                var numberOfEquivalents = carbEquivalents / equivalent
-                // Only use delay in first loop
-                var firstIndex = true
-                // New date for each carb equivalent
-                var useDate = entries.last?.createdAt ?? Date()
-                // Group and Identify all FPUs together
-                let fpuID = UUID().uuidString
-                // Create an array of all future carb equivalents.
-                var futureCarbArray = [CarbsEntry]()
-                while carbEquivalents > 0, numberOfEquivalents > 0 {
-                    if firstIndex {
-                        useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
-                        firstIndex = false
-                    } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
-
-                    let eachCarbEntry = CarbsEntry(
-                        id: UUID().uuidString, createdAt: useDate,
-                        carbs: equivalent, fat: 0, protein: 0, note: nil,
-                        enteredBy: CarbsEntry.manual, isFPU: true,
-                        fpuID: fpuID
-                    )
-                    futureCarbArray.append(eachCarbEntry)
-                    numberOfEquivalents -= 1
-                }
-                // Save the array
+            var entriesToStore: [CarbsEntry] = []
+
+            guard let lastEntry = entries.last else { return }
+
+            if let fat = lastEntry.fat, let protein = lastEntry.protein, fat > 0 || protein > 0 {
+                let (futureCarbArray, carbEquivalents) = processFPU(
+                    entries: entries,
+                    fat: fat,
+                    protein: protein,
+                    createdAt: lastEntry.createdAt
+                )
                 if carbEquivalents > 0 {
                 if carbEquivalents > 0 {
                     self.storage.transaction { storage in
                     self.storage.transaction { storage in
                         storage.append(futureCarbArray, to: file, uniqBy: \.id)
                         storage.append(futureCarbArray, to: file, uniqBy: \.id)
-                        uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                        entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
                             .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                             .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                             .sorted { $0.createdAt > $1.createdAt } ?? []
                             .sorted { $0.createdAt > $1.createdAt } ?? []
-                        storage.save(Array(uniqEvents), as: file)
+                        storage.save(Array(entriesToStore), as: file)
                     }
                     }
                 }
                 }
-            } // ------------------------- END OF TPU ----------------------------------------
-            // Store the actual (normal) carbs
-            if entries.last?.carbs ?? 0 > 0 {
-                uniqEvents = []
+            }
+
+            if lastEntry.carbs > 0 {
                 self.storage.transaction { storage in
                 self.storage.transaction { storage in
                     storage.append(entries, to: file, uniqBy: \.createdAt)
                     storage.append(entries, to: file, uniqBy: \.createdAt)
-                    uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                    entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
                         .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                         .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                         .sorted { $0.createdAt > $1.createdAt } ?? []
                         .sorted { $0.createdAt > $1.createdAt } ?? []
-                    storage.save(Array(uniqEvents), as: file)
+                    storage.save(Array(entriesToStore), as: file)
                 }
                 }
             }
             }
 
 
-            // MARK: Save to CoreData. TEST
-
             var cbs: Decimal = 0
             var cbs: Decimal = 0
             var carbDate = Date()
             var carbDate = Date()
             if entries.isNotEmpty {
             if entries.isNotEmpty {
@@ -131,11 +91,96 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 }
                 }
             }
             }
             broadcaster.notify(CarbsObserver.self, on: processQueue) {
             broadcaster.notify(CarbsObserver.self, on: processQueue) {
-                $0.carbsDidUpdate(uniqEvents)
+                $0.carbsDidUpdate(entriesToStore)
             }
             }
         }
         }
     }
     }
 
 
+    /**
+     Calculates the duration for processing FPUs (fat and protein units) based on the FPUs and the time cap.
+
+     - The function uses predefined rules to determine the duration based on the number of FPUs.
+     - Ensures that the duration does not exceed the time cap.
+
+     - Parameters:
+       - fpus: The number of FPUs calculated from fat and protein.
+       - timeCap: The maximum allowed duration.
+
+     - Returns: The computed duration in hours.
+     */
+    private func calculateComputedDuration(fpus: Decimal, timeCap: Int) -> Int {
+        switch fpus {
+        case ..<2:
+            return 3
+        case 2 ..< 3:
+            return 4
+        case 3 ..< 4:
+            return 5
+        default:
+            return timeCap
+        }
+    }
+
+    /**
+     Processes fat and protein entries to generate future carb equivalents, ensuring each equivalent is at least 1.0 grams.
+
+     - The function calculates the equivalent carb dosage size and adjusts the interval to ensure each equivalent is at least 1.0 grams.
+     - Creates future carb entries based on the adjusted carb equivalent size and interval.
+
+     - Parameters:
+       - entries: An array of `CarbsEntry` objects representing the carbohydrate entries to be processed.
+       - fat: The amount of fat in the last entry.
+       - protein: The amount of protein in the last entry.
+       - createdAt: The creation date of the last entry.
+
+     - Returns: A tuple containing the array of future carb entries and the total carb equivalents.
+     */
+    private func processFPU(entries _: [CarbsEntry], fat: Decimal, protein: Decimal, createdAt: Date) -> ([CarbsEntry], Decimal) {
+        let interval = settings.settings.minuteInterval
+        let timeCap = settings.settings.timeCap
+        let adjustment = settings.settings.individualAdjustmentFactor
+        let delay = settings.settings.delay
+
+        let kcal = protein * 4 + fat * 9
+        let carbEquivalents = (kcal / 10) * adjustment
+        let fpus = carbEquivalents / 10
+        var computedDuration = calculateComputedDuration(fpus: fpus, timeCap: timeCap)
+
+        var carbEquivalentSize: Decimal = carbEquivalents / Decimal(computedDuration)
+        carbEquivalentSize /= Decimal(60 / interval)
+
+        if carbEquivalentSize < 1.0 {
+            carbEquivalentSize = 1.0
+            computedDuration = Int(carbEquivalents / carbEquivalentSize)
+        }
+
+        let roundedEquivalent: Double = round(Double(carbEquivalentSize * 10)) / 10
+        carbEquivalentSize = Decimal(roundedEquivalent)
+        var numberOfEquivalents = carbEquivalents / carbEquivalentSize
+
+        var useDate = createdAt
+        let fpuID = UUID().uuidString
+        var futureCarbArray = [CarbsEntry]()
+        var firstIndex = true
+
+        while carbEquivalents > 0, numberOfEquivalents > 0 {
+            useDate = firstIndex ? useDate.addingTimeInterval(delay.minutes.timeInterval) : useDate
+                .addingTimeInterval(interval.minutes.timeInterval)
+            firstIndex = false
+
+            let eachCarbEntry = CarbsEntry(
+                id: UUID().uuidString, createdAt: useDate,
+                carbs: carbEquivalentSize, fat: 0, protein: 0, note: nil,
+                enteredBy: CarbsEntry.manual, isFPU: true,
+                fpuID: fpuID
+            )
+            futureCarbArray.append(eachCarbEntry)
+            numberOfEquivalents -= 1
+        }
+
+        return (futureCarbArray, carbEquivalents)
+    }
+
     func syncDate() -> Date {
     func syncDate() -> Date {
         Date().addingTimeInterval(-1.days.timeInterval)
         Date().addingTimeInterval(-1.days.timeInterval)
     }
     }