Determination+helper.swift 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import CoreData
  2. import Foundation
  3. extension OrefDetermination {
  4. static func fetch(_ predicate: NSPredicate = .predicateForOneDayAgo) -> NSFetchRequest<OrefDetermination> {
  5. let request = OrefDetermination.fetchRequest()
  6. request.sortDescriptors = [NSSortDescriptor(keyPath: \OrefDetermination.deliverAt, ascending: false)]
  7. request.predicate = predicate
  8. request.fetchLimit = 1
  9. return request
  10. }
  11. }
  12. extension Determination {
  13. var minPredBGFromReason: Decimal? {
  14. // Split reason into parts by semicolon and get first part
  15. let reasonParts = reason.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []
  16. // Find the part that contains "minPredBG"
  17. if let minPredBGPart = reasonParts.first(where: { $0.contains("minPredBG") }) {
  18. // Extract the number after "minPredBG"
  19. let components = minPredBGPart.components(separatedBy: "minPredBG ")
  20. if let valueComponent = components.dropFirst().first {
  21. // Get everything after "minPredBG " and convert to Decimal
  22. let valueString = valueComponent.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.-").inverted)
  23. return Decimal(string: valueString)
  24. }
  25. }
  26. return nil
  27. }
  28. }
  29. extension OrefDetermination {
  30. var reasonParts: [String] {
  31. reason?.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []
  32. }
  33. var reasonConclusion: String {
  34. reason?.components(separatedBy: "; ").last ?? ""
  35. }
  36. var minPredBGFromReason: Decimal? {
  37. // Find the part that contains "minPredBG"
  38. if let minPredBGPart = reasonParts.first(where: { $0.contains("minPredBG") }) {
  39. // Extract the number after "minPredBG"
  40. let components = minPredBGPart.components(separatedBy: "minPredBG ")
  41. if let valueComponent = components.dropFirst().first {
  42. // Get everything after "minPredBG " and convert to Decimal
  43. let valueString = valueComponent.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.-").inverted)
  44. return Decimal(string: valueString)
  45. }
  46. }
  47. return nil
  48. }
  49. }
  50. extension NSPredicate {
  51. static var enactedDetermination: NSPredicate {
  52. let date = Date.halfHourAgo
  53. return NSPredicate(format: "enacted == %@ AND timestamp >= %@", true as NSNumber, date as NSDate)
  54. }
  55. static var determinationsForCobIobCharts: NSPredicate {
  56. let date = Date.oneDayAgo
  57. return NSPredicate(format: "deliverAt >= %@", date as NSDate)
  58. }
  59. static var enactedDeterminationsNotYetUploadedToNightscout: NSPredicate {
  60. NSPredicate(
  61. format: "deliverAt >= %@ AND isUploadedToNS == %@ AND enacted == %@",
  62. Date.oneDayAgo as NSDate,
  63. false as NSNumber,
  64. true as NSNumber
  65. )
  66. }
  67. static var suggestedDeterminationsNotYetUploadedToNightscout: NSPredicate {
  68. NSPredicate(
  69. format: "deliverAt >= %@ AND isUploadedToNS == %@ AND (enacted == %@ OR enacted == nil OR enacted != %@)",
  70. Date.oneDayAgo as NSDate,
  71. false as NSNumber,
  72. true as NSNumber,
  73. true as NSNumber
  74. )
  75. }
  76. static var determinationsForStats: NSPredicate {
  77. let date = Date.threeMonthsAgo
  78. return NSPredicate(format: "deliverAt >= %@", date as NSDate)
  79. }
  80. }
  81. // MARK: - DeterminationDTO and Conformance to ImportableDTO
  82. /// Data Transfer Object for the enacted.json.
  83. struct DeterminationDTO: Decodable, ImportableDTO {
  84. let threshold: Decimal?
  85. let timestamp: String?
  86. let insulinForManualBolus: Decimal?
  87. let sensitivityRatio: Decimal?
  88. let predictions: Predictions?
  89. let received: Bool?
  90. let currentTarget: Decimal?
  91. let expectedDelta: Decimal?
  92. let cob: Int?
  93. let minDelta: Decimal?
  94. let bg: Decimal?
  95. let manualBolusErrorString: Decimal?
  96. let eventualBG: Decimal?
  97. let isf: Decimal?
  98. let rate: Decimal?
  99. let duration: Decimal?
  100. let temp: String?
  101. let insulinReq: Decimal?
  102. let deliverAt: String?
  103. let reason: String?
  104. let iob: Decimal?
  105. let reservoir: Decimal?
  106. enum CodingKeys: String, CodingKey {
  107. case threshold
  108. case timestamp
  109. case insulinForManualBolus
  110. case sensitivityRatio
  111. case predictions = "predBGs"
  112. case received = "recieved"
  113. case currentTarget = "current_target"
  114. case expectedDelta
  115. case cob = "COB"
  116. case minDelta
  117. case bg
  118. case manualBolusErrorString
  119. case eventualBG
  120. case isf = "ISF"
  121. case rate
  122. case duration
  123. case temp
  124. case insulinReq
  125. case deliverAt
  126. case reason
  127. case iob = "IOB"
  128. case reservoir
  129. }
  130. // Conformance to ImportableDTO
  131. typealias ManagedObject = OrefDetermination
  132. /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
  133. func store(in context: NSManagedObjectContext) -> OrefDetermination {
  134. let determinationEntity = OrefDetermination(context: context)
  135. let dateFormatter = ISO8601DateFormatter()
  136. determinationEntity.timestamp = timestamp.flatMap { dateFormatter.date(from: $0) }
  137. determinationEntity.deliverAt = deliverAt.flatMap { dateFormatter.date(from: $0) }
  138. determinationEntity.cob = cob.map { Int16($0) } ?? 0
  139. determinationEntity.temp = temp
  140. determinationEntity.iob = iob.map { NSDecimalNumber(decimal: $0) }
  141. determinationEntity.minDelta = minDelta.map { NSDecimalNumber(decimal: $0) }
  142. determinationEntity.expectedDelta = expectedDelta.map { NSDecimalNumber(decimal: $0) }
  143. determinationEntity.rate = rate.map { NSDecimalNumber(decimal: $0) }
  144. determinationEntity.reason = reason
  145. determinationEntity.reservoir = reservoir.map { NSDecimalNumber(decimal: $0) }
  146. determinationEntity.duration = duration.map { NSDecimalNumber(decimal: $0) }
  147. determinationEntity.currentTarget = currentTarget.map { NSDecimalNumber(decimal: $0) }
  148. determinationEntity.insulinForManualBolus = insulinForManualBolus.map { NSDecimalNumber(decimal: $0) }
  149. determinationEntity.sensitivityRatio = sensitivityRatio.map { NSDecimalNumber(decimal: $0) }
  150. determinationEntity.threshold = threshold.map { NSDecimalNumber(decimal: $0) }
  151. determinationEntity.eventualBG = eventualBG.map { NSDecimalNumber(decimal: $0) }
  152. determinationEntity.received = received ?? false
  153. determinationEntity.insulinReq = insulinReq.map { NSDecimalNumber(decimal: $0) }
  154. determinationEntity.insulinSensitivity = isf.map { NSDecimalNumber(decimal: $0) }
  155. determinationEntity.manualBolusErrorString = manualBolusErrorString.map { NSDecimalNumber(decimal: $0) }
  156. determinationEntity.glucose = bg.map { NSDecimalNumber(decimal: $0) }
  157. if let predictionData = predictions {
  158. var forecasts = Set<Forecast>()
  159. if let iobPredictions = predictionData.iob {
  160. forecasts.insert(createForecast(context: context, type: "IOB", values: iobPredictions))
  161. }
  162. if let ztPredictions = predictionData.zt {
  163. forecasts.insert(createForecast(context: context, type: "ZT", values: ztPredictions))
  164. }
  165. if let uamPredictions = predictionData.uam {
  166. forecasts.insert(createForecast(context: context, type: "UAM", values: uamPredictions))
  167. }
  168. determinationEntity.forecasts = forecasts
  169. }
  170. return determinationEntity
  171. }
  172. private func createForecast(context: NSManagedObjectContext, type: String, values: [Int]) -> Forecast {
  173. let forecast = Forecast(context: context)
  174. forecast.type = type
  175. forecast.date = Date()
  176. forecast.forecastValues = Set(values.enumerated().map { index, value in
  177. let forecastValue = ForecastValue(context: context)
  178. forecastValue.index = Int32(index)
  179. forecastValue.value = Int32(value)
  180. return forecastValue
  181. })
  182. return forecast
  183. }
  184. }