DeterminationStorage.swift 8.1 KB

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