DeterminationStorage.swift 7.6 KB

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