|
@@ -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)
|
|
|
}
|
|
}
|