PumpEvent+helper.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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.timestamp = Date.now.addingTimeInterval(Double(index) * 60)
  20. event.type = "Mock Data"
  21. // event.amount = 1.0
  22. // event.isExternal = false
  23. // event.isSMB = false
  24. // event.duration = 0
  25. return event
  26. }
  27. return events
  28. }
  29. }
  30. public extension PumpEventStored {
  31. enum EventType: String, JSON {
  32. case bolus = "Bolus"
  33. case smb = "SMB"
  34. case isExternal = "External Insulin"
  35. case mealBolus = "Meal Bolus"
  36. case correctionBolus = "Correction Bolus"
  37. case snackBolus = "Snack Bolus"
  38. case bolusWizard = "BolusWizard"
  39. case tempBasal = "TempBasal"
  40. case tempBasalDuration = "TempBasalDuration"
  41. case pumpSuspend = "PumpSuspend"
  42. case pumpResume = "PumpResume"
  43. case pumpAlarm = "PumpAlarm"
  44. case pumpBattery = "PumpBattery"
  45. case rewind = "Rewind"
  46. case prime = "Prime"
  47. case journalCarbs = "JournalEntryMealMarker"
  48. case nsNote = "Note"
  49. case nsTempBasal = "Temp Basal"
  50. case nsCarbCorrection = "Carb Correction"
  51. case nsTempTarget = "Temporary Target"
  52. case nsInsulinChange = "Insulin Change"
  53. case nsSiteChange = "Site Change"
  54. case nsBatteryChange = "Pump Battery Change"
  55. case nsAnnouncement = "Announcement"
  56. case nsSensorChange = "Sensor Start"
  57. case nsExercise = "Exercise"
  58. case capillaryGlucose = "BG Check"
  59. }
  60. enum TempType: String, JSON {
  61. case absolute
  62. case percent
  63. }
  64. }
  65. extension NSPredicate {
  66. static var pumpHistoryLast1440Minutes: NSPredicate {
  67. let date = Date.oneDayAgoInMinutes
  68. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  69. }
  70. static var pumpHistoryLast24h: NSPredicate {
  71. let date = Date.oneDayAgo
  72. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  73. }
  74. static var recentPumpHistory: NSPredicate {
  75. let date = Date.twentyMinutesAgo
  76. return NSPredicate(format: "timestamp >= %@", date as NSDate)
  77. }
  78. static var lastPumpBolus: NSPredicate {
  79. let date = Date.twentyMinutesAgo
  80. return NSPredicate(format: "timestamp >= %@ AND bolus.isExternal == %@", date as NSDate, false as NSNumber)
  81. }
  82. static func duplicateInLastHour(_ date: Date) -> NSPredicate {
  83. let date60m = Date.oneHourAgo
  84. return NSPredicate(format: "timestamp >= %@ && timestamp == %@", date60m as NSDate, date as NSDate)
  85. }
  86. static var pumpEventsNotYetUploadedToNightscout: NSPredicate {
  87. let date = Date.oneDayAgo
  88. return NSPredicate(format: "timestamp >= %@ AND isUploadedToNS == %@", date as NSDate, false as NSNumber)
  89. }
  90. static var pumpEventsNotYetUploadedToHealth: NSPredicate {
  91. let date = Date.oneDayAgo
  92. return NSPredicate(format: "timestamp >= %@ AND isUploadedToHealth == %@", date as NSDate, false as NSNumber)
  93. }
  94. static var pumpEventsNotYetUploadedToTidepool: NSPredicate {
  95. let date = Date.oneDayAgo
  96. return NSPredicate(format: "timestamp >= %@ AND isUploadedToTidepool == %@", date as NSDate, false as NSNumber)
  97. }
  98. }
  99. // Declare helper structs ("data transfer objects" = DTO) to utilize parsing a flattened pump history
  100. struct BolusDTO: Codable {
  101. var id: String
  102. var timestamp: String
  103. var amount: Double
  104. var isExternal: Bool
  105. var isSMB: Bool
  106. var duration: Int
  107. var _type: String = "Bolus"
  108. }
  109. struct TempBasalDTO: Codable {
  110. var id: String
  111. var timestamp: String
  112. var temp: String
  113. var rate: Double
  114. var _type: String = "TempBasal"
  115. }
  116. struct TempBasalDurationDTO: Codable {
  117. var id: String
  118. var timestamp: String
  119. var duration: Int
  120. var _type: String = "TempBasalDuration"
  121. private enum CodingKeys: String, CodingKey {
  122. case id
  123. case timestamp
  124. case duration = "duration (min)"
  125. case _type
  126. }
  127. }
  128. // Mask distinct DTO subtypes with a common enum that conforms to Encodable
  129. enum PumpEventDTO: Encodable {
  130. case bolus(BolusDTO)
  131. case tempBasal(TempBasalDTO)
  132. case tempBasalDuration(TempBasalDurationDTO)
  133. func encode(to encoder: Encoder) throws {
  134. switch self {
  135. case let .bolus(bolus):
  136. try bolus.encode(to: encoder)
  137. case let .tempBasal(tempBasal):
  138. try tempBasal.encode(to: encoder)
  139. case let .tempBasalDuration(tempBasalDuration):
  140. try tempBasalDuration.encode(to: encoder)
  141. }
  142. }
  143. }
  144. // Extension with helper functions to map pump events to DTO objects via uniform masking enum
  145. extension PumpEventStored {
  146. static let dateFormatter: ISO8601DateFormatter = {
  147. let formatter = ISO8601DateFormatter()
  148. formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
  149. return formatter
  150. }()
  151. func toBolusDTOEnum() -> PumpEventDTO? {
  152. guard let timestamp = timestamp, let bolus = bolus, let amount = bolus.amount else {
  153. return nil
  154. }
  155. let bolusDTO = BolusDTO(
  156. id: id ?? UUID().uuidString,
  157. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  158. amount: amount.doubleValue,
  159. isExternal: bolus.isExternal,
  160. isSMB: bolus.isSMB,
  161. duration: 0
  162. )
  163. return .bolus(bolusDTO)
  164. }
  165. func toTempBasalDTOEnum() -> PumpEventDTO? {
  166. guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal, let rate = tempBasal.rate else {
  167. return nil
  168. }
  169. let tempBasalDTO = TempBasalDTO(
  170. id: "_\(id)",
  171. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  172. temp: tempBasal.tempType ?? "unknown",
  173. rate: rate.doubleValue
  174. )
  175. return .tempBasal(tempBasalDTO)
  176. }
  177. func toTempBasalDurationDTOEnum() -> PumpEventDTO? {
  178. guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal else {
  179. return nil
  180. }
  181. let tempBasalDurationDTO = TempBasalDurationDTO(
  182. id: id,
  183. timestamp: PumpEventStored.dateFormatter.string(from: timestamp),
  184. duration: Int(tempBasal.duration)
  185. )
  186. return .tempBasalDuration(tempBasalDurationDTO)
  187. }
  188. }