Просмотр исходного кода

Revert storePumpEvents in parts; refactor storeGlucose

polscm32 1 год назад
Родитель
Сommit
4ec6f167ab

+ 21 - 0
Model/Helper/GlucoseStored+helper.swift

@@ -27,6 +27,27 @@ extension GlucoseStored {
 
         return glucose.allSatisfy { $0.glucose == firstValue }
     }
+
+    // Preview
+    @discardableResult static func makePreviewGlucose(count: Int, provider: CoreDataStack) -> [GlucoseStored] {
+        let context = provider.persistentContainer.viewContext
+        let baseGlucose = 120
+        let glucoseValues = (0 ..< count).map { index -> GlucoseStored in
+            let glucose = GlucoseStored(context: context)
+            glucose.id = UUID()
+            glucose.date = Date.now.addingTimeInterval(Double(index) * -300) // Every 5 minutes
+            glucose.glucose = Int16(baseGlucose + (index % 3) * 10) // Varying between 120-140
+            glucose.direction = BloodGlucose.Direction.flat.rawValue
+            glucose.isManual = false
+            glucose.isUploadedToNS = false
+            glucose.isUploadedToHealth = false
+            glucose.isUploadedToTidepool = false
+            return glucose
+        }
+
+        try? context.save()
+        return glucoseValues
+    }
 }
 
 extension NSPredicate {

+ 16 - 6
Model/Helper/PumpEvent+helper.swift

@@ -18,14 +18,24 @@ extension PumpEventStored {
         let context = provider.persistentContainer.viewContext
         let events = (0 ..< count).map { index -> PumpEventStored in
             let event = PumpEventStored(context: context)
-            event.timestamp = Date.now.addingTimeInterval(Double(index) * 60)
-            event.type = "Mock Data"
-//            event.amount = 1.0
-//            event.isExternal = false
-//            event.isSMB = false
-//            event.duration = 0
+            event.id = UUID().uuidString
+            event.timestamp = Date.now.addingTimeInterval(Double(index) * -300) // Every 5 minutes
+            event.type = EventType.bolus.rawValue
+            event.isUploadedToNS = false
+            event.isUploadedToHealth = false
+            event.isUploadedToTidepool = false
+
+            // Add a bolus
+            let bolus = BolusStored(context: context)
+            bolus.amount = 2.5 as NSDecimalNumber
+            bolus.isExternal = false
+            bolus.isSMB = false
+            bolus.pumpEvent = event
+
             return event
         }
+
+        try? context.save()
         return events
     }
 }

+ 18 - 42
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -8,8 +8,6 @@ import Swinject
 import UIKit
 
 protocol FetchGlucoseManager: SourceInfoProvider {
-    func updateGlucoseStore(newBloodGlucose: [BloodGlucose])
-    func refreshCGM()
     func updateGlucoseSource(cgmGlucoseSourceType: CGMType, cgmGlucosePluginId: String, newManager: CGMManagerUI?)
     func deleteGlucoseSource()
     func removeCalibrations()
@@ -166,32 +164,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         return Manager.init(rawState: rawState)
     }
 
