Sfoglia il codice sorgente

Fix temp basal calculation for Tidepool upload WIP

Deniz Cengiz 1 anno fa
parent
commit
0a996153c1

+ 20 - 6
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -131,6 +131,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.tempBasal.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
 
                         let newTempBasal = TempBasalStored(context: self.context)
                         newTempBasal.pumpEvent = newPumpEvent
@@ -150,6 +151,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
 
                     case .resume:
                         guard existingEvents.isEmpty else {
@@ -163,6 +165,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.pumpResume.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
 
                     case .rewind:
                         guard existingEvents.isEmpty else {
@@ -176,6 +179,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.rewind.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
 
                     case .prime:
                         guard existingEvents.isEmpty else {
@@ -189,6 +193,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.prime.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
 
                     case .alarm:
                         guard existingEvents.isEmpty else {
@@ -202,6 +207,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.pumpAlarm.rawValue
                         newPumpEvent.isUploadedToNS = false
                         newPumpEvent.isUploadedToHealth = false
+                        newPumpEvent.isUploadedToTidepool = false
                         newPumpEvent.note = event.title
 
                     default:
@@ -500,12 +506,20 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         isExternal: event.bolus?.isExternal ?? false
                     )
                 case PumpEvent.tempBasal.rawValue:
-                    return PumpHistoryEvent(
-                        id: event.id ?? UUID().uuidString,
-                        type: .tempBasal,
-                        timestamp: event.timestamp ?? Date(),
-                        amount: event.tempBasal?.rate as Decimal?
-                    )
+                    if let id = event.id, let timestamp = event.timestamp, let tempBasal = event.tempBasal,
+                       let tempBasalRate = tempBasal.rate
+                    {
+                        return PumpHistoryEvent(
+                            id: id,
+                            type: .tempBasal,
+                            timestamp: timestamp,
+                            amount: tempBasalRate as Decimal,
+                            duration: Int(tempBasal.duration)
+                        )
+                    } else {
+                        return nil
+                    }
+
                 default:
                     return nil
                 }

+ 96 - 104
FreeAPS/Sources/Services/Network/TidepoolManager.swift

@@ -25,6 +25,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
     @Injected() private var carbsStorage: CarbsStorage!
     @Injected() private var storage: FileStorage!
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
+    @Injected() private var apsManager: APSManager!
 
     private let processQueue = DispatchQueue(label: "BaseNetworkManager.processQueue")
     private var tidepoolService: RemoteDataService? {
@@ -58,8 +59,10 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
     init(resolver: Resolver) {
         injectServices(resolver)
         loadTidepoolManager()
+
         pumpHistoryStorage.delegate = self
         carbsStorage.delegate = self
+
         subscribe()
     }
 
@@ -155,7 +158,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                 try self.backgroundContext.save()
             } catch let error as NSError {
                 debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToHealth: \(error.userInfo)"
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToTidepool: \(error.userInfo)"
                 )
             }
         }
@@ -201,102 +204,85 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
     func uploadDose(_ events: [PumpHistoryEvent]) {
         guard !events.isEmpty, let tidepoolService = self.tidepoolService else { return }
 
-        let eventsBasal = events.filter { $0.type == .tempBasal || $0.type == .tempBasalDuration }
-            .sorted { $0.timestamp < $1.timestamp }
-
-//        let doseDataBasal: [DoseEntry] = eventsBasal.reduce([]) { result, event in
-//            var result = result
-//            switch event.type {
-//            case .tempBasal:
-//                // update the previous tempBasal with endtime = starttime of the last event
-//                if let last: DoseEntry = result.popLast() {
-//                    let value = max(
-//                        0,
-//                        Double(event.timestamp.timeIntervalSince1970 - last.startDate.timeIntervalSince1970) / 3600
-//                    ) *
-//                        (last.scheduledBasalRate?.doubleValue(for: .internationalUnitsPerHour) ?? 0.0)
-//                    result.append(DoseEntry(
-//                        type: .tempBasal,
-//                        startDate: last.startDate,
-//                        endDate: event.timestamp,
-//                        value: value,
-//                        unit: last.unit,
-//                        deliveredUnits: value,
-//                        syncIdentifier: last.syncIdentifier,
-//                        insulinType: last.insulinType,
-//                        automatic: last.automatic,
-//                        manuallyEntered: last.manuallyEntered
-//                    ))
-//                }
-//                result.append(DoseEntry(
-//                    type: .tempBasal,
-//                    startDate: event.timestamp,
-//                    value: 0.0,
-//                    unit: .units,
-//                    syncIdentifier: event.id,
-//                    scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: Double(event.amount!)),
-//                    insulinType: nil,
-//                    automatic: true,
-//                    manuallyEntered: false,
-//                    isMutable: true
-//                ))
-//            case .tempBasalDuration:
-//                if let last: DoseEntry = result.popLast(),
-//                   last.type == .tempBasal,
-//                   last.startDate == event.timestamp
-//                {
-//                    let durationMin = event.durationMin ?? 0
-//                    // result.append(last)
-//                    let value = (Double(durationMin) / 60.0) *
-//                        (last.scheduledBasalRate?.doubleValue(for: .internationalUnitsPerHour) ?? 0.0)
-//                    result.append(DoseEntry(
-//                        type: .tempBasal,
-//                        startDate: last.startDate,
-//                        endDate: Calendar.current.date(byAdding: .minute, value: durationMin, to: last.startDate) ?? last
-//                            .startDate,
-//                        value: value,
-//                        unit: last.unit,
-//                        deliveredUnits: value,
-//                        syncIdentifier: last.syncIdentifier,
-//                        scheduledBasalRate: last.scheduledBasalRate,
-//                        insulinType: last.insulinType,
-//                        automatic: last.automatic,
-//                        manuallyEntered: last.manuallyEntered
-//                    ))
-//                }
-//            default: break
-//            }
-//            return result
-//        }
-
-        let tempBasals: [DoseEntry] = events.compactMap { event -> DoseEntry? in
+        let tempBasalEventCount = events.filter { $0.type == .tempBasal }.count
+
+        // Fetch existing entries with an additional +1 count to get the previous entry as well -> need to update
+        var existingTempBasalEntries: [PumpEventStored] = CoreDataStack.shared.fetchEntities(
+            ofType: PumpEventStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.pumpHistoryLast24h,
+            key: "timestamp",
+            ascending: false,
+            fetchLimit: tempBasalEventCount + 1,
+            batchSize: 50
+        ).filter { $0.tempBasal != nil }
+
+        // remove first fetched existing entry, so new events and old events are off by 1 index, so the same index is always current event in events and the previous event to that one in existing events array.
+        existingTempBasalEntries.removeFirst()
+
+        let insulinDoseEvents: [DoseEntry] = events.reduce([]) { result, event in
+            var result = result
             switch event.type {
             case .tempBasal:
-                return DoseEntry(
-                    type: .tempBasal,
-                    startDate: event.timestamp,
-                    endDate: event.timestamp
-                        .addingTimeInterval(TimeInterval(minutes: Double(event.duration ?? 0))),
-                    value: 0.0,
-                    unit: .units,
-                    syncIdentifier: event.id,
-                    scheduledBasalRate: HKQuantity(
-                        unit: .internationalUnitsPerHour,
-                        doubleValue: Double(event.rate!)
-                    ),
-                    insulinType: nil,
-                    automatic: true,
-                    manuallyEntered: false,
-                    isMutable: true
-                )
-            default: return nil
-            }
-        }
+                if let duration = event.duration, let amount = event.amount {
+                    let value = (Decimal(duration) / 60.0) * amount
+
+                    // Create updated previous entry, if applicable -> update end date
+                    if let lastEntry = existingTempBasalEntries.first, let lastTimeStamp = lastEntry.timestamp {
+                        let lastEndDate = event.timestamp
+                        let lastDuration = lastEndDate.timeIntervalSince(lastTimeStamp) / 3600
+                        let lastDeliveredUnits = Double(lastDuration / 60.0) *
+                            Double(truncating: lastEntry.tempBasal?.rate ?? 0.0)
+
+                        let updatedLastEntry = DoseEntry(
+                            type: .tempBasal,
+                            startDate: lastTimeStamp,
+                            endDate: lastEndDate,
+                            value: lastDeliveredUnits,
+                            unit: .units,
+                            deliveredUnits: lastDeliveredUnits,
+                            syncIdentifier: lastEntry.id ?? UUID().uuidString,
+                            scheduledBasalRate: HKQuantity(
+                                unit: .internationalUnitsPerHour,
+                                doubleValue: Double(truncating: lastEntry.tempBasal?.rate ?? 0.0)
+                            ),
+                            insulinType: apsManager.pumpManager?.status.insulinType ?? nil,
+                            automatic: true,
+                            manuallyEntered: false,
+                            isMutable: false
+                        )
+                        result.append(updatedLastEntry)
+                    }
+
+                    // Create new entry for current event
+                    let newDoseEntry = DoseEntry(
+                        type: .tempBasal,
+                        startDate: event.timestamp,
+                        endDate: event.timestamp.addingTimeInterval(TimeInterval(minutes: Double(duration))),
+                        value: Double(value),
+                        unit: .units,
+                        deliveredUnits: Double(value),
+                        syncIdentifier: event.id,
+                        scheduledBasalRate: HKQuantity(
+                            unit: .internationalUnitsPerHour,
+                            doubleValue: Double(amount)
+                        ),
+                        insulinType: apsManager.pumpManager?.status.insulinType ?? nil,
+                        automatic: true,
+                        manuallyEntered: false,
+                        isMutable: false
+                    )
+
+                    result.append(newDoseEntry)
+
+                    // Remove the first element from existingTempBasalEntries so that it does not get processed again
+                    if existingTempBasalEntries.isNotEmpty {
+                        existingTempBasalEntries.removeFirst()
+                    }
+                }
 
-        let boluses: [DoseEntry] = events.compactMap { event -> DoseEntry? in
-            switch event.type {
             case .bolus:
-                return DoseEntry(
+                let bolusDoseEntry = DoseEntry(
                     type: .bolus,
                     startDate: event.timestamp,
                     endDate: event.timestamp,
@@ -305,14 +291,22 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                     deliveredUnits: nil,
                     syncIdentifier: event.id,
                     scheduledBasalRate: nil,
-                    insulinType: nil,
+                    insulinType: apsManager.pumpManager?.status.insulinType ?? nil,
                     automatic: event.isSMB ?? true,
                     manuallyEntered: event.isExternal ?? false
                 )
-            default: return nil
+
+                result.append(bolusDoseEntry)
+
+            default:
+                break
             }
+
+            return result
         }
 
+        debug(.service, "TIDEPOOL DOSE ENTRIES: \(insulinDoseEvents)")
+
         let pumpEvents: [PersistedPumpEvent] = events.compactMap { event -> PersistedPumpEvent? in
             if let pumpEventType = event.type.mapEventTypeToPumpEventType() {
                 let dose: DoseEntry? = switch pumpEventType {
@@ -340,7 +334,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
         }
 
         processQueue.async {
-            tidepoolService.uploadDoseData(created: tempBasals + boluses, deleted: []) { result in
+            tidepoolService.uploadDoseData(created: insulinDoseEvents, deleted: []) { result in
                 switch result {
                 case let .failure(error):
                     debug(.nightscout, "Error synchronizing Dose data: \(String(describing: error))")
@@ -348,10 +342,9 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                     debug(.nightscout, "Success synchronizing Dose data")
                     // After successful upload, update the isUploadedToTidepool flag in Core Data
                     Task {
-                        let insulinEvents = events
-                            .filter {
-                                $0.type == .tempBasal || $0.type == .tempBasalDuration || $0.type == .bolus
-                            }
+                        let insulinEvents = events.filter {
+                            $0.type == .tempBasal || $0.type == .tempBasalDuration || $0.type == .bolus
+                        }
                         await self.updateInsulinAsUploaded(insulinEvents)
                     }
                 }
@@ -365,8 +358,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                     debug(.nightscout, "Success synchronizing Pump Event data")
                     // After successful upload, update the isUploadedToTidepool flag in Core Data
                     Task {
-                        let pumpEventType = events.map({ $0.type.mapEventTypeToPumpEventType()
-                        })
+                        let pumpEventType = events.map { $0.type.mapEventTypeToPumpEventType() }
                         let pumpEvents = events.filter { _ in pumpEventType.contains(pumpEventType) }
 
                         await self.updateInsulinAsUploaded(pumpEvents)
@@ -392,7 +384,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                 try self.backgroundContext.save()
             } catch let error as NSError {
                 debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToHealth: \(error.userInfo)"
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToTidepool: \(error.userInfo)"
                 )
             }
         }
@@ -469,7 +461,7 @@ final class BaseTidepoolManager: TidepoolManager, Injectable, CarbsStoredDelegat
                 try self.backgroundContext.save()
             } catch let error as NSError {
                 debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToHealth: \(error.userInfo)"
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToTidepool: \(error.userInfo)"
                 )
             }
         }