|
@@ -56,74 +56,42 @@ class JSONImporter {
|
|
|
return try decoder.decode(T.self, from: data)
|
|
return try decoder.decode(T.self, from: data)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Retrieves the set of dates for all glucose values currently stored in CoreData.
|
|
|
|
|
|
|
+ /// Fetches a set of unique `Date` values for a specific `NSManagedObject` type from Core Data.
|
|
|
///
|
|
///
|
|
|
- /// - Parameters: the start and end dates to fetch glucose values, inclusive
|
|
|
|
|
- /// - Returns: A set of dates corresponding to existing glucose readings.
|
|
|
|
|
- /// - Throws: An error if the fetch operation fails.
|
|
|
|
|
- private func fetchGlucoseDates(start: Date, end: Date) async throws -> Set<Date> {
|
|
|
|
|
- let allReadings = try await coreDataStack.fetchEntitiesAsync(
|
|
|
|
|
- ofType: GlucoseStored.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: .predicateForDateBetween(start: start, end: end),
|
|
|
|
|
- key: "date",
|
|
|
|
|
- ascending: false
|
|
|
|
|
- ) as? [GlucoseStored] ?? []
|
|
|
|
|
-
|
|
|
|
|
- return Set(allReadings.compactMap(\.date))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Retrieves the set of timestamps for all pump events currently stored in CoreData.
|
|
|
|
|
|
|
+ /// This helper function is used to retrieve all existing date-like values (e.g., `date`, `timestamp`, `deliverAt`)
|
|
|
|
|
+ /// from a given entity type within a specified time range. It wraps the fetch and transformation
|
|
|
|
|
+ /// in a `context.perform` block to ensure thread safety when used on private background contexts.
|
|
|
///
|
|
///
|
|
|
- /// - Parameters: the start and end dates to fetch pump events, inclusive
|
|
|
|
|
- /// - Returns: A set of dates corresponding to existing pump events.
|
|
|
|
|
- /// - Throws: An error if the fetch operation fails.
|
|
|
|
|
- private func fetchPumpTimestamps(start: Date, end: Date) async throws -> Set<Date> {
|
|
|
|
|
- let allPumpEvents = try await coreDataStack.fetchEntitiesAsync(
|
|
|
|
|
- ofType: PumpEventStored.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: .predicateForTimestampBetween(start: start, end: end),
|
|
|
|
|
- key: "timestamp",
|
|
|
|
|
- ascending: false
|
|
|
|
|
- ) as? [PumpEventStored] ?? []
|
|
|
|
|
-
|
|
|
|
|
- return Set(allPumpEvents.compactMap(\.timestamp))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Retrieves the set of timestamps for all carb entries currently stored in CoreData.
|
|
|
|
|
|
|
+ /// - Parameters:
|
|
|
|
|
+ /// - type: The `NSManagedObject` subclass to fetch (e.g., `GlucoseStored.self`, `PumpEventStored.self`)
|
|
|
|
|
+ /// - predicate: A preconstructed predicate that filters the entity by date/timestamp range.
|
|
|
|
|
+ /// - sortKey: The string name of the date-like field used to sort the fetch results. **This must match the key used in Core Data.**
|
|
|
|
|
+ /// - dateKeyPath: A key path pointing to the `Date?` property on the entity used to extract the actual date value from each record.
|
|
|
///
|
|
///
|
|
|
- /// - Parameters: the start and end dates to fetch carb entries, inclusive
|
|
|
|
|
- /// - Returns: A set of dates corresponding to existing carb entries.
|
|
|
|
|
- /// - Throws: An error if the fetch operation fails.
|
|
|
|
|
- private func fetchCarbEntryDates(start: Date, end: Date) async throws -> Set<Date> {
|
|
|
|
|
- let allCarbEntryDates = try await coreDataStack.fetchEntitiesAsync(
|
|
|
|
|
- ofType: CarbEntryStored.self,
|
|
|
|
|
|
|
+ /// - Returns: A `Set<Date>` containing all non-nil date values from the fetched entities.
|
|
|
|
|
+ /// - Throws: `CoreDataError.fetchError` if casting the fetched objects fails, or if the fetch itself fails.
|
|
|
|
|
+
|
|
|
|
|
+ private func fetchDates<T: NSManagedObject>(
|
|
|
|
|
+ ofType type: T.Type,
|
|
|
|
|
+ predicate: NSPredicate,
|
|
|
|
|
+ sortKey: String,
|
|
|
|
|
+ dateKeyPath: KeyPath<T, Date?>
|
|
|
|
|
+ ) async throws -> Set<Date> {
|
|
|
|
|
+ let fetched = try await coreDataStack.fetchEntitiesAsync(
|
|
|
|
|
+ ofType: type,
|
|
|
onContext: context,
|
|
onContext: context,
|
|
|
- predicate: .predicateForDateBetween(start: start, end: end),
|
|
|
|
|
- key: "date",
|
|
|
|
|
|
|
+ predicate: predicate,
|
|
|
|
|
+ key: sortKey,
|
|
|
ascending: false
|
|
ascending: false
|
|
|
- ) as? [CarbEntryStored] ?? []
|
|
|
|
|
-
|
|
|
|
|
- return Set(allCarbEntryDates.compactMap(\.date))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- /// Retrieves the set of dates for all oref determinations currently stored in CoreData.
|
|
|
|
|
- ///
|
|
|
|
|
- /// - Parameters:
|
|
|
|
|
- /// - start: the start to fetch from; inclusive
|
|
|
|
|
- /// - end: the end date to fetch to; inclusive
|
|
|
|
|
- /// - Returns: A set of dates corresponding to existing determinations.
|
|
|
|
|
- /// - Throws: An error if the fetch operation fails.
|
|
|
|
|
- private func fetchDeterminationDates(start: Date, end: Date) async throws -> Set<Date> {
|
|
|
|
|
- let determinations = try await coreDataStack.fetchEntitiesAsync(
|
|
|
|
|
- ofType: OrefDetermination.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: .predicateForDeliverAtBetween(start: start, end: end),
|
|
|
|
|
- key: "deliverAt",
|
|
|
|
|
- ascending: false
|
|
|
|
|
- ) as? [OrefDetermination] ?? []
|
|
|
|
|
|
|
+ return try await context.perform {
|
|
|
|
|
+ guard let typed = fetched as? [T] else {
|
|
|
|
|
+ throw CoreDataError.fetchError(function: #function, file: #file)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return Set(determinations.compactMap(\.deliverAt))
|
|
|
|
|
|
|
+ return Set(typed.compactMap { $0[keyPath: dateKeyPath] })
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Imports glucose history from a JSON file into CoreData.
|
|
/// Imports glucose history from a JSON file into CoreData.
|
|
@@ -141,7 +109,12 @@ class JSONImporter {
|
|
|
func importGlucoseHistory(url: URL, now: Date) async throws {
|
|
func importGlucoseHistory(url: URL, now: Date) async throws {
|
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
|
let glucoseHistoryFull: [BloodGlucose] = try readJsonFile(url: url)
|
|
let glucoseHistoryFull: [BloodGlucose] = try readJsonFile(url: url)
|
|
|
- let existingDates = try await fetchGlucoseDates(start: twentyFourHoursAgo, end: now)
|
|
|
|
|
|
|
+ let existingDates = try await fetchDates(
|
|
|
|
|
+ ofType: GlucoseStored.self,
|
|
|
|
|
+ predicate: .predicateForDateBetween(start: twentyFourHoursAgo, end: now),
|
|
|
|
|
+ sortKey: "date",
|
|
|
|
|
+ dateKeyPath: \.date
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
// only import glucose values from the last 24 hours that don't exist
|
|
// only import glucose values from the last 24 hours that don't exist
|
|
|
let glucoseHistory = glucoseHistoryFull
|
|
let glucoseHistory = glucoseHistoryFull
|
|
@@ -232,7 +205,12 @@ class JSONImporter {
|
|
|
func importPumpHistory(url: URL, now: Date) async throws {
|
|
func importPumpHistory(url: URL, now: Date) async throws {
|
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
|
let pumpHistoryRaw: [PumpHistoryEvent] = try readJsonFile(url: url)
|
|
let pumpHistoryRaw: [PumpHistoryEvent] = try readJsonFile(url: url)
|
|
|
- let existingTimestamps = try await fetchPumpTimestamps(start: twentyFourHoursAgo, end: now)
|
|
|
|
|
|
|
+ let existingTimestamps = try await fetchDates(
|
|
|
|
|
+ ofType: PumpEventStored.self,
|
|
|
|
|
+ predicate: .predicateForTimestampBetween(start: twentyFourHoursAgo, end: now),
|
|
|
|
|
+ sortKey: "timestamp",
|
|
|
|
|
+ dateKeyPath: \.timestamp
|
|
|
|
|
+ )
|
|
|
let pumpHistoryFiltered = pumpHistoryRaw
|
|
let pumpHistoryFiltered = pumpHistoryRaw
|
|
|
.filter { $0.timestamp >= twentyFourHoursAgo && $0.timestamp <= now && !existingTimestamps.contains($0.timestamp) }
|
|
.filter { $0.timestamp >= twentyFourHoursAgo && $0.timestamp <= now && !existingTimestamps.contains($0.timestamp) }
|
|
|
|
|
|
|
@@ -272,7 +250,12 @@ class JSONImporter {
|
|
|
func importCarbHistory(url: URL, now: Date) async throws {
|
|
func importCarbHistory(url: URL, now: Date) async throws {
|
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
|
let carbHistoryFull: [CarbsEntry] = try readJsonFile(url: url)
|
|
let carbHistoryFull: [CarbsEntry] = try readJsonFile(url: url)
|
|
|
- let existingDates = try await fetchCarbEntryDates(start: twentyFourHoursAgo, end: now)
|
|
|
|
|
|
|
+ let existingDates = try await fetchDates(
|
|
|
|
|
+ ofType: CarbEntryStored.self,
|
|
|
|
|
+ predicate: .predicateForDateBetween(start: twentyFourHoursAgo, end: now),
|
|
|
|
|
+ sortKey: "date",
|
|
|
|
|
+ dateKeyPath: \.date
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
// Only import carb entries from the last 24 hours that do not exist yet in Core Data
|
|
// Only import carb entries from the last 24 hours that do not exist yet in Core Data
|
|
|
// Only import "true" carb entries; ignore all FPU entries (aka carb equivalents)
|
|
// Only import "true" carb entries; ignore all FPU entries (aka carb equivalents)
|
|
@@ -314,7 +297,12 @@ class JSONImporter {
|
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
let twentyFourHoursAgo = now - 24.hours.timeInterval
|
|
|
let enactedDetermination: Determination = try readJsonFile(url: enactedUrl)
|
|
let enactedDetermination: Determination = try readJsonFile(url: enactedUrl)
|
|
|
let suggestedDetermination: Determination = try readJsonFile(url: suggestedUrl)
|
|
let suggestedDetermination: Determination = try readJsonFile(url: suggestedUrl)
|
|
|
- let existingDates = try await fetchDeterminationDates(start: twentyFourHoursAgo, end: now)
|
|
|
|
|
|
|
+ let existingDates = try await fetchDates(
|
|
|
|
|
+ ofType: OrefDetermination.self,
|
|
|
|
|
+ predicate: .predicateForDeliverAtBetween(start: twentyFourHoursAgo, end: now),
|
|
|
|
|
+ sortKey: "deliverAt",
|
|
|
|
|
+ dateKeyPath: \.deliverAt
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
/// Helper function to check if entries are from within the last 24 hours that do not yet exist in Core Data
|
|
/// Helper function to check if entries are from within the last 24 hours that do not yet exist in Core Data
|
|
|
func checkDeterminationDate(_ date: Date) -> Bool {
|
|
func checkDeterminationDate(_ date: Date) -> Bool {
|