-    /// function called when a callback is fired by CGM BLE - no more used
-    public func updateGlucoseStore(newBloodGlucose: [BloodGlucose]) {
-        let syncDate = glucoseStorage.syncDate()
-        debug(.deviceManager, "CGM BLE FETCHGLUCOSE  : SyncDate is \(syncDate)")
-        glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: newBloodGlucose)
-    }
-
-    /// function to try to force the refresh of the CGM - generally provide by the pump heartbeat
-    public func refreshCGM() {
-        debug(.deviceManager, "refreshCGM by pump")
-
-        Publishers.CombineLatest(
-            Just(glucoseStorage.syncDate()),
-            glucoseSource.fetchIfNeeded()
-        )
-        .eraseToAnyPublisher()
-        .receive(on: processQueue)
-        .sink { syncDate, glucose in
-            debug(.nightscout, "refreshCGM FETCHGLUCOSE : SyncDate is \(syncDate)")
-            self.glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: glucose)
-        }
-        .store(in: &lifetime)
-    }
-
-    private func fetchGlucose() -> [GlucoseStored]? {
-        CoreDataStack.shared.fetchEntities(
+    private func fetchGlucose() async -> [GlucoseStored]? {
+        await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
             predicate: NSPredicate.predicateFor30MinAgo,
@@ -201,9 +175,9 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         ) as? [GlucoseStored]
     }
 
-    private func processGlucose() -> [BloodGlucose] {
-        context.performAndWait {
-            guard let results = fetchGlucose() else { return [] }
+    private func processGlucose() async -> [BloodGlucose] {
+        guard let results = await fetchGlucose() else { return [] }
+        return await context.perform {
             return results.map { result in
                 BloodGlucose(
                     sgv: Int(result.glucose),
@@ -220,7 +194,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         }
     }
 
-    private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose]) {
+    private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose]) async {
         // calibration add if required only for sensor
         let newGlucose = overcalibrate(entries: glucose)
 
@@ -229,7 +203,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
         // start background time extension
         var backGroundFetchBGTaskID: UIBackgroundTaskIdentifier?
-        backGroundFetchBGTaskID = UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
+        backGroundFetchBGTaskID = await UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
             guard let bg = backGroundFetchBGTaskID else { return }
             UIApplication.shared.endBackgroundTask(bg)
             backGroundFetchBGTaskID = .invalid
@@ -237,7 +211,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
         guard newGlucose.isNotEmpty else {
             if let backgroundTask = backGroundFetchBGTaskID {
-                UIApplication.shared.endBackgroundTask(backgroundTask)
+                await UIApplication.shared.endBackgroundTask(backgroundTask)
                 backGroundFetchBGTaskID = .invalid
             }
             return
@@ -249,7 +223,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         guard filtered.isNotEmpty else {
             // end of the Background tasks
             if let backgroundTask = backGroundFetchBGTaskID {
-                UIApplication.shared.endBackgroundTask(backgroundTask)
+                await UIApplication.shared.endBackgroundTask(backgroundTask)
                 backGroundFetchBGTaskID = .invalid
             }
             return
@@ -259,7 +233,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         // filter the data if it is the case
         if settingsManager.settings.smoothGlucose {
             // limited to 30 min of old glucose data
-            let oldGlucoseValues = processGlucose()
+            let oldGlucoseValues = await processGlucose()
 
             var smoothedValues = oldGlucoseValues + filtered
             // smooth with 3 repeats
@@ -270,13 +244,13 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
             filtered = smoothedValues.filter { $0.dateString > syncDate }
         }
 
-        glucoseStorage.storeGlucose(filtered)
+        await glucoseStorage.storeGlucose(filtered)
 
         deviceDataManager.heartbeat(date: Date())
 
         // End of the Background tasks
         if let backgroundTask = backGroundFetchBGTaskID {
-            UIApplication.shared.endBackgroundTask(backgroundTask)
+            await UIApplication.shared.endBackgroundTask(backgroundTask)
             backGroundFetchBGTaskID = .invalid
         }
     }
@@ -301,10 +275,12 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
                 )
                 .eraseToAnyPublisher()
                 .sink { newGlucose, syncDate in
-                    self.glucoseStoreAndHeartDecision(
-                        syncDate: syncDate,
-                        glucose: newGlucose
-                    )
+                    Task {
+                        await self.glucoseStoreAndHeartDecision(
+                            syncDate: syncDate,
+                            glucose: newGlucose
+                        )
+                    }
                 }
                 .store(in: &self.lifetime)
             }

+ 3 - 3
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -9,7 +9,7 @@ import Swinject
 
 protocol GlucoseStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
-    func storeGlucose(_ glucose: [BloodGlucose])
+    func storeGlucose(_ glucose: [BloodGlucose]) async
     func addManualGlucose(glucose: Int)
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool
     func syncDate() -> Date
@@ -60,8 +60,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return formatter
     }
 
-    func storeGlucose(_ glucose: [BloodGlucose]) {
-        coredataContext.perform {
+    func storeGlucose(_ glucose: [BloodGlucose]) async {
+        await coredataContext.perform {
             // Get new glucose values that don't exist yet
             let newGlucose = self.filterNewGlucoseValues(glucose)
             guard !newGlucose.isEmpty else { return }

+ 158 - 137
Trio/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -17,7 +17,6 @@ protocol PumpHistoryStorage {
     func getPumpHistoryNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func getPumpHistoryNotYetUploadedToHealth() async -> [PumpHistoryEvent]
     func getPumpHistoryNotYetUploadedToTidepool() async -> [PumpHistoryEvent]
-    func deleteInsulin(at date: Date)
 }
 
 final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
@@ -49,7 +48,164 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
     func storePumpEvents(_ events: [NewPumpEvent]) async {
         await context.perform {
             for event in events {
-                self.createPumpEvent(event)
+                let existingEvents: [PumpEventStored] = CoreDataStack.shared.fetchEntities(
+                    ofType: PumpEventStored.self,
+                    onContext: self.context,
+                    predicate: NSPredicate.duplicateInLastHour(event.date),
+                    key: "timestamp",
+                    ascending: false,
+                    batchSize: 50
+                ) as? [PumpEventStored] ?? []
+
+                switch event.type {
+                case .bolus:
+
+                    guard let dose = event.dose else { continue }
+                    let amount = self.roundDose(
+                        dose.unitsInDeliverableIncrements,
+                        toIncrement: Double(self.settings.preferences.bolusIncrement)
+                    )
+
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+
+                        if let existingEvent = existingEvents.first(where: { $0.type == EventType.bolus.rawValue }) {
+                            if existingEvent.timestamp == event.date {
+                                if let existingAmount = existingEvent.bolus?.amount, amount < existingAmount as Decimal {
+                                    // Update existing event with new smaller value
+                                    existingEvent.bolus?.amount = amount as NSDecimalNumber
+                                    existingEvent.bolus?.isSMB = dose.automatic ?? true
+                                    existingEvent.isUploadedToNS = false
+                                    existingEvent.isUploadedToHealth = false
+                                    existingEvent.isUploadedToTidepool = false
+
+                                    print("Updated existing event with smaller value: \(amount)")
+                                }
+                            }
+                        }
+                        continue
+                    }
+
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.bolus.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                    let newBolusEntry = BolusStored(context: self.context)
+                    newBolusEntry.pumpEvent = newPumpEvent
+                    newBolusEntry.amount = NSDecimalNumber(decimal: amount)
+                    newBolusEntry.isExternal = dose.manuallyEntered
+                    newBolusEntry.isSMB = dose.automatic ?? true
+
+                case .tempBasal:
+                    guard let dose = event.dose else { continue }
+
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+
+                    let rate = Decimal(dose.unitsPerHour)
+                    let minutes = (dose.endDate - dose.startDate).timeInterval / 60
+                    let delivered = dose.deliveredUnits
+                    let date = event.date
+
+                    let isCancel = delivered != nil
+                    guard !isCancel else { continue }
+
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = date
+                    newPumpEvent.type = PumpEvent.tempBasal.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                    let newTempBasal = TempBasalStored(context: self.context)
+                    newTempBasal.pumpEvent = newPumpEvent
+                    newTempBasal.duration = Int16(round(minutes))
+                    newTempBasal.rate = rate as NSDecimalNumber
+                    newTempBasal.tempType = TempType.absolute.rawValue
+
+                case .suspend:
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                case .resume:
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.pumpResume.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                case .rewind:
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.rewind.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                case .prime:
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.prime.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+
+                case .alarm:
+                    guard existingEvents.isEmpty else {
+                        // Duplicate found, do not store the event
+                        print("Duplicate event found with timestamp: \(event.date)")
+                        continue
+                    }
+                    let newPumpEvent = PumpEventStored(context: self.context)
+                    newPumpEvent.id = UUID().uuidString
+                    newPumpEvent.timestamp = event.date
+                    newPumpEvent.type = PumpEvent.pumpAlarm.rawValue
+                    newPumpEvent.isUploadedToNS = false
+                    newPumpEvent.isUploadedToHealth = false
+                    newPumpEvent.isUploadedToTidepool = false
+                    newPumpEvent.note = event.title
+
+                default:
+                    continue
+                }
             }
 
             do {
@@ -64,127 +220,6 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         }
     }
 
-    private func createPumpEvent(_ event: NewPumpEvent) {
-        // Check for duplicates
-        let existingEvents = findExistingEvents(for: event.date)
-
-        // for bolus events, update if there is a duplicate
-        if event.type == .bolus {
-            if handleExistingBolusEvent(event, existingEvents: existingEvents) {
-                return
-            }
-            // for all other events, do not save if there is a duplicate
-        } else if !existingEvents.isEmpty {
-            return
-        }
-
-        // If there is no duplicate or no bolus update necessary, create new event
-        let pumpEvent = PumpEventStored(context: context)
-        pumpEvent.id = UUID().uuidString
-        pumpEvent.timestamp = event.date
-        pumpEvent.isUploadedToNS = false
-        pumpEvent.isUploadedToHealth = false
-        pumpEvent.isUploadedToTidepool = false
-
-        switch event.type {
-        case .bolus:
-            createBolusEvent(event, pumpEvent: pumpEvent)
-            pumpEvent.type = PumpEvent.bolus.rawValue
-        case .tempBasal:
-            createTempBasalEvent(event, pumpEvent: pumpEvent)
-            pumpEvent.type = PumpEvent.tempBasal.rawValue
-        case .suspend:
-            pumpEvent.type = PumpEvent.pumpSuspend.rawValue
-        case .resume:
-            pumpEvent.type = PumpEvent.pumpResume.rawValue
-        case .rewind:
-            pumpEvent.type = PumpEvent.rewind.rawValue
-        case .prime:
-            pumpEvent.type = PumpEvent.prime.rawValue
-        case .alarm:
-            pumpEvent.type = PumpEvent.pumpAlarm.rawValue
-            pumpEvent.note = event.title
-        default:
-            return
-        }
-    }
-
-    private func findExistingEvents(for date: Date) -> [PumpEventStored] {
-        CoreDataStack.shared.fetchEntities(
-            ofType: PumpEventStored.self,
-            onContext: context,
-            predicate: NSPredicate.duplicateInLastHour(date),
-            key: "timestamp",
-            ascending: false,
-            batchSize: 50
-        ) as? [PumpEventStored] ?? []
-    }
-
-    private func handleExistingBolusEvent(_ event: NewPumpEvent, existingEvents: [PumpEventStored]) -> Bool {
-        guard let dose = event.dose,
-              let existingEvent = existingEvents.first(where: { $0.type == EventType.bolus.rawValue })
-        else {
-            return false
-        }
-
-        let amount = roundDose(
-            dose.unitsInDeliverableIncrements,
-            toIncrement: Double(settings.preferences.bolusIncrement)
-        )
-
-        guard let existingAmount = existingEvent.bolus?.amount else {
-            return false
-        }
-
-        // if the amount is the same, do not save again
-        if amount == existingAmount as Decimal {
-            return true
-        }
-
-        // if the new amount is smaller, update the existing event
-        if amount < existingAmount as Decimal {
-            existingEvent.bolus?.amount = amount as NSDecimalNumber
-            existingEvent.bolus?.isSMB = dose.automatic ?? true
-            existingEvent.isUploadedToNS = false
-            existingEvent.isUploadedToHealth = false
-            existingEvent.isUploadedToTidepool = false
-            return true
-        }
-
-        return false
-    }
-
-    private func createTempBasalEvent(_ event: NewPumpEvent, pumpEvent: PumpEventStored) {
-        guard let dose = event.dose else { return }
-
-        let rate = Decimal(dose.unitsPerHour)
-        let minutes = (dose.endDate - dose.startDate).timeInterval / 60
-
-        let isCancel = dose.deliveredUnits != nil
-        guard !isCancel else { return }
-
-        let tempBasal = TempBasalStored(context: context)
-        tempBasal.rate = rate as NSDecimalNumber
-        tempBasal.duration = Int16(round(minutes))
-        tempBasal.tempType = TempType.absolute.rawValue
-        tempBasal.pumpEvent = pumpEvent
-    }
-
-    private func createBolusEvent(_ event: NewPumpEvent, pumpEvent: PumpEventStored) {
-        guard let dose = event.dose else { return }
-
-        let amount = roundDose(
-            dose.unitsInDeliverableIncrements,
-            toIncrement: Double(settings.preferences.bolusIncrement)
-        )
-
-        let bolus = BolusStored(context: context)
-        bolus.amount = NSDecimalNumber(decimal: amount)
-        bolus.isExternal = dose.manuallyEntered
-        bolus.isSMB = dose.automatic ?? true
-        bolus.pumpEvent = pumpEvent
-    }
-
     func storeExternalInsulinEvent(amount: Decimal, timestamp: Date) async {
         debug(.default, "External insulin saved")
         await context.perform {
@@ -219,20 +254,6 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self)?.reversed() ?? []
     }
 
-    func deleteInsulin(at date: Date) {
-        processQueue.sync {
-            var allValues = storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self) ?? []
-            guard let entryIndex = allValues.firstIndex(where: { $0.timestamp == date }) else {
-                return
-            }
-            allValues.remove(at: entryIndex)
-            storage.save(allValues, as: OpenAPS.Monitor.pumpHistory)
-            broadcaster.notify(PumpHistoryObserver.self, on: processQueue) {
-                $0.pumpHistoryDidUpdate(allValues)
-            }
-        }
-    }
-
     func determineBolusEventType(for event: PumpEventStored) -> PumpEventStored.EventType {
         if event.bolus!.isSMB {
             return .smb

+ 22 - 0
Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift

@@ -76,3 +76,25 @@ struct GlucoseChartView: ChartContent {
         }
     }
 }
+
+#Preview {
+    let previewStack = CoreDataStack.preview
+    return NavigationView {
+        VStack {
+            Chart {
+                GlucoseChartView(
+                    glucoseData: GlucoseStored.makePreviewGlucose(count: 24, provider: previewStack),
+                    units: .mgdL,
+                    highGlucose: 180,
+                    lowGlucose: 70,
+                    currentGlucoseTarget: 100,
+                    isSmoothingEnabled: false,
+                    glucoseColorScheme: .dynamicColor
+                )
+            }
+            .frame(height: 200)
+            .padding()
+        }
+        .navigationTitle("Glucose Chart")
+    }
+}

+ 1 - 1
Trio/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -348,7 +348,7 @@ extension NightscoutConfig {
                     self.backfilling = false
                 }
 
-                glucoseStorage.storeGlucose(glucose)
+                await glucoseStorage.storeGlucose(glucose)
 
                 Task.detached {
                     await self.healthKitManager.uploadGlucose()