Procházet zdrojové kódy

Upload Glucose to Health

polscm32 aka Marvout před 1 rokem
rodič
revize
1278b6adc4

+ 9 - 15
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -238,7 +238,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
 
         guard filtered.isNotEmpty else {
-            // end of the BG tasks
+            // end of the Background tasks
             if let backgroundTask = backGroundFetchBGTaskID {
                 UIApplication.shared.endBackgroundTask(backgroundTask)
                 backGroundFetchBGTaskID = .invalid
@@ -265,24 +265,18 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
         deviceDataManager.heartbeat(date: Date())
 
+        // Upload to NS/Health/Tidepool
         Task.detached {
-            await self.nightscoutManager.uploadGlucose()
-            await self.tidepoolService.uploadGlucose(device: self.cgmManager?.cgmManagerStatus.device)
-        }
-
-        let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
+            async let uploadToNS: () = self.nightscoutManager.uploadGlucose()
+            async let uploadToHealth: () = self.healthKitManager.uploadGlucose()
+            async let uploadToTidepool: () = self.tidepoolService.uploadGlucose(device: self.cgmManager?.cgmManagerStatus.device)
 
-        guard glucoseForHealth.isNotEmpty else {
-            // end of the BG tasks
-            if let backgroundTask = backGroundFetchBGTaskID {
-                UIApplication.shared.endBackgroundTask(backgroundTask)
-                backGroundFetchBGTaskID = .invalid
-            }
-            return
+            await uploadToNS
+            await uploadToHealth
+            await uploadToTidepool
         }
-        healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth)
 
-        // end of the BG tasks
+        // End of the Background tasks
         if let backgroundTask = backGroundFetchBGTaskID {
             UIApplication.shared.endBackgroundTask(backgroundTask)
             backGroundFetchBGTaskID = .invalid

+ 81 - 2
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -15,6 +15,8 @@ protocol GlucoseStorage {
     func getGlucoseNotYetUploadedToNightscout() async -> [BloodGlucose]
     func getCGMStateNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func getManualGlucoseNotYetUploadedToNightscout() async -> [NightscoutTreatment]
+    func getGlucoseNotYetUploadedToHealth() async -> [BloodGlucose]
+    func getManualGlucoseNotYetUploadedToHealth() async -> [NightscoutTreatment]
     var alarm: GlucoseAlarm? { get }
 }
 
@@ -80,6 +82,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                         glucoseEntry.date = entry.dateString
                         glucoseEntry.direction = entry.direction?.rawValue
                         glucoseEntry.isUploadedToNS = false /// the value is not uploaded to NS (yet)
+                        glucoseEntry.isUploadedToHealth = false /// the value is not uploaded to Health (yet)
                         return false // Continue processing
                     }
                 )
@@ -235,7 +238,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     // Fetch glucose that is not uploaded to Nightscout yet
-    /// Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
+    /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
     func getGlucoseNotYetUploadedToNightscout() async -> [BloodGlucose] {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
@@ -266,7 +269,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     // Fetch manual glucose that is not uploaded to Nightscout yet
-    /// Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
+    /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
     func getManualGlucoseNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
@@ -320,6 +323,82 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return Array(Set(allValuesSet).subtracting(Set(alreadyUploadedValues)))
     }
 
