DeterminationStorage.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import Combine
  2. import CoreData
  3. import Foundation
  4. import Swinject
  5. protocol DeterminationStorage {
  6. func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID]
  7. func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
  8. func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
  9. func fetchForecastObjects(
  10. for data: (id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID]),
  11. in context: NSManagedObjectContext
  12. ) async -> (UUID, Forecast?, [ForecastValue])
  13. func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination?
  14. }
  15. final class BaseDeterminationStorage: DeterminationStorage, Injectable {
  16. private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  17. private let backgroundContext = CoreDataStack.shared.newTaskContext()
  18. init(resolver: Resolver) {
  19. injectServices(resolver)
  20. }
  21. func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID] {
  22. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  23. ofType: OrefDetermination.self,
  24. onContext: backgroundContext,
  25. predicate: predicate,
  26. key: "deliverAt",
  27. ascending: false,
  28. fetchLimit: 1
  29. )
  30. return await backgroundContext.perform {
  31. guard let fetchedResults = results as? [OrefDetermination] else { return [] }
  32. return fetchedResults.map(\.objectID)
  33. }
  34. }
  35. func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID] {
  36. await context.perform {
  37. do {
  38. guard let determination = try context.existingObject(with: determinationID) as? OrefDetermination,
  39. let forecastSet = determination.forecasts
  40. else {
  41. return []
  42. }
  43. let forecasts = Array(forecastSet)
  44. return forecasts.map(\.objectID) as [NSManagedObjectID]
  45. } catch {
  46. debugPrint(
  47. "Failed \(DebuggingIdentifiers.failed) to fetch Forecast IDs for OrefDetermination with ID \(determinationID): \(error.localizedDescription)"
  48. )
  49. return []
  50. }
  51. }
  52. }
  53. func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID] {
  54. await context.perform {
  55. do {
  56. guard let forecast = try context.existingObject(with: forecastID) as? Forecast,
  57. let forecastValueSet = forecast.forecastValues
  58. else {
  59. return []
  60. }
  61. let forecastValues = forecastValueSet.sorted(by: { $0.index < $1.index })
  62. return forecastValues.map(\.objectID)
  63. } catch {
  64. debugPrint(
  65. "Failed \(DebuggingIdentifiers.failed) to fetch Forecast Value IDs with ID \(forecastID): \(error.localizedDescription)"
  66. )
  67. return []
  68. }
  69. }
  70. }
  71. // Fetch forecast value IDs for a given data set
  72. func fetchForecastObjects(
  73. for data: (id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID]),
  74. in context: NSManagedObjectContext
  75. ) async -> (UUID, Forecast?, [ForecastValue]) {
  76. var forecast: Forecast?
  77. var forecastValues: [ForecastValue] = []
  78. do {
  79. try await context.perform {
  80. // Fetch the forecast object
  81. forecast = try context.existingObject(with: data.forecastID) as? Forecast
  82. // Fetch the first 3h of forecast values
  83. for forecastValueID in data.forecastValueIDs.prefix(36) {
  84. if let forecastValue = try context.existingObject(with: forecastValueID) as? ForecastValue {
  85. forecastValues.append(forecastValue)
  86. }
  87. }
  88. }
  89. } catch {
  90. debugPrint(
  91. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch forecast Values with error: \(error.localizedDescription)"
  92. )
  93. }
  94. return (data.id, forecast, forecastValues)
  95. }
  96. // Convert NSDecimalNumber to Decimal
  97. func decimal(from nsDecimalNumber: NSDecimalNumber?) -> Decimal {
  98. nsDecimalNumber?.decimalValue ?? 0.0
  99. }
  100. // Convert NSSet to array of Ints for Predictions
  101. func parseForecastValues(ofType type: String, from determinationID: NSManagedObjectID) async -> [Int]? {
  102. let forecastIDs = await getForecastIDs(for: determinationID, in: backgroundContext)
  103. var forecastValuesList: [Int] = []
  104. for forecastID in forecastIDs {
  105. await backgroundContext.perform {
  106. if let forecast = try? self.backgroundContext.existingObject(with: forecastID) as? Forecast {
  107. // Filter the forecast based on the type
  108. if forecast.type == type {
  109. let forecastValueIDs = forecast.forecastValues?.sorted(by: { $0.index < $1.index }).map(\.objectID) ?? []
  110. for forecastValueID in forecastValueIDs {
  111. if let forecastValue = try? self.backgroundContext
  112. .existingObject(with: forecastValueID) as? ForecastValue
  113. {
  114. let forecastValueInt = Int(forecastValue.value)
  115. forecastValuesList.append(forecastValueInt)
  116. }
  117. }
  118. }
  119. }
  120. }
  121. }
  122. return forecastValuesList.isEmpty ? nil : forecastValuesList
  123. }
  124. func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination? {
  125. var result: Determination?
  126. guard let determinationId = determinationIds.first else {
  127. return nil
  128. }
  129. let predictions = Predictions(
  130. iob: await parseForecastValues(ofType: "iob", from: determinationId),
  131. zt: await parseForecastValues(ofType: "zt", from: determinationId),
  132. cob: await parseForecastValues(ofType: "cob", from: determinationId),
  133. uam: await parseForecastValues(ofType: "uam", from: determinationId)
  134. )
  135. return await backgroundContext.perform {
  136. do {
  137. let orefDetermination = try self.backgroundContext.existingObject(with: determinationId) as? OrefDetermination
  138. // Check if the fetched object is of the expected type
  139. if let orefDetermination = orefDetermination {
  140. result = Determination(
  141. id: orefDetermination.id ?? UUID(),
  142. reason: orefDetermination.reason ?? "",
  143. units: orefDetermination.smbToDeliver as Decimal?,
  144. insulinReq: self.decimal(from: orefDetermination.insulinReq),
  145. eventualBG: orefDetermination.eventualBG as? Int,
  146. sensitivityRatio: self.decimal(from: orefDetermination.sensitivityRatio),
  147. rate: self.decimal(from: orefDetermination.rate),
  148. duration: self.decimal(from: orefDetermination.duration),
  149. iob: self.decimal(from: orefDetermination.iob),
  150. cob: orefDetermination.cob != 0 ? Decimal(orefDetermination.cob) : nil,
  151. predictions: predictions,
  152. deliverAt: orefDetermination.deliverAt,
  153. carbsReq: orefDetermination.carbsRequired != 0 ? Decimal(orefDetermination.carbsRequired) : nil,
  154. temp: TempType(rawValue: orefDetermination.temp ?? "absolute"),
  155. bg: self.decimal(from: orefDetermination.glucose),
  156. reservoir: self.decimal(from: orefDetermination.reservoir),
  157. isf: self.decimal(from: orefDetermination.insulinSensitivity),
  158. timestamp: orefDetermination.timestamp,
  159. tdd: self.decimal(from: orefDetermination.totalDailyDose),
  160. insulin: nil,
  161. current_target: self.decimal(from: orefDetermination.currentTarget),
  162. insulinForManualBolus: self.decimal(from: orefDetermination.insulinForManualBolus),
  163. manualBolusErrorString: self.decimal(from: orefDetermination.manualBolusErrorString),
  164. minDelta: self.decimal(from: orefDetermination.minDelta),
  165. expectedDelta: self.decimal(from: orefDetermination.expectedDelta),
  166. minGuardBG: nil,
  167. minPredBG: nil,
  168. threshold: self.decimal(from: orefDetermination.threshold),
  169. carbRatio: self.decimal(from: orefDetermination.carbRatio),
  170. received: orefDetermination.enacted // this is actually part of NS...
  171. )
  172. }
  173. } catch {
  174. debugPrint(
  175. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch managed object with error: \(error.localizedDescription)"
  176. )
  177. }
  178. return result
  179. }
  180. }
  181. }