PumpEvent+helper.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import CoreData
  2. import Foundation
  3. extension PumpEventStored {
  4. static func fetch(_ predicate: NSPredicate, ascending: Bool, fetchLimit: Int? = nil) -> NSFetchRequest<PumpEventStored> {
  5. let request = PumpEventStored.fetchRequest()
  6. request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: ascending)]
  7. request.resultType = .managedObjectResultType
  8. request.predicate = predicate
  9. if let fetchLimit = fetchLimit {
  10. request.fetchLimit = fetchLimit
  11. }
  12. return request
  13. }
  14. }
  15. public extension PumpEventStored {
  16. enum EventType: String, JSON {
  17. case bolus = "Bolus"
  18. case smb = "SMB"
  19. case isExternal = "External Insulin"
  20. case mealBolus = "Meal Bolus"
  21. case correctionBolus = "Correction Bolus"
  22. case snackBolus = "Snack Bolus"
  23. case bolusWizard = "BolusWizard"
  24. case tempBasal = "TempBasal"
  25. case tempBasalDuration = "TempBasalDuration"
  26. case pumpSuspend = "PumpSuspend"
  27. case pumpResume = "PumpResume"
  28. case pumpAlarm = "PumpAlarm"
  29. case pumpBattery = "PumpBattery"
  30. case rewind = "Rewind"
  31. case prime = "Prime"
  32. case journalCarbs = "JournalEntryMealMarker"
  33. case nsNote = "Note"
  34. case nsTempBasal = "Temp Basal"
  35. case nsCarbCorrection = "Carb Correction"
  36. case nsTempTarget = "Temporary Target"
  37. case nsInsulinChange = "Insulin Change"
  38. case nsSiteChange = "Site Change"
  39. case nsBatteryChange = "Pump Battery Change"
  40. case nsAnnouncement = "Announcement"
  41. case nsSensorChange = "Sensor Start"
  42. case nsExercise = "Exercise"
  43. case capillaryGlucose = "BG Check"
  44. }
  45. enum TempType: String, JSON {
  46. case absolute
  47. case percent
  48. }
  49. }
  50. extension NSPredicate {
  51. static var pumpHistoryLast1440Minutes: NSPredicate {
  52. let date = Date.oneDayAgoInMinutes
  53. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  54. }
  55. static var pumpHistoryLast24h: NSPredicate {
  56. let date = Date.oneDayAgo
  57. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  58. }
  59. static var pumpHistoryForStats: NSPredicate {
  60. let date = Date.threeMonthsAgo
  61. return NSPredicate(format: "pumpEvent.timestamp >= %@", date as NSDate)
  62. }
  63. static var recentPumpHistory: NSPredicate {
  64. let date = Date.twentyMinutesAgo
  65. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  66. }
  67. static var lastPumpBolus: NSPredicate {
  68. let date = Date.twentyMinutesAgo
  69. return NSPredicate(format: "timestamp >= %@ AND bolus.isExternal == %@", date as NSDate, false as NSNumber)
  70. }
  71. static func duplicateInLastHour(_ date: Date) -> NSPredicate {
  72. let date60m = Date.oneHourAgo
  73. return NSPredicate(format: "timestamp >= %@ && timestamp == %@", date60m as NSDate, date as NSDate)
  74. }
  75. static var pumpEventsNotYetUploadedToNightscout: NSPredicate {
  76. let date = Date.oneDayAgo
  77. return NSPredicate(format: "timestamp >= %@ AND isUploadedToNS == %@", date as NSDate, false as NSNumber)
  78. }
  79. static var pumpEventsNotYetUploadedToHealth: NSPredicate {
  80. let date = Date.oneDayAgo
  81. return NSPredicate(format: "timestamp >= %@ AND isUploadedToHealth == %@", date as NSDate, false as NSNumber)
  82. }
  83. static var pumpEventsNotYetUploadedToTidepool: NSPredicate {
  84. let date = Date.oneDayAgo
  85. return NSPredicate(format: "timestamp >= %@ AND isUploadedToTidepool == %@", date as NSDate, false as NSNumber)
  86. }
  87. }
  88. // Declare helper structs ("data transfer objects" = DTO) to utilize parsing a flattened pump history
  89. struct BolusDTO: Codable {
  90. var id: String
  91. var timestamp: String
  92. var amount: Double
  93. var isExternal: Bool
  94. var isSMB: Bool
  95. var duration: Int
  96. var _type: String = EventType.bolus.rawValue
  97. }
  98. struct TempBasalDTO: Codable {
  99. var id: String
  100. var timestamp: String
  101. var temp: String
  102. var rate: Double
  103. var _type: String = EventType.tempBasal.rawValue
  104. }
  105. struct TempBasalDurationDTO: Codable {
  106. var id: String
  107. var timestamp: String
  108. var duration: Int
  109. var _type: String = EventType.tempBasalDuration.rawValue
  110. private enum CodingKeys: String, CodingKey {
  111. case id
  112. case timestamp
  113. case duration = "duration (min)"
  114. case _type
  115. }
  116. }
  117. struct SuspendDTO: Codable {
  118. var id: String
  119. var timestamp: String
  120. var _type: String = EventType.pumpSuspend.rawValue
  121. }
  122. struct ResumeDTO: Codable {
  123. var id: String
  124. var timestamp: String
  125. var _type: String = EventType.pumpResume.rawValue
  126. }
  127. struct RewindDTO: Codable {
  128. var id: String
  129. var timestamp: String
  130. var _type: String = EventType.rewind.rawValue
  131. }
  132. struct PrimeDTO: Codable {
  133. var id: String
  134. var timestamp: String
  135. var _type: String = EventType.prime.rawValue
  136. }
  137. // Mask distinct DTO subtypes with a common enum that conforms to Encodable
  138. enum PumpEventDTO: Encodable {
  139. case bolus(BolusDTO)
  140. case tempBasal(TempBasalDTO)
  141. case tempBasalDuration(TempBasalDurationDTO)
  142. case suspend(SuspendDTO)
  143. case resume(ResumeDTO)
  144. case rewind(RewindDTO)
  145. case prime(PrimeDTO)
  146. func encode(to encoder: Encoder) throws {
  147. switch self {
  148. case let .bolus(bolus):
  149. try bolus.encode(to: encoder)
  150. case let .tempBasal(tempBasal):
  151. try tempBasal.encode(to: encoder)
  152. case let .tempBasalDuration(tempBasalDuration):
  153. try tempBasalDuration.encode(to: encoder)
  154. case let .suspend(suspend):
  155. try suspend.encode(to: encoder)
  156. case let .resume(resume):
  157. try resume.encode(to: encoder)
  158. case let .rewind(rewind):
  159. try rewind.encode(to: encoder)
  160. case let .prime(prime):
  161. try prime.encode(to: encoder)
  162. }
  163. }
  164. }
  165. // Extension with helper functions to map pump events to DTO objects via uniform masking enum
  166. extension PumpEventStored {
  167. static let dateFormatter: ISO8601DateFormatter = {
  168. let formatter = ISO8601DateFormatter()
  169. formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
  170. return formatter
  171. }()
  172. func toBolusDTOEnum() -> PumpEventDTO? {
  173. guard let timestamp = timestamp, let bolus = bolus, let amount = bolus.amount else {
  174. return nil
  175. }
  176. let bolusDTO = BolusDTO(
  177. id: id ?? UUID().uuidString,
  178. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  179. amount: amount.doubleValue,
  180. isExternal: bolus.isExternal,
  181. isSMB: bolus.isSMB,
  182. duration: 0
  183. )
  184. return .bolus(bolusDTO)
  185. }
  186. func toTempBasalDTOEnum() -> PumpEventDTO? {
  187. guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal, let rate = tempBasal.rate else {
  188. return nil
  189. }
  190. let tempBasalDTO = TempBasalDTO(
  191. id: "_\(id)",
  192. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  193. temp: tempBasal.tempType ?? "unknown",
  194. rate: rate.doubleValue
  195. )
  196. return .tempBasal(tempBasalDTO)
  197. }
  198. func toTempBasalDurationDTOEnum() -> PumpEventDTO? {
  199. guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal else {
  200. return nil
  201. }
  202. let tempBasalDurationDTO = TempBasalDurationDTO(
  203. id: id,
  204. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  205. duration: Int(tempBasal.duration)
  206. )
  207. return .tempBasalDuration(tempBasalDurationDTO)
  208. }
  209. func toPumpSuspendDTO() -> PumpEventDTO? {
  210. guard let id = id, let timestamp = timestamp, let type = type, type == EventType.pumpSuspend.rawValue else {
  211. return nil
  212. }
  213. let suspendDTO = SuspendDTO(
  214. id: id,
  215. timestamp: PumpEventStored.dateFormatter.string(from: timestamp)
  216. )
  217. return .suspend(suspendDTO)
  218. }
  219. func toPumpResumeDTO() -> PumpEventDTO? {
  220. guard let id = id, let timestamp = timestamp, let type = type, type == EventType.pumpResume.rawValue else {
  221. return nil
  222. }
  223. let resumeDTO = ResumeDTO(
  224. id: id,
  225. timestamp: PumpEventStored.dateFormatter.string(from: timestamp)
  226. )
  227. return .resume(resumeDTO)
  228. }
  229. func toRewindDTO() -> PumpEventDTO? {
  230. guard let id = id, let timestamp = timestamp, let type = type, type == EventType.rewind.rawValue else {
  231. return nil
  232. }
  233. let rewindDTO = RewindDTO(
  234. id: id,
  235. timestamp: PumpEventStored.dateFormatter.string(from: timestamp)
  236. )
  237. return .rewind(rewindDTO)
  238. }
  239. func toPrimeDTO() -> PumpEventDTO? {
  240. guard let id = id, let timestamp = timestamp, let type = type, type == EventType.prime.rawValue else {
  241. return nil
  242. }
  243. let primeDTO = PrimeDTO(
  244. id: id,
  245. timestamp: PumpEventStored.dateFormatter.string(from: timestamp)
  246. )
  247. return .prime(primeDTO)
  248. }
  249. }