+    // Fetch glucose that is not uploaded to Nightscout yet
+    /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
+    func getGlucoseNotYetUploadedToHealth() async -> [BloodGlucose] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: GlucoseStored.self,
+            onContext: coredataContext,
+            predicate: NSPredicate.glucoseNotYetUploadedToHealth,
+            key: "date",
+            ascending: false,
+            fetchLimit: 288
+        )
+
+        guard let fetchedResults = results as? [GlucoseStored] else { return [] }
+
+        return await coredataContext.perform {
+            return fetchedResults.map { result in
+                BloodGlucose(
+                    _id: result.id?.uuidString ?? UUID().uuidString,
+                    sgv: Int(result.glucose),
+                    direction: BloodGlucose.Direction(from: result.direction ?? ""),
+                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                    dateString: result.date ?? Date(),
+                    unfiltered: Decimal(result.glucose),
+                    filtered: Decimal(result.glucose),
+                    noise: nil,
+                    glucose: Int(result.glucose)
+                )
+            }
+        }
+    }
+
+    // Fetch manual glucose that is not uploaded to Nightscout yet
+    /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
+    func getManualGlucoseNotYetUploadedToHealth() async -> [NightscoutTreatment] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: GlucoseStored.self,
+            onContext: coredataContext,
+            predicate: NSPredicate.manualGlucoseNotYetUploadedToHealth,
+            key: "date",
+            ascending: false,
+            fetchLimit: 288
+        )
+
+        guard let fetchedResults = results as? [GlucoseStored] else { return [] }
+
+        return await coredataContext.perform {
+            return fetchedResults.map { result in
+                NightscoutTreatment(
+                    duration: nil,
+                    rawDuration: nil,
+                    rawRate: nil,
+                    absolute: nil,
+                    rate: nil,
+                    eventType: .capillaryGlucose,
+                    createdAt: result.date,
+                    enteredBy: "Trio",
+                    bolus: nil,
+                    insulin: nil,
+                    notes: "Trio User",
+                    carbs: nil,
+                    fat: nil,
+                    protein: nil,
+                    foodType: nil,
+                    targetTop: nil,
+                    targetBottom: nil,
+                    glucoseType: "Manual",
+                    glucose: self.settingsManager.settings
+                        .units == .mgdL ? (self.glucoseFormatter.string(from: Int(result.glucose) as NSNumber) ?? "")
+                        : (self.glucoseFormatter.string(from: Decimal(result.glucose).asMmolL as NSNumber) ?? ""),
+                    units: self.settingsManager.settings.units == .mmolL ? "mmol" : "mg/dl",
+                    id: result.id?.uuidString
+                )
+            }
+        }
+    }
+
     var alarm: GlucoseAlarm? {
         /// glucose can not be older than 20 minutes due to the predicate in the fetch request
         coredataContext.performAndWait {

+ 1 - 0
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -195,6 +195,7 @@ extension DataTable {
                 newItem.glucose = Int16(glucoseAsInt)
                 newItem.isManual = true
                 newItem.isUploadedToNS = false
+                newItem.isUploadedToHealth = false
 
                 do {
                     guard self.coredataContext.hasChanges else { return }

+ 17 - 11
FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift

@@ -26,20 +26,26 @@ extension AppleHealthKit {
                     return
                 }
 
-                self.healthKitManager.requestPermission { ok, error in
-                    DispatchQueue.main.async {
-                        self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG()
-                    }
+                Task {
+                    do {
+                        let permissionGranted = try await self.healthKitManager.requestPermission()
 
-                    guard ok, error == nil else {
-                        warning(.service, "Permission not granted for HealthKitManager", error: error)
-                        return
-                    }
+                        await MainActor.run {
+                            self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG()
+                        }
 
-                    debug(.service, "Permission  granted HealthKitManager")
+                        if permissionGranted {
+                            debug(.service, "Permission granted for HealthKitManager")
 
-                    self.healthKitManager.createBGObserver()
-                    self.healthKitManager.enableBackgroundDelivery()
+                            self.healthKitManager.createBGObserver()
+                            self.healthKitManager.enableBackgroundDelivery()
+
+                        } else {
+                            warning(.service, "Permission not granted for HealthKitManager")
+                        }
+                    } catch {
+                        warning(.service, "Error requesting permission for HealthKitManager", error: error)
+                    }
                 }
             }
         }

+ 7 - 2
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -324,10 +324,15 @@ extension NightscoutConfig {
             let glucose = await nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval))
 
             if glucose.isNotEmpty {
+                
                 await MainActor.run {
                     self.backfilling = false
-                    self.healthKitManager.saveIfNeeded(bloodGlucose: glucose)
-                    self.glucoseStorage.storeGlucose(glucose)
+                }
+                
+                self.glucoseStorage.storeGlucose(glucose)
+
+                Task.detached {
+                    await self.healthKitManager.uploadGlucose()
                 }
             } else {
                 await MainActor.run {

+ 190 - 44
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -1,4 +1,5 @@
 import Combine
+import CoreData
 import Foundation
 import HealthKit
 import LoopKit
@@ -12,9 +13,9 @@ protocol HealthKitManager: GlucoseSource {
     /// Check availability to save data of BG type to Health store
     func checkAvailabilitySaveBG() -> Bool
     /// Requests user to give permissions on using HealthKit
-    func requestPermission(completion: ((Bool, Error?) -> Void)?)
+    func requestPermission() async throws -> Bool
     /// Save blood glucose to Health store (dublicate of bg will ignore)
-    func saveIfNeeded(bloodGlucose: [BloodGlucose])
+    func uploadGlucose() async
     /// Save carbs to Health store (dublicate of bg will ignore)
     func saveIfNeeded(carbs: [CarbsEntry])
     /// Save Insulin to Health store
@@ -55,6 +56,8 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
     @Injected() private var broadcaster: Broadcaster!
     @Injected() var carbsStorage: CarbsStorage!
 
+    private var backgroundContext = CoreDataStack.shared.newTaskContext()
+
     private let processQueue = DispatchQueue(label: "BaseHealthKitManager.processQueue")
     private var lifetime = Lifetime()
 
@@ -126,75 +129,218 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
         Config.healthBGObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false
     }
 
-    func requestPermission(completion: ((Bool, Error?) -> Void)? = nil) {
+    func requestPermission() async throws -> Bool {
         guard isAvailableOnCurrentDevice else {
-            completion?(false, HKError.notAvailableOnCurrentDevice)
-            return
+            throw HKError.notAvailableOnCurrentDevice
         }
         guard Config.readPermissions.isNotEmpty, Config.writePermissions.isNotEmpty else {
-            completion?(false, HKError.dataNotAvailable)
-            return
+            throw HKError.dataNotAvailable
         }
 
-        healthKitStore.requestAuthorization(toShare: Config.writePermissions, read: Config.readPermissions) { status, error in
-            completion?(status, error)
+        return try await withCheckedThrowingContinuation { continuation in
+            healthKitStore.requestAuthorization(toShare: Config.writePermissions, read: Config.readPermissions) { status, error in
+                if let error = error {
+                    continuation.resume(throwing: error)
+                } else {
+                    continuation.resume(returning: status)
+                }
+            }
         }
     }
 
-    func saveIfNeeded(bloodGlucose: [BloodGlucose]) {
+    func uploadGlucose() async {
+        await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToHealth())
+        await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToHealth())
+    }
+
+    func uploadGlucose(_ glucose: [BloodGlucose]) async {
         guard settingsManager.settings.useAppleHealth,
               let sampleType = Config.healthBGObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType),
-              bloodGlucose.isNotEmpty
+              glucose.isNotEmpty
         else { return }
 
-        let bloodGlucoseToSave = filterSamplesToSave(bloodGlucose: bloodGlucose, sampleType: sampleType)
+        do {
+            // Create HealthKit samples from all the passed glucose values
+            let glucoseSamples = glucose.compactMap { glucoseSample -> HKQuantitySample? in
+                guard let glucoseValue = glucoseSample.glucose else { return nil }
+
+                return HKQuantitySample(
+                    type: sampleType,
+                    quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucoseValue)),
+                    start: glucoseSample.dateString,
+                    end: glucoseSample.dateString,
+                    metadata: [
+                        HKMetadataKeyExternalUUID: glucoseSample.id,
+                        HKMetadataKeySyncIdentifier: glucoseSample.id,
+                        HKMetadataKeySyncVersion: 1,
+                        Config.freeAPSMetaKey: true
+                    ]
+                )
+            }
 
-        guard bloodGlucoseToSave.isNotEmpty else {
-            debug(.service, "No new blood glucose samples to save.")
-            return
+            guard glucoseSamples.isNotEmpty else {
+                debug(.service, "No glucose samples available for upload.")
+                return
+            }
+
+            // Attempt to save the blood glucose samples to Apple Health
+            try await healthKitStore.save(glucoseSamples)
+            debug(.service, "Successfully stored \(glucoseSamples.count) blood glucose samples in HealthKit.")
+
+            // After successful upload, update the isUploadedToHealth flag in Core Data
+            await updateGlucoseAsUploaded(glucose)
+
+        } catch {
+            debug(.service, "Failed to upload glucose samples to HealthKit: \(error.localizedDescription)")
         }
+    }
 
-        save(samples: bloodGlucoseToSave, sampleType: sampleType)
+    func uploadManualGlucose(_ glucose: [NightscoutTreatment]) async {
+        guard settingsManager.settings.useAppleHealth,
+              let sampleType = Config.healthBGObject,
+              checkAvailabilitySave(objectTypeToHealthStore: sampleType),
+              glucose.isNotEmpty
+        else { return }
+
+        do {
+            // Create HealthKit samples from all the passed glucose values
+            let glucoseSamples = glucose.compactMap { glucoseSample -> HKQuantitySample? in
+                guard let glucoseValue = glucoseSample.glucose else { return nil }
+                guard let glucoseValueAsDouble = Double(glucoseValue) else { return nil }
+                guard let date = glucoseSample.dateString as? Date else { return nil }
+
+                return HKQuantitySample(
+                    type: sampleType,
+                    quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: glucoseValueAsDouble),
+                    start: date,
+                    end: date,
+                    metadata: [
+                        HKMetadataKeyExternalUUID: glucoseSample.id ?? UUID(),
+                        HKMetadataKeySyncIdentifier: glucoseSample.id ?? UUID(),
+                        HKMetadataKeySyncVersion: 1,
+                        Config.freeAPSMetaKey: true
+                    ]
+                )
+            }
+
+            guard glucoseSamples.isNotEmpty else {
+                debug(.service, "No glucose samples available for upload.")
+                return
+            }
+
+            // Attempt to save the blood glucose samples to Apple Health
+            try await healthKitStore.save(glucoseSamples)
+            debug(.service, "Successfully stored \(glucoseSamples.count) blood glucose samples in HealthKit.")
+
+            // After successful upload, update the isUploadedToHealth flag in Core Data
+            await updateManualGlucoseAsUploaded(glucose)
+
+        } catch {
+            debug(.service, "Failed to upload glucose samples to HealthKit: \(error.localizedDescription)")
+        }
     }
 
-    private func filterSamplesToSave(bloodGlucose: [BloodGlucose], sampleType: HKQuantityType) -> [HKQuantitySample] {
-        var samplesToSave: [HKQuantitySample] = []
+    private func updateGlucoseAsUploaded(_ glucose: [BloodGlucose]) async {
+        await backgroundContext.perform {
+            let ids = glucose.map(\.id) as NSArray
+            let fetchRequest: NSFetchRequest<GlucoseStored> = GlucoseStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", ids)
 
-        loadSamplesFromHealth(sampleType: sampleType, withIDs: bloodGlucose.map(\.id)) { existingSamples in
-            let existingSampleIDs = existingSamples.compactMap(\.syncIdentifier)
-            samplesToSave = bloodGlucose
-                .filter { !existingSampleIDs.contains($0.id) }
-                .compactMap { glucoseSample in
-                    guard let glucoseValue = glucoseSample.glucose else { return nil } 
-                    return HKQuantitySample(
-                        type: sampleType,
-                        quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucoseValue)),
-                        start: glucoseSample.dateString,
-                        end: glucoseSample.dateString,
-                        metadata: [
-                            HKMetadataKeyExternalUUID: glucoseSample.id,
-                            HKMetadataKeySyncIdentifier: glucoseSample.id,
-                            HKMetadataKeySyncVersion: 1,
-                            Config.freeAPSMetaKey: true
-                        ]
-                    )
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                for result in results {
+                    result.isUploadedToNS = true
                 }
-        }
 
-        return samplesToSave
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToHealth: \(error.userInfo)"
+                )
+            }
+        }
     }
 
