import CoreData import Foundation extension PumpEventStored { static func fetch(_ predicate: NSPredicate, ascending: Bool, fetchLimit: Int? = nil) -> NSFetchRequest { let request = PumpEventStored.fetchRequest() request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: ascending)] request.resultType = .managedObjectResultType request.predicate = predicate if let fetchLimit = fetchLimit { request.fetchLimit = fetchLimit } return request } // Preview @discardableResult static func makePreviewEvents(count: Int, provider: CoreDataStack) -> [PumpEventStored] { let context = provider.persistentContainer.viewContext let events = (0 ..< count).map { index -> PumpEventStored in let event = PumpEventStored(context: context) event.id = UUID().uuidString event.timestamp = Date.now.addingTimeInterval(Double(index) * -300) // Every 5 minutes event.type = EventType.bolus.rawValue event.isUploadedToNS = false event.isUploadedToHealth = false event.isUploadedToTidepool = false // Add a bolus let bolus = BolusStored(context: context) bolus.amount = 2.5 as NSDecimalNumber bolus.isExternal = false bolus.isSMB = false bolus.pumpEvent = event return event } try? context.save() return events } } public extension PumpEventStored { enum EventType: String, JSON { case bolus = "Bolus" case smb = "SMB" case isExternal = "External Insulin" case mealBolus = "Meal Bolus" case correctionBolus = "Correction Bolus" case snackBolus = "Snack Bolus" case bolusWizard = "BolusWizard" case tempBasal = "TempBasal" case tempBasalDuration = "TempBasalDuration" case pumpSuspend = "PumpSuspend" case pumpResume = "PumpResume" case pumpAlarm = "PumpAlarm" case pumpBattery = "PumpBattery" case rewind = "Rewind" case prime = "Prime" case journalCarbs = "JournalEntryMealMarker" case siteChange = "SiteChange" case nsNote = "Note" case nsTempBasal = "Temp Basal" case nsCarbCorrection = "Carb Correction" case nsTempTarget = "Temporary Target" case nsInsulinChange = "Insulin Change" case nsSiteChange = "Site Change" case nsBatteryChange = "Pump Battery Change" case nsAnnouncement = "Announcement" case nsSensorChange = "Sensor Start" case nsExercise = "Exercise" case capillaryGlucose = "BG Check" } enum TempType: String, JSON { case absolute case percent } } extension NSPredicate { static var pumpHistoryLast1440Minutes: NSPredicate { let date = Date.oneDayAgoInMinutes return NSPredicate(format: "timestamp >= %@", date as NSDate) } static var pumpHistoryLast48h: NSPredicate { let date = Date() - TimeInterval(hours: 48) return NSPredicate(format: "timestamp >= %@", date as NSDate) } static var pumpHistoryLast24h: NSPredicate { let date = Date.oneDayAgo return NSPredicate(format: "timestamp >= %@", date as NSDate) } static var pumpHistoryForStats: NSPredicate { let date = Date.threeMonthsAgo return NSPredicate(format: "pumpEvent.timestamp >= %@", date as NSDate) } static var recentPumpHistory: NSPredicate { let date = Date.twentyMinutesAgo return NSPredicate( format: "type == %@ AND timestamp >= %@", PumpEventStored.EventType.tempBasal.rawValue, date as NSDate ) } static var lastPumpBolus: NSPredicate { let date = Date.twentyMinutesAgo return NSPredicate(format: "timestamp >= %@ AND bolus.isExternal == %@", date as NSDate, false as NSNumber) } static func duplicates(_ date: Date) -> NSPredicate { NSPredicate(format: "timestamp == %@", date as NSDate) } static var pumpEventsNotYetUploadedToNightscout: NSPredicate { let date = Date.oneDayAgo return NSPredicate(format: "timestamp >= %@ AND isUploadedToNS == %@", date as NSDate, false as NSNumber) } static var pumpEventsNotYetUploadedToHealth: NSPredicate { let date = Date.oneDayAgo return NSPredicate(format: "timestamp >= %@ AND isUploadedToHealth == %@", date as NSDate, false as NSNumber) } static var pumpEventsNotYetUploadedToTidepool: NSPredicate { let date = Date.oneDayAgo return NSPredicate(format: "timestamp >= %@ AND isUploadedToTidepool == %@", date as NSDate, false as NSNumber) } } // MARK: - Native mapping extension PumpEventStored { /// Converts a stored pump event into the `PumpHistoryEvent`s the oref algorithm can use. /// A temp basal yields a duration entry followed by a rate entry. func toPumpHistoryEvents() -> [PumpHistoryEvent] { var events: [PumpHistoryEvent] = [] if let bolus = toBolusPumpHistoryEvent() { events.append(bolus) } if let duration = toTempBasalDurationPumpHistoryEvent() { events.append(duration) } if let tempBasal = toTempBasalPumpHistoryEvent() { events.append(tempBasal) } if let suspend = toSuspendPumpHistoryEvent() { events.append(suspend) } if let resume = toResumePumpHistoryEvent() { events.append(resume) } if let rewind = toRewindPumpHistoryEvent() { events.append(rewind) } if let prime = toPrimePumpHistoryEvent() { events.append(prime) } return events } private func toBolusPumpHistoryEvent() -> PumpHistoryEvent? { guard let timestamp = timestamp, let bolus = bolus, let amount = bolus.amount else { return nil } return PumpHistoryEvent( id: id ?? UUID().uuidString, type: .bolus, timestamp: timestamp, amount: Decimal(algorithmValue: amount.doubleValue), duration: 0, isSMB: bolus.isSMB, isExternal: bolus.isExternal ) } // The temp basal duration populates `durationMin`, not `duration`. private func toTempBasalDurationPumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal else { return nil } return PumpHistoryEvent( id: id, type: .tempBasalDuration, timestamp: timestamp, durationMin: Int(tempBasal.duration) ) } // The temp basal rate entry id is prefixed with "_". An unrecognized `tempType` maps to nil. private func toTempBasalPumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, let tempBasal = tempBasal, let rate = tempBasal.rate else { return nil } return PumpHistoryEvent( id: "_\(id)", type: .tempBasal, timestamp: timestamp, rate: Decimal(algorithmValue: rate.doubleValue), temp: tempBasal.tempType.flatMap { Trio.TempType(rawValue: $0) } ) } private func toSuspendPumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, type == EventType.pumpSuspend.rawValue else { return nil } return PumpHistoryEvent(id: id, type: .pumpSuspend, timestamp: timestamp) } private func toResumePumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, type == EventType.pumpResume.rawValue else { return nil } return PumpHistoryEvent(id: id, type: .pumpResume, timestamp: timestamp) } private func toRewindPumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, type == EventType.rewind.rawValue else { return nil } return PumpHistoryEvent(id: id, type: .rewind, timestamp: timestamp) } private func toPrimePumpHistoryEvent() -> PumpHistoryEvent? { guard let id = id, let timestamp = timestamp, type == EventType.prime.rawValue else { return nil } return PumpHistoryEvent(id: id, type: .prime, timestamp: timestamp) } }