| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- import CoreData
- import Foundation
- import SwiftDate
- import Swinject
- protocol CarbsObserver {
- func carbsDidUpdate(_ carbs: [CarbsEntry])
- }
- protocol CarbsStorage {
- func storeCarbs(_ carbs: [CarbsEntry])
- func syncDate() -> Date
- func recent() -> [CarbsEntry]
- func nightscoutTretmentsNotUploaded() -> [NightscoutTreatment]
- func deleteCarbs(at date: Date)
- }
- final class BaseCarbsStorage: CarbsStorage, Injectable {
- private let processQueue = DispatchQueue(label: "BaseCarbsStorage.processQueue")
- @Injected() private var storage: FileStorage!
- @Injected() private var broadcaster: Broadcaster!
- @Injected() private var settings: SettingsManager!
- let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
- init(resolver: 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]) {
- processQueue.sync {
- let file = OpenAPS.Monitor.carbHistory
- 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 {
- self.storage.transaction { storage in
- storage.append(futureCarbArray, to: file, uniqBy: \.id)
- entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
- .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
- .sorted { $0.createdAt > $1.createdAt } ?? []
- storage.save(Array(entriesToStore), as: file)
- }
- }
- }
- if lastEntry.carbs > 0 {
- self.storage.transaction { storage in
- storage.append(entries, to: file, uniqBy: \.createdAt)
- entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
- .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
- .sorted { $0.createdAt > $1.createdAt } ?? []
- storage.save(Array(entriesToStore), as: file)
- }
- }
- var cbs: Decimal = 0
- var carbDate = Date()
- if entries.isNotEmpty {
- cbs = entries[0].carbs
- carbDate = entries[0].createdAt
- }
- if cbs != 0 {
- self.coredataContext.perform {
- let carbDataForStats = Carbohydrates(context: self.coredataContext)
- carbDataForStats.date = carbDate
- carbDataForStats.carbs = cbs as NSDecimalNumber
- try? self.coredataContext.save()
- }
- }
- broadcaster.notify(CarbsObserver.self, on: processQueue) {
- $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 {
- Date().addingTimeInterval(-1.days.timeInterval)
- }
- func recent() -> [CarbsEntry] {
- storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self)?.reversed() ?? []
- }
- func deleteCarbs(at date: Date) {
- processQueue.sync {
- var allValues = storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self) ?? []
- guard let entryIndex = allValues.firstIndex(where: { $0.createdAt == date }) else {
- return
- }
- // If deleteing a FPUs remove all of those with the same ID
- if allValues[entryIndex].isFPU != nil, allValues[entryIndex].isFPU ?? false {
- let fpuString = allValues[entryIndex].fpuID
- allValues.removeAll(where: { $0.fpuID == fpuString })
- storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
- broadcaster.notify(CarbsObserver.self, on: processQueue) {
- $0.carbsDidUpdate(allValues)
- }
- } else {
- allValues.remove(at: entryIndex)
- storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
- broadcaster.notify(CarbsObserver.self, on: processQueue) {
- $0.carbsDidUpdate(allValues)
- }
- }
- }
- }
- func nightscoutTretmentsNotUploaded() -> [NightscoutTreatment] {
- let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedPumphistory, as: [NightscoutTreatment].self) ?? []
- let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual }
- let treatments = eventsManual.map {
- NightscoutTreatment(
- duration: nil,
- rawDuration: nil,
- rawRate: nil,
- absolute: nil,
- rate: nil,
- eventType: .nsCarbCorrection,
- createdAt: $0.createdAt,
- enteredBy: CarbsEntry.manual,
- bolus: nil,
- insulin: nil,
- carbs: $0.carbs,
- fat: $0.fat,
- protein: $0.protein,
- foodType: $0.note,
- targetTop: nil,
- targetBottom: nil
- )
- }
- return Array(Set(treatments).subtracting(Set(uploaded)))
- }
- }
|