-    private func save(samples: [HKQuantitySample], sampleType: HKQuantityType) {
-        healthKitStore.save(samples) { success, error in
-            if let error = error {
-                debug(.service, "Failed to store blood glucose in HealthKit Store: \(error.localizedDescription)")
-            } else if success {
-                debug(.service, "Successfully stored \(samples.count) blood glucose samples.")
+    private func updateManualGlucoseAsUploaded(_ glucose: [NightscoutTreatment]) async {
+        await backgroundContext.perform {
+            let ids = glucose.map(\.id) as NSArray
+            let fetchRequest: NSFetchRequest<GlucoseStored> = GlucoseStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", ids)
+
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                for result in results {
+                    result.isUploadedToNS = true
+                }
+
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToHealth: \(error.userInfo)"
+                )
             }
         }
     }
 
+//    func saveIfNeeded(bloodGlucose: [BloodGlucose]) {
+//        guard settingsManager.settings.useAppleHealth,
+//              let sampleType = Config.healthBGObject,
+//              checkAvailabilitySave(objectTypeToHealthStore: sampleType),
+//              bloodGlucose.isNotEmpty
+//        else { return }
+//
+//        Task {
+//            let bloodGlucoseToSave = await filterSamplesToSave(bloodGlucose: bloodGlucose, sampleType: sampleType)
+//
+//            guard bloodGlucoseToSave.isNotEmpty else {
+//                debug(.service, "No new blood glucose samples to save.")
+//                return
+//            }
+//
+//            await save(samples: bloodGlucoseToSave, sampleType: sampleType)
+//        }
+//    }
+//
+//    private func filterSamplesToSave(bloodGlucose: [BloodGlucose], sampleType: HKQuantityType) async -> [HKQuantitySample] {
+//        var samplesToSave: [HKQuantitySample] = []
+//
+//        loadSamplesFromHealth(sampleType: sampleType, withIDs: bloodGlucose.map(\.id)) { existingSamples in
+//            let existingSampleIDs = Set(existingSamples.compactMap(\.syncIdentifier))
+//            samplesToSave = bloodGlucose
+//                .filter { !existingSampleIDs.contains($0.id) }
+//                .compactMap { glucoseSample in
+//                    guard let glucoseValue = glucoseSample.glucose else { return nil }
+//
+//                    return HKQuantitySample(
+//                        type: sampleType,
+//                        quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucoseValue)),
+//                        start: glucoseSample.dateString,
+//                        end: glucoseSample.dateString,
+//                        metadata: [
+//                            HKMetadataKeyExternalUUID: glucoseSample.id,
+//                            HKMetadataKeySyncIdentifier: glucoseSample.id,
+//                            HKMetadataKeySyncVersion: 1,
+//                            Config.freeAPSMetaKey: true
+//                        ]
+//                    )
+//                }
+//        }
+//
+//        return samplesToSave
+//    }
+//
+//    private func save(samples: [HKQuantitySample], sampleType _: HKQuantityType) async {
+//        do {
+//            try await healthKitStore.save(samples)
+//            debug(.service, "Successfully stored \(samples.count) blood glucose samples.")
+//        } catch {
+//            debug(.service, "Failed to store blood glucose in HealthKit Store: \(error.localizedDescription)")
+//        }
+//    }
+
     func saveIfNeeded(carbs: [CarbsEntry]) {
         guard settingsManager.settings.useAppleHealth,
               let sampleType = Config.healthCarbObject,

+ 1 - 0
GlucoseStored+CoreDataProperties.swift

@@ -12,6 +12,7 @@ public extension GlucoseStored {
     @NSManaged var id: UUID?
     @NSManaged var isManual: Bool
     @NSManaged var isUploadedToNS: Bool
+    @NSManaged var isUploadedToHealth: Bool
 }
 
 extension GlucoseStored: Identifiable {}

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

@@ -70,6 +70,11 @@ extension NSPredicate {
         return NSPredicate(format: "date >= %@ AND isUploadedToNS == %@", date as NSDate, false as NSNumber)
     }
 
+    static var glucoseNotYetUploadedToHealth: NSPredicate {
+        let date = Date.oneDayAgo
+        return NSPredicate(format: "date >= %@ AND isUploadedToHealth == %@", date as NSDate, false as NSNumber)
+    }
+
     static var manualGlucoseNotYetUploadedToNightscout: NSPredicate {
         let date = Date.oneDayAgo
         return NSPredicate(
@@ -79,6 +84,16 @@ extension NSPredicate {
             true as NSNumber
         )
     }
+
+    static var manualGlucoseNotYetUploadedToHealth: NSPredicate {
+        let date = Date.oneDayAgo
+        return NSPredicate(
+            format: "date >= %@ AND isUploadedToHealth == %@ AND isManual == %@",
+            date as NSDate,
+            false as NSNumber,
+            true as NSNumber
+        )
+    }
 }
 
 extension GlucoseStored: Encodable {

+ 6 - 2
Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23G93" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23222.3" systemVersion="23G93" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
     <entity name="BolusStored" representedClassName="BolusStored" syncable="YES">
         <attribute name="amount" optional="YES" attributeType="Decimal" defaultValueString="0"/>
         <attribute name="isExternal" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
@@ -44,9 +44,10 @@
         <attribute name="glucose" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
         <attribute name="isManual" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
+        <attribute name="isUploadedToHealth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <attribute name="isUploadedToNS" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <fetchIndex name="byDate">
-            <fetchIndexElement property="date" type="Binary" order="descending"/>
+            <fetchIndexElement property="date" type="Binary" order="ascending"/>
         </fetchIndex>
         <fetchIndex name="byIsManual">
             <fetchIndexElement property="isManual" type="Binary" order="ascending"/>
@@ -54,6 +55,9 @@
         <fetchIndex name="byIsUploadedToNS">
             <fetchIndexElement property="isUploadedToNS" type="Binary" order="ascending"/>
         </fetchIndex>
+        <fetchIndex name="byIsUploadedToHealth">
+            <fetchIndexElement property="isUploadedToHealth" type="Binary" order="ascending"/>
+        </fetchIndex>
     </entity>
     <entity name="LoopStatRecord" representedClassName="LoopStatRecord" syncable="YES">
         <attribute name="duration" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>