PumpEvent+helper.swift 10 KB

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