PumpHistoryStorage.swift 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. import CoreData
  2. import Foundation
  3. import LoopKit
  4. import SwiftDate
  5. import Swinject
  6. protocol PumpHistoryObserver {
  7. func pumpHistoryDidUpdate(_ events: [PumpHistoryEvent])
  8. }
  9. protocol PumpHistoryStorage {
  10. func storePumpEvents(_ events: [NewPumpEvent])
  11. func storeEvents(_ events: [PumpHistoryEvent])
  12. func storeJournalCarbs(_ carbs: Int)
  13. func recent() -> [PumpHistoryEvent]
  14. func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment]
  15. func saveCancelTempEvents()
  16. func deleteInsulin(at date: Date)
  17. }
  18. final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
  19. private let processQueue = DispatchQueue(label: "BasePumpHistoryStorage.processQueue")
  20. @Injected() private var storage: FileStorage!
  21. @Injected() private var broadcaster: Broadcaster!
  22. init(resolver: Resolver) {
  23. injectServices(resolver)
  24. }
  25. typealias PumpEvent = PumpEventStored.EventType
  26. typealias TempType = PumpEventStored.TempType
  27. private let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
  28. func storePumpEvents(_ events: [NewPumpEvent]) {
  29. processQueue.async {
  30. self.context.perform {
  31. for event in events {
  32. let id = UUID().uuidString
  33. switch event.type {
  34. case .bolus:
  35. guard let dose = event.dose else { continue }
  36. let amount = Decimal(string: dose.unitsInDeliverableIncrements.description)
  37. let newPumpEvent = PumpEventStored(context: self.context)
  38. // newPumpEvent.id = id
  39. newPumpEvent.timestamp = event.date
  40. newPumpEvent.type = PumpEvent.bolus.rawValue
  41. let newBolusEntry = BolusStored(context: self.context)
  42. newBolusEntry.pumpEvent = newPumpEvent
  43. newBolusEntry.amount = amount as? NSDecimalNumber
  44. newBolusEntry.isExternal = dose.manuallyEntered
  45. newBolusEntry.isSMB = dose.automatic ?? true
  46. case .tempBasal:
  47. guard let dose = event.dose else { continue }
  48. let rate = Decimal(dose.unitsPerHour)
  49. let minutes = (dose.endDate - dose.startDate).timeInterval / 60
  50. let delivered = dose.deliveredUnits
  51. let date = event.date
  52. let isCancel = delivered != nil
  53. guard !isCancel else { continue }
  54. let newPumpEvent = PumpEventStored(context: self.context)
  55. // newPumpEvent.id = id
  56. newPumpEvent.timestamp = date
  57. newPumpEvent.type = PumpEvent.tempBasal.rawValue
  58. let newTempBasal = TempBasalStored(context: self.context)
  59. newTempBasal.pumpEvent = newPumpEvent
  60. newTempBasal.duration = Int16(round(minutes))
  61. newTempBasal.rate = rate as NSDecimalNumber
  62. newTempBasal.tempType = TempType.absolute.rawValue
  63. case .suspend:
  64. let newPumpEvent = PumpEventStored(context: self.context)
  65. // newPumpEvent.id = id
  66. newPumpEvent.timestamp = event.date
  67. newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
  68. case .resume:
  69. let newPumpEvent = PumpEventStored(context: self.context)
  70. // newPumpEvent.id = id
  71. newPumpEvent.timestamp = event.date
  72. newPumpEvent.type = PumpEvent.pumpResume.rawValue
  73. case .rewind:
  74. let newPumpEvent = PumpEventStored(context: self.context)
  75. // newPumpEvent.id = id
  76. newPumpEvent.timestamp = event.date
  77. newPumpEvent.type = PumpEvent.rewind.rawValue
  78. case .prime:
  79. let newPumpEvent = PumpEventStored(context: self.context)
  80. // newPumpEvent.id = id
  81. newPumpEvent.timestamp = event.date
  82. newPumpEvent.type = PumpEvent.prime.rawValue
  83. case .alarm:
  84. let newPumpEvent = PumpEventStored(context: self.context)
  85. // newPumpEvent.id = id
  86. newPumpEvent.timestamp = event.date
  87. newPumpEvent.type = PumpEvent.pumpAlarm.rawValue
  88. default:
  89. continue
  90. }
  91. }
  92. do {
  93. guard self.context.hasChanges else { return }
  94. try self.context.save()
  95. debugPrint("\(DebuggingIdentifiers.succeeded) stored pump events in Core Data")
  96. } catch let error as NSError {
  97. debugPrint("\(DebuggingIdentifiers.failed) failed to store pump events with error: \(error.userInfo)")
  98. }
  99. }
  100. }
  101. }
  102. // func storePumpEvents(_ events: [NewPumpEvent]) {
  103. // processQueue.async {
  104. // self.context.perform {
  105. // for event in events {
  106. // let id = UUID().uuidString
  107. //
  108. // switch event.type {
  109. // case .bolus:
  110. // guard let dose = event.dose else { continue }
  111. // let amount = Decimal(string: dose.unitsInDeliverableIncrements.description)
  112. //
  113. // let newPumpEvent = NSEntityDescription.insertNewObject(
  114. // forEntityName: "PumpEventStored",
  115. // into: self.context
  116. // ) as! PumpEventStored
  117. // newPumpEvent.id = id
  118. // newPumpEvent.timestamp = event.date
  119. // newPumpEvent.type = PumpEvent.bolus.rawValue
  120. //
  121. // let newBolusEntry = NSEntityDescription.insertNewObject(
  122. // forEntityName: "BolusStored",
  123. // into: self.context
  124. // ) as! BolusStored
  125. // newBolusEntry.pumpEvent = newPumpEvent
  126. // newBolusEntry.amount = amount as? NSDecimalNumber
  127. // newBolusEntry.isExternal = dose.manuallyEntered
  128. // newBolusEntry.isSMB = dose.automatic ?? true
  129. //
  130. // case .tempBasal:
  131. // guard let dose = event.dose else { continue }
  132. //
  133. // let rate = Decimal(dose.unitsPerHour)
  134. // let minutes = (dose.endDate - dose.startDate).timeInterval / 60
  135. // let delivered = dose.deliveredUnits
  136. // let date = event.date
  137. //
  138. // let isCancel = delivered != nil
  139. // guard !isCancel else { continue }
  140. //
  141. // let newPumpEvent = NSEntityDescription.insertNewObject(
  142. // forEntityName: "PumpEventStored",
  143. // into: self.context
  144. // ) as! PumpEventStored
  145. // newPumpEvent.id = id
  146. // newPumpEvent.timestamp = date
  147. // newPumpEvent.type = PumpEvent.tempBasal.rawValue
  148. //
  149. // let newTempBasal = NSEntityDescription.insertNewObject(
  150. // forEntityName: "TempBasalStored",
  151. // into: self.context
  152. // ) as! TempBasalStored
  153. // newTempBasal.pumpEvent = newPumpEvent
  154. // newTempBasal.duration = Int16(round(minutes))
  155. // newTempBasal.rate = rate as NSDecimalNumber
  156. // newTempBasal.tempType = TempType.absolute.rawValue
  157. //
  158. // case .suspend:
  159. // let newPumpEvent = NSEntityDescription.insertNewObject(
  160. // forEntityName: "PumpEventStored",
  161. // into: self.context
  162. // ) as! PumpEventStored
  163. // newPumpEvent.id = id
  164. // newPumpEvent.timestamp = event.date
  165. // newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
  166. //
  167. // case .resume:
  168. // let newPumpEvent = NSEntityDescription.insertNewObject(
  169. // forEntityName: "PumpEventStored",
  170. // into: self.context
  171. // ) as! PumpEventStored
  172. // newPumpEvent.id = id
  173. // newPumpEvent.timestamp = event.date
  174. // newPumpEvent.type = PumpEvent.pumpResume.rawValue
  175. //
  176. // case .rewind:
  177. // let newPumpEvent = NSEntityDescription.insertNewObject(
  178. // forEntityName: "PumpEventStored",
  179. // into: self.context
  180. // ) as! PumpEventStored
  181. // newPumpEvent.id = id
  182. // newPumpEvent.timestamp = event.date
  183. // newPumpEvent.type = PumpEvent.rewind.rawValue
  184. //
  185. // case .prime:
  186. // let newPumpEvent = NSEntityDescription.insertNewObject(
  187. // forEntityName: "PumpEventStored",
  188. // into: self.context
  189. // ) as! PumpEventStored
  190. // newPumpEvent.id = id
  191. // newPumpEvent.timestamp = event.date
  192. // newPumpEvent.type = PumpEvent.prime.rawValue
  193. //
  194. // case .alarm:
  195. // let newPumpEvent = NSEntityDescription.insertNewObject(
  196. // forEntityName: "PumpEventStored",
  197. // into: self.context
  198. // ) as! PumpEventStored
  199. // newPumpEvent.id = id
  200. // newPumpEvent.timestamp = event.date
  201. // newPumpEvent.type = PumpEvent.pumpAlarm.rawValue
  202. //
  203. // default:
  204. // continue
  205. // }
  206. // }
  207. //
  208. // do {
  209. // if self.context.hasChanges {
  210. // try self.context.save()
  211. // }
  212. // } catch {
  213. // print(error.localizedDescription)
  214. // }
  215. // }
  216. // }
  217. // }
  218. // func storePumpEvents(_ events: [NewPumpEvent]) {
  219. // processQueue.async {
  220. // let eventsToStore = events.flatMap { event -> [PumpHistoryEvent] in
  221. // let id = event.raw.md5String
  222. // switch event.type {
  223. // case .bolus:
  224. // guard let dose = event.dose else { return [] }
  225. // let amount = Decimal(string: dose.unitsInDeliverableIncrements.description)
  226. // let minutes = Int((dose.endDate - dose.startDate).timeInterval / 60)
  227. //
  228. // self.context.perform {
  229. // // create pump event
  230. // let newPumpEvent = PumpEventStored(context: self.context)
  231. // newPumpEvent.id = id
  232. // newPumpEvent.timestamp = event.date
  233. // newPumpEvent.type = PumpEvent.bolus.rawValue
  234. //
  235. // // create bolus entry and specify relationship to pump event
  236. // let newBolusEntry = BolusStored(context: self.context)
  237. // newBolusEntry.pumpEvent = newPumpEvent
  238. // newBolusEntry.amount = amount as? NSDecimalNumber
  239. // newBolusEntry.isExternal = dose.manuallyEntered
  240. // newBolusEntry.isSMB = dose.automatic ?? true
  241. //
  242. // do {
  243. // guard self.context.hasChanges else { return }
  244. // try self.context.save()
  245. // } catch {
  246. // print(error.localizedDescription)
  247. // }
  248. // }
  249. //
  250. // return [PumpHistoryEvent(
  251. // id: id,
  252. // type: .bolus,
  253. // timestamp: event.date,
  254. // amount: amount,
  255. // duration: minutes,
  256. // durationMin: nil,
  257. // rate: nil,
  258. // temp: nil,
  259. // carbInput: nil,
  260. // isSMB: dose.automatic,
  261. // isExternal: dose.manuallyEntered
  262. // )]
  263. // case .tempBasal:
  264. // guard let dose = event.dose else { return [] }
  265. //
  266. // let rate = Decimal(dose.unitsPerHour)
  267. // let minutes = (dose.endDate - dose.startDate).timeInterval / 60
  268. // let delivered = dose.deliveredUnits
  269. // let date = event.date
  270. //
  271. // let isCancel = delivered != nil //! event.isMutable && delivered != nil
  272. // guard !isCancel else { return [] }
  273. //
  274. // self.context.perform {
  275. // // create pump event
  276. // let newPumpEvent = PumpEventStored(context: self.context)
  277. // newPumpEvent.id = id
  278. // newPumpEvent.timestamp = date
  279. // newPumpEvent.type = PumpEvent.tempBasal.rawValue
  280. //
  281. // // create temp basal and specify relationship
  282. // let newTempBasal = TempBasalStored(context: self.context)
  283. // newTempBasal.pumpEvent = newPumpEvent
  284. // newTempBasal.duration = Int16(round(minutes))
  285. // newTempBasal.rate = rate as NSDecimalNumber
  286. // newTempBasal.tempType = TempType.absolute.rawValue
  287. //
  288. // do {
  289. // guard self.context.hasChanges else { return }
  290. // try self.context.save()
  291. // } catch {
  292. // print(error.localizedDescription)
  293. // }
  294. // }
  295. //
  296. // return [
  297. // PumpHistoryEvent(
  298. // id: id,
  299. // type: .tempBasalDuration,
  300. // timestamp: date,
  301. // amount: nil,
  302. // duration: nil,
  303. // durationMin: Int(round(minutes)),
  304. // rate: nil,
  305. // temp: nil,
  306. // carbInput: nil
  307. // ),
  308. // PumpHistoryEvent(
  309. // id: "_" + id,
  310. // type: .tempBasal,
  311. // timestamp: date,
  312. // amount: nil,
  313. // duration: nil,
  314. // durationMin: nil,
  315. // rate: rate,
  316. // temp: .absolute,
  317. // carbInput: nil
  318. // )
  319. // ]
  320. // case .suspend:
  321. // self.context.perform {
  322. // // create pump event
  323. // let newPumpEvent = PumpEventStored(context: self.context)
  324. // newPumpEvent.id = id
  325. // newPumpEvent.timestamp = event.date
  326. // newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
  327. //
  328. // do {
  329. // guard self.context.hasChanges else { return }
  330. // try self.context.save()
  331. // } catch {
  332. // print(error.localizedDescription)
  333. // }
  334. // }
  335. // return [
  336. // PumpHistoryEvent(
  337. // id: id,
  338. // type: .pumpSuspend,
  339. // timestamp: event.date,
  340. // amount: nil,
  341. // duration: nil,
  342. // durationMin: nil,
  343. // rate: nil,
  344. // temp: nil,
  345. // carbInput: nil
  346. // )
  347. // ]
  348. // case .resume:
  349. // self.context.perform {
  350. // // create pump event
  351. // let newPumpEvent = PumpEventStored(context: self.context)
  352. // newPumpEvent.id = id
  353. // newPumpEvent.timestamp = event.date
  354. // newPumpEvent.type = PumpEvent.pumpResume.rawValue
  355. //
  356. // do {
  357. // guard self.context.hasChanges else { return }
  358. // try self.context.save()
  359. // } catch {
  360. // print(error.localizedDescription)
  361. // }
  362. // }
  363. // return [
  364. // PumpHistoryEvent(
  365. // id: id,
  366. // type: .pumpResume,
  367. // timestamp: event.date,
  368. // amount: nil,
  369. // duration: nil,
  370. // durationMin: nil,
  371. // rate: nil,
  372. // temp: nil,
  373. // carbInput: nil
  374. // )
  375. // ]
  376. // case .rewind:
  377. // return [
  378. // PumpHistoryEvent(
  379. // id: id,
  380. // type: .rewind,
  381. // timestamp: event.date,
  382. // amount: nil,
  383. // duration: nil,
  384. // durationMin: nil,
  385. // rate: nil,
  386. // temp: nil,
  387. // carbInput: nil
  388. // )
  389. // ]
  390. // case .prime:
  391. // return [
  392. // PumpHistoryEvent(
  393. // id: id,
  394. // type: .prime,
  395. // timestamp: event.date,
  396. // amount: nil,
  397. // duration: nil,
  398. // durationMin: nil,
  399. // rate: nil,
  400. // temp: nil,
  401. // carbInput: nil
  402. // )
  403. // ]
  404. // case .alarm:
  405. // return [
  406. // PumpHistoryEvent(
  407. // id: id,
  408. // type: .pumpAlarm,
  409. // timestamp: event.date,
  410. // note: event.title
  411. // )
  412. // ]
  413. // default:
  414. // return []
  415. // }
  416. // }
  417. //
  418. // self.storeEvents(eventsToStore)
  419. // }
  420. // }
  421. func storeJournalCarbs(_ carbs: Int) {
  422. processQueue.async {
  423. let eventsToStore = [
  424. PumpHistoryEvent(
  425. id: UUID().uuidString,
  426. type: .journalCarbs,
  427. timestamp: Date(),
  428. amount: nil,
  429. duration: nil,
  430. durationMin: nil,
  431. rate: nil,
  432. temp: nil,
  433. carbInput: carbs
  434. )
  435. ]
  436. self.storeEvents(eventsToStore)
  437. }
  438. }
  439. func storeEvents(_ events: [PumpHistoryEvent]) {
  440. processQueue.async {
  441. let file = OpenAPS.Monitor.pumpHistory
  442. var uniqEvents: [PumpHistoryEvent] = []
  443. self.storage.transaction { storage in
  444. storage.append(events, to: file, uniqBy: \.id)
  445. uniqEvents = storage.retrieve(file, as: [PumpHistoryEvent].self)?
  446. .filter { $0.timestamp.addingTimeInterval(1.days.timeInterval) > Date() }
  447. .sorted { $0.timestamp > $1.timestamp } ?? []
  448. storage.save(Array(uniqEvents), as: file)
  449. }
  450. self.broadcaster.notify(PumpHistoryObserver.self, on: self.processQueue) {
  451. $0.pumpHistoryDidUpdate(uniqEvents)
  452. }
  453. }
  454. }
  455. func recent() -> [PumpHistoryEvent] {
  456. storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self)?.reversed() ?? []
  457. }
  458. func deleteInsulin(at date: Date) {
  459. processQueue.sync {
  460. var allValues = storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self) ?? []
  461. guard let entryIndex = allValues.firstIndex(where: { $0.timestamp == date }) else {
  462. return
  463. }
  464. allValues.remove(at: entryIndex)
  465. storage.save(allValues, as: OpenAPS.Monitor.pumpHistory)
  466. broadcaster.notify(PumpHistoryObserver.self, on: processQueue) {
  467. $0.pumpHistoryDidUpdate(allValues)
  468. }
  469. }
  470. }
  471. func determineBolusEventType(for event: PumpHistoryEvent) -> EventType {
  472. if event.isSMB ?? false {
  473. return .smb
  474. }
  475. if event.isExternal ?? false {
  476. return .isExternal
  477. }
  478. return event.type
  479. }
  480. func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment] {
  481. let events = recent()
  482. guard !events.isEmpty else { return [] }
  483. let temps: [NigtscoutTreatment] = events.reduce([]) { result, event in
  484. var result = result
  485. switch event.type {
  486. case .tempBasal:
  487. result.append(NigtscoutTreatment(
  488. duration: nil,
  489. rawDuration: nil,
  490. rawRate: event,
  491. absolute: event.rate,
  492. rate: event.rate,
  493. eventType: .nsTempBasal,
  494. createdAt: event.timestamp,
  495. enteredBy: NigtscoutTreatment.local,
  496. bolus: nil,
  497. insulin: nil,
  498. notes: nil,
  499. carbs: nil,
  500. fat: nil,
  501. protein: nil,
  502. targetTop: nil,
  503. targetBottom: nil
  504. ))
  505. case .tempBasalDuration:
  506. if var last = result.popLast(), last.eventType == .nsTempBasal, last.createdAt == event.timestamp {
  507. last.duration = event.durationMin
  508. last.rawDuration = event
  509. result.append(last)
  510. }
  511. default: break
  512. }
  513. return result
  514. }
  515. let bolusesAndCarbs = events.compactMap { event -> NigtscoutTreatment? in
  516. switch event.type {
  517. case .bolus:
  518. let eventType = determineBolusEventType(for: event)
  519. return NigtscoutTreatment(
  520. duration: event.duration,
  521. rawDuration: nil,
  522. rawRate: nil,
  523. absolute: nil,
  524. rate: nil,
  525. eventType: eventType,
  526. createdAt: event.timestamp,
  527. enteredBy: NigtscoutTreatment.local,
  528. bolus: event,
  529. insulin: event.amount,
  530. notes: nil,
  531. carbs: nil,
  532. fat: nil,
  533. protein: nil,
  534. targetTop: nil,
  535. targetBottom: nil
  536. )
  537. case .journalCarbs:
  538. return NigtscoutTreatment(
  539. duration: nil,
  540. rawDuration: nil,
  541. rawRate: nil,
  542. absolute: nil,
  543. rate: nil,
  544. eventType: .nsCarbCorrection,
  545. createdAt: event.timestamp,
  546. enteredBy: NigtscoutTreatment.local,
  547. bolus: nil,
  548. insulin: nil,
  549. notes: nil,
  550. carbs: Decimal(event.carbInput ?? 0),
  551. fat: nil,
  552. protein: nil,
  553. targetTop: nil,
  554. targetBottom: nil
  555. )
  556. default: return nil
  557. }
  558. }
  559. let misc = events.compactMap { event -> NigtscoutTreatment? in
  560. switch event.type {
  561. case .prime:
  562. return NigtscoutTreatment(
  563. duration: event.duration,
  564. rawDuration: nil,
  565. rawRate: nil,
  566. absolute: nil,
  567. rate: nil,
  568. eventType: .nsSiteChange,
  569. createdAt: event.timestamp,
  570. enteredBy: NigtscoutTreatment.local,
  571. bolus: event,
  572. insulin: nil,
  573. notes: nil,
  574. carbs: nil,
  575. fat: nil,
  576. protein: nil,
  577. targetTop: nil,
  578. targetBottom: nil
  579. )
  580. case .rewind:
  581. return NigtscoutTreatment(
  582. duration: nil,
  583. rawDuration: nil,
  584. rawRate: nil,
  585. absolute: nil,
  586. rate: nil,
  587. eventType: .nsInsulinChange,
  588. createdAt: event.timestamp,
  589. enteredBy: NigtscoutTreatment.local,
  590. bolus: nil,
  591. insulin: nil,
  592. notes: nil,
  593. carbs: nil,
  594. fat: nil,
  595. protein: nil,
  596. targetTop: nil,
  597. targetBottom: nil
  598. )
  599. case .pumpAlarm:
  600. return NigtscoutTreatment(
  601. duration: 30, // minutes
  602. rawDuration: nil,
  603. rawRate: nil,
  604. absolute: nil,
  605. rate: nil,
  606. eventType: .nsAnnouncement,
  607. createdAt: event.timestamp,
  608. enteredBy: NigtscoutTreatment.local,
  609. bolus: nil,
  610. insulin: nil,
  611. notes: "Alarm \(String(describing: event.note)) \(event.type)",
  612. carbs: nil,
  613. fat: nil,
  614. protein: nil,
  615. targetTop: nil,
  616. targetBottom: nil
  617. )
  618. default: return nil
  619. }
  620. }
  621. let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedPumphistory, as: [NigtscoutTreatment].self) ?? []
  622. let treatments = Array(Set([bolusesAndCarbs, temps, misc].flatMap { $0 }).subtracting(Set(uploaded)))
  623. return treatments.sorted { $0.createdAt! > $1.createdAt! }
  624. }
  625. func saveCancelTempEvents() {
  626. let basalID = UUID().uuidString
  627. let date = Date()
  628. let events = [
  629. PumpHistoryEvent(
  630. id: basalID,
  631. type: .tempBasalDuration,
  632. timestamp: date,
  633. amount: nil,
  634. duration: nil,
  635. durationMin: 0,
  636. rate: nil,
  637. temp: nil,
  638. carbInput: nil
  639. ),
  640. PumpHistoryEvent(
  641. id: "_" + basalID,
  642. type: .tempBasal,
  643. timestamp: date,
  644. amount: nil,
  645. duration: nil,
  646. durationMin: nil,
  647. rate: 0,
  648. temp: .absolute,
  649. carbInput: nil
  650. )
  651. ]
  652. storeEvents(events)
  653. }
  654. }