DeterminationStorage.swift 8.1 KB

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