DeterminationStorage.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 _: 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. let forecastValueIDs = await getForecastValueIDs(for: forecastID, in: backgroundContext)
  76. await backgroundContext.perform {
  77. for forecastValueID in forecastValueIDs {
  78. if let forecastValue = try? self.backgroundContext.existingObject(with: forecastValueID) as? ForecastValue {
  79. let forecastValueInt = Int(forecastValue.value)
  80. forecastValuesList.append(forecastValueInt)
  81. }
  82. }
  83. }
  84. }
  85. return forecastValuesList
  86. }
  87. func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination? {
  88. var result: Determination?
  89. guard let determinationId = determinationIds.first else {
  90. return nil
  91. }
  92. let predictions = Predictions(
  93. iob: await parseForecastValues(ofType: "iob", from: determinationId),
  94. zt: await parseForecastValues(ofType: "zt", from: determinationId),
  95. cob: await parseForecastValues(ofType: "cob", from: determinationId),
  96. uam: await parseForecastValues(ofType: "uam", from: determinationId)
  97. )
  98. return await backgroundContext.perform {
  99. do {
  100. let orefDetermination = try self.backgroundContext.existingObject(with: determinationId) as? OrefDetermination
  101. // Check if the fetched object is of the expected type
  102. if let orefDetermination = orefDetermination {
  103. let forecastSet = orefDetermination.forecasts
  104. result = Determination(
  105. id: orefDetermination.id ?? UUID(),
  106. reason: orefDetermination.reason ?? "",
  107. units: orefDetermination.smbToDeliver as Decimal?,
  108. insulinReq: self.decimal(from: orefDetermination.insulinReq),
  109. eventualBG: orefDetermination.eventualBG as? Int,
  110. sensitivityRatio: self.decimal(from: orefDetermination.sensitivityRatio),
  111. rate: self.decimal(from: orefDetermination.rate),
  112. duration: self.decimal(from: orefDetermination.duration),
  113. iob: self.decimal(from: orefDetermination.iob),
  114. cob: orefDetermination.cob != 0 ? Decimal(orefDetermination.cob) : nil,
  115. predictions: predictions,
  116. deliverAt: orefDetermination.deliverAt,
  117. carbsReq: orefDetermination.carbsRequired != 0 ? Decimal(orefDetermination.carbsRequired) : nil,
  118. temp: TempType(rawValue: orefDetermination.temp ?? "absolute"),
  119. bg: self.decimal(from: orefDetermination.glucose),
  120. reservoir: self.decimal(from: orefDetermination.reservoir),
  121. isf: self.decimal(from: orefDetermination.insulinSensitivity),
  122. timestamp: orefDetermination.timestamp,
  123. tdd: self.decimal(from: orefDetermination.totalDailyDose),
  124. insulin: nil,
  125. current_target: self.decimal(from: orefDetermination.currentTarget),
  126. insulinForManualBolus: self.decimal(from: orefDetermination.insulinForManualBolus),
  127. manualBolusErrorString: self.decimal(from: orefDetermination.manualBolusErrorString),
  128. minDelta: self.decimal(from: orefDetermination.minDelta),
  129. expectedDelta: self.decimal(from: orefDetermination.expectedDelta),
  130. minGuardBG: nil,
  131. minPredBG: nil,
  132. threshold: self.decimal(from: orefDetermination.threshold),
  133. carbRatio: self.decimal(from: orefDetermination.carbRatio),
  134. received: orefDetermination.enacted // this is actually part of NS...
  135. )
  136. }
  137. } catch {
  138. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch managed object with error: \(error.localizedDescription)")
  139. }
  140. return result
  141. }
  142. }
  143. }