Determination+helper.swift 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 tdd: Decimal?
  85. let threshold: Decimal?
  86. let timestamp: String?
  87. let insulinForManualBolus: Decimal?
  88. let sensitivityRatio: Decimal?
  89. let predictions: Predictions?
  90. let received: Bool?
  91. let currentTarget: Decimal?
  92. let expectedDelta: Decimal?
  93. let cob: Int?
  94. let minDelta: Decimal?
  95. let bg: Decimal?
  96. let manualBolusErrorString: Decimal?
  97. let eventualBG: Decimal?
  98. let isf: Decimal?
  99. let rate: Decimal?
  100. let duration: Decimal?
  101. let temp: String?
  102. let insulinReq: Decimal?
  103. let insulin: Insulin?
  104. let deliverAt: String?
  105. let reason: String?
  106. let iob: Decimal?
  107. let reservoir: Decimal?
  108. enum CodingKeys: String, CodingKey {
  109. case tdd = "TDD"
  110. case threshold
  111. case timestamp
  112. case insulinForManualBolus
  113. case sensitivityRatio
  114. case predictions = "predBGs"
  115. case received = "recieved"
  116. case currentTarget = "current_target"
  117. case expectedDelta
  118. case cob = "COB"
  119. case minDelta
  120. case bg
  121. case manualBolusErrorString
  122. case eventualBG
  123. case isf = "ISF"
  124. case rate
  125. case duration
  126. case temp
  127. case insulinReq
  128. case insulin
  129. case deliverAt
  130. case reason
  131. case iob = "IOB"
  132. case reservoir
  133. }
  134. // Conformance to ImportableDTO
  135. typealias ManagedObject = OrefDetermination
  136. /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
  137. func store(in context: NSManagedObjectContext) -> OrefDetermination {
  138. let determinationEntity = OrefDetermination(context: context)
  139. let dateFormatter = ISO8601DateFormatter()
  140. determinationEntity.timestamp = timestamp.flatMap { dateFormatter.date(from: $0) }
  141. determinationEntity.deliverAt = deliverAt.flatMap { dateFormatter.date(from: $0) }
  142. determinationEntity.cob = cob.map { Int16($0) } ?? 0
  143. determinationEntity.temp = temp
  144. determinationEntity.iob = iob.map { NSDecimalNumber(decimal: $0) }
  145. determinationEntity.minDelta = minDelta.map { NSDecimalNumber(decimal: $0) }
  146. determinationEntity.expectedDelta = expectedDelta.map { NSDecimalNumber(decimal: $0) }
  147. determinationEntity.rate = rate.map { NSDecimalNumber(decimal: $0) }
  148. determinationEntity.reason = reason
  149. determinationEntity.totalDailyDose = tdd.map { NSDecimalNumber(decimal: $0) }
  150. determinationEntity.reservoir = reservoir.map { NSDecimalNumber(decimal: $0) }
  151. determinationEntity.duration = duration.map { NSDecimalNumber(decimal: $0) }
  152. determinationEntity.currentTarget = currentTarget.map { NSDecimalNumber(decimal: $0) }
  153. determinationEntity.insulinForManualBolus = insulinForManualBolus.map { NSDecimalNumber(decimal: $0) }
  154. determinationEntity.sensitivityRatio = sensitivityRatio.map { NSDecimalNumber(decimal: $0) }
  155. determinationEntity.threshold = threshold.map { NSDecimalNumber(decimal: $0) }
  156. determinationEntity.eventualBG = eventualBG.map { NSDecimalNumber(decimal: $0) }
  157. determinationEntity.received = received ?? false
  158. determinationEntity.insulinReq = insulinReq.map { NSDecimalNumber(decimal: $0) }
  159. determinationEntity.insulinSensitivity = isf.map { NSDecimalNumber(decimal: $0) }
  160. determinationEntity.manualBolusErrorString = manualBolusErrorString.map { NSDecimalNumber(decimal: $0) }
  161. determinationEntity.glucose = bg.map { NSDecimalNumber(decimal: $0) }
  162. if let predictionData = predictions {
  163. var forecasts = Set<Forecast>()
  164. if let iobPredictions = predictionData.iob {
  165. forecasts.insert(createForecast(context: context, type: "IOB", values: iobPredictions))
  166. }
  167. if let ztPredictions = predictionData.zt {
  168. forecasts.insert(createForecast(context: context, type: "ZT", values: ztPredictions))
  169. }
  170. if let uamPredictions = predictionData.uam {
  171. forecasts.insert(createForecast(context: context, type: "UAM", values: uamPredictions))
  172. }
  173. determinationEntity.forecasts = forecasts
  174. }
  175. return determinationEntity
  176. }
  177. private func createForecast(context: NSManagedObjectContext, type: String, values: [Int]) -> Forecast {
  178. let forecast = Forecast(context: context)
  179. forecast.type = type
  180. forecast.date = Date()
  181. forecast.forecastValues = Set(values.enumerated().map { index, value in
  182. let forecastValue = ForecastValue(context: context)
  183. forecastValue.index = Int32(index)
  184. forecastValue.value = Int32(value)
  185. return forecastValue
  186. })
  187. return forecast
  188. }
  189. }