DataManager.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import CoreData
  2. import Foundation
  3. // Fetch Data for Glucose and Determination from Core Data and map them to the Structs in order to pass them thread safe to the glucoseDidUpdate/ pushUpdate function
  4. @available(iOS 16.2, *)
  5. extension LiveActivityManager {
  6. func fetchAndMapGlucose() async throws -> [GlucoseData] {
  7. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  8. ofType: GlucoseStored.self,
  9. onContext: context,
  10. predicate: NSPredicate.predicateForSixHoursAgo,
  11. key: "date",
  12. ascending: false
  13. )
  14. return try await context.perform {
  15. guard let glucoseResults = results as? [GlucoseStored] else {
  16. throw CoreDataError.fetchError(function: #function, file: #file)
  17. }
  18. return glucoseResults.map {
  19. GlucoseData(glucose: Int($0.glucose), date: $0.date ?? Date(), direction: $0.directionEnum)
  20. }
  21. }
  22. }
  23. // TODO: extract logic or at least rename function appropiately
  24. func fetchAndMapDetermination() async throws -> DeterminationData? {
  25. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  26. ofType: OrefDetermination.self,
  27. onContext: context,
  28. predicate: NSPredicate.predicateFor30MinAgoForDetermination,
  29. key: "deliverAt",
  30. ascending: false,
  31. fetchLimit: 1,
  32. relationshipKeyPathsForPrefetching: ["forecasts", "forecasts.forecastValues"]
  33. )
  34. let tddResults = try await CoreDataStack.shared.fetchEntitiesAsync(
  35. ofType: TDDStored.self,
  36. onContext: context,
  37. predicate: NSPredicate.predicateFor30MinAgo,
  38. key: "date",
  39. ascending: false,
  40. fetchLimit: 1,
  41. propertiesToFetch: ["total"]
  42. )
  43. return try await context.perform {
  44. guard let determinationResults = results as? [OrefDetermination],
  45. let tddResults = tddResults as? [[String: Any]]
  46. else {
  47. throw CoreDataError.fetchError(function: #function, file: #file)
  48. }
  49. guard let determination = determinationResults.first else {
  50. return nil
  51. }
  52. let tddValue = (tddResults.first?["total"] as? NSDecimalNumber)?.decimalValue ?? 0
  53. // Compute cone bounds and per-type lines from forecast relationships (cap at 24 values = 2h)
  54. var allForecastValues = [[Int]]()
  55. var forecastLines = [(type: String, values: [Int])]()
  56. if let forecasts = determination.forecasts {
  57. for forecast in forecasts.sorted(by: { ($0.type ?? "") < ($1.type ?? "") }) {
  58. let values = forecast.forecastValuesArray.prefix(24).map { Int($0.value) }
  59. guard !values.isEmpty else { continue }
  60. allForecastValues.append(Array(values))
  61. if let type = forecast.type {
  62. forecastLines.append((type: type, values: Array(values)))
  63. }
  64. }
  65. }
  66. let minCount = allForecastValues.map(\.count).min() ?? 0
  67. var minForecast = [Int]()
  68. var maxForecast = [Int]()
  69. for index in 0 ..< minCount {
  70. let col = allForecastValues.compactMap { $0.indices.contains(index) ? $0[index] : nil }
  71. minForecast.append(col.min() ?? 0)
  72. maxForecast.append(col.max() ?? 0)
  73. }
  74. return DeterminationData(
  75. cob: Int(determination.cob),
  76. tdd: tddValue,
  77. target: determination.currentTarget?.decimalValue ?? 0,
  78. date: determination.deliverAt,
  79. minForecast: minForecast,
  80. maxForecast: maxForecast,
  81. forecastLines: forecastLines
  82. )
  83. }
  84. }
  85. func fetchAndMapTempTarget() async throws -> TempTargetData? {
  86. try await fetchAndMapLatest(
  87. ofType: TempTargetStored.self,
  88. predicate: .predicateForOneDayAgo,
  89. key: "date",
  90. propertiesToFetch: ["enabled", "name", "target", "date", "duration"]
  91. ) { row in
  92. TempTargetData(
  93. isActive: row["enabled"] as? Bool ?? false,
  94. tempTargetName: row["name"] as? String ?? "Temp Target",
  95. date: row["date"] as? Date ?? Date(),
  96. duration: row["duration"] as? Decimal ?? 0,
  97. target: row["target"] as? Decimal ?? 0
  98. )
  99. }
  100. }
  101. func fetchAndMapOverride() async throws -> OverrideData? {
  102. try await fetchAndMapLatest(
  103. ofType: OverrideStored.self,
  104. predicate: .predicateForOneDayAgo,
  105. key: "date",
  106. propertiesToFetch: ["enabled", "name", "target", "date", "duration"]
  107. ) { row in
  108. OverrideData(
  109. isActive: row["enabled"] as? Bool ?? false,
  110. overrideName: row["name"] as? String ?? "Override",
  111. date: row["date"] as? Date ?? Date(),
  112. duration: row["duration"] as? Decimal ?? 0,
  113. target: row["target"] as? Decimal ?? 0
  114. )
  115. }
  116. }
  117. private func fetchAndMapLatest<Entity: NSManagedObject, Output>(
  118. ofType type: Entity.Type,
  119. predicate: NSPredicate,
  120. key: String,
  121. propertiesToFetch: [String],
  122. map: @escaping ([String: Any]) -> Output
  123. ) async throws -> Output? {
  124. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  125. ofType: type,
  126. onContext: context,
  127. predicate: predicate,
  128. key: key,
  129. ascending: false,
  130. fetchLimit: 1,
  131. propertiesToFetch: propertiesToFetch
  132. )
  133. return try await context.perform {
  134. guard let rows = results as? [[String: Any]] else {
  135. throw CoreDataError.fetchError(function: #function, file: #file)
  136. }
  137. return rows.first.map(map)
  138. }
  139. }
  140. }