UnfinalizedDose.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //
  2. // UnfinalizedDose.swift
  3. // MockKit
  4. //
  5. // Created by Pete Schwamb on 7/30/19.
  6. // Copyright © 2019 LoopKit Authors. All rights reserved.
  7. //
  8. import Foundation
  9. import LoopKit
  10. public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConvertible {
  11. public typealias RawValue = [String: Any]
  12. enum DoseType: Int {
  13. case bolus = 0
  14. case tempBasal
  15. case suspend
  16. case resume
  17. }
  18. private let dateFormatter = ISO8601DateFormatter()
  19. fileprivate var uniqueKey: Data {
  20. return "\(doseType) \(scheduledUnits ?? units) \(dateFormatter.string(from: startTime))".data(using: .utf8)!
  21. }
  22. let doseType: DoseType
  23. public var units: Double
  24. var scheduledUnits: Double? // Tracks the scheduled units, as boluses may be canceled before finishing, at which point units would reflect actual delivered volume.
  25. var scheduledTempRate: Double? // Tracks the original temp rate, as during finalization the units are discretized to pump pulses, changing the actual rate
  26. let startTime: Date
  27. var duration: TimeInterval
  28. var finishTime: Date {
  29. get {
  30. return startTime.addingTimeInterval(duration)
  31. }
  32. set {
  33. duration = newValue.timeIntervalSince(startTime)
  34. }
  35. }
  36. public var progress: Double {
  37. let elapsed = -startTime.timeIntervalSinceNow
  38. return min(elapsed / duration, 1)
  39. }
  40. public var finished: Bool {
  41. return progress >= 1
  42. }
  43. // Units per hour
  44. public var rate: Double {
  45. guard duration.hours > 0 else {
  46. return 0
  47. }
  48. return units / duration.hours
  49. }
  50. public var finalizedUnits: Double? {
  51. guard finished else {
  52. return nil
  53. }
  54. return units
  55. }
  56. init(bolusAmount: Double, startTime: Date, duration: TimeInterval) {
  57. self.doseType = .bolus
  58. self.units = bolusAmount
  59. self.startTime = startTime
  60. self.duration = duration
  61. self.scheduledUnits = nil
  62. }
  63. init(tempBasalRate: Double, startTime: Date, duration: TimeInterval) {
  64. self.doseType = .tempBasal
  65. self.units = tempBasalRate * duration.hours
  66. self.startTime = startTime
  67. self.duration = duration
  68. self.scheduledUnits = nil
  69. }
  70. init(suspendStartTime: Date) {
  71. self.doseType = .suspend
  72. self.units = 0
  73. self.startTime = suspendStartTime
  74. self.duration = 0
  75. }
  76. init(resumeStartTime: Date) {
  77. self.doseType = .resume
  78. self.units = 0
  79. self.startTime = resumeStartTime
  80. self.duration = 0
  81. }
  82. public mutating func cancel(at date: Date) {
  83. guard date < finishTime else {
  84. return
  85. }
  86. scheduledUnits = units
  87. let newDuration = date.timeIntervalSince(startTime)
  88. switch doseType {
  89. case .bolus:
  90. units = rate * newDuration.hours
  91. case .tempBasal:
  92. scheduledTempRate = rate
  93. units = floor(rate * newDuration.hours * 20) / 20
  94. default:
  95. break
  96. }
  97. duration = newDuration
  98. }
  99. public var isMutable: Bool {
  100. switch doseType {
  101. case .bolus, .tempBasal:
  102. return !finished
  103. default:
  104. return false
  105. }
  106. }
  107. public var description: String {
  108. switch doseType {
  109. case .bolus:
  110. return "Bolus units:\(units) startTime:\(startTime) duration:\(String(describing: duration))"
  111. case .tempBasal:
  112. return "TempBasal rate:\(scheduledTempRate ?? rate) units:\(units) startTime:\(startTime) duration:\(String(describing: duration))"
  113. case .suspend, .resume:
  114. return "\(doseType) startTime:\(startTime)"
  115. }
  116. }
  117. // RawRepresentable
  118. public init?(rawValue: RawValue) {
  119. guard
  120. let rawDoseType = rawValue["doseType"] as? Int,
  121. let doseType = DoseType(rawValue: rawDoseType),
  122. let units = rawValue["units"] as? Double,
  123. let startTime = rawValue["startTime"] as? Date,
  124. let duration = rawValue["duration"] as? Double
  125. else {
  126. return nil
  127. }
  128. self.doseType = doseType
  129. self.units = units
  130. self.startTime = startTime
  131. self.duration = duration
  132. if let scheduledUnits = rawValue["scheduledUnits"] as? Double {
  133. self.scheduledUnits = scheduledUnits
  134. }
  135. if let scheduledTempRate = rawValue["scheduledTempRate"] as? Double {
  136. self.scheduledTempRate = scheduledTempRate
  137. }
  138. }
  139. public var rawValue: RawValue {
  140. var rawValue: RawValue = [
  141. "doseType": doseType.rawValue,
  142. "units": units,
  143. "startTime": startTime,
  144. "duration": duration,
  145. ]
  146. if let scheduledUnits = scheduledUnits {
  147. rawValue["scheduledUnits"] = scheduledUnits
  148. }
  149. if let scheduledTempRate = scheduledTempRate {
  150. rawValue["scheduledTempRate"] = scheduledTempRate
  151. }
  152. return rawValue
  153. }
  154. }
  155. extension NewPumpEvent {
  156. init(_ dose: UnfinalizedDose) {
  157. let title = String(describing: dose)
  158. let entry = DoseEntry(dose)
  159. self.init(date: dose.startTime, dose: entry, isMutable: dose.isMutable, raw: dose.uniqueKey, title: title)
  160. }
  161. public var unfinalizedDose: UnfinalizedDose? {
  162. if let dose = dose {
  163. let duration = dose.endDate.timeIntervalSince(dose.startDate)
  164. switch dose.type {
  165. case .basal:
  166. return nil
  167. case .bolus:
  168. var newDose = UnfinalizedDose(bolusAmount: dose.programmedUnits, startTime: dose.startDate, duration: duration)
  169. if let delivered = dose.deliveredUnits {
  170. newDose.scheduledUnits = dose.programmedUnits
  171. newDose.units = delivered
  172. }
  173. return newDose
  174. case .resume:
  175. return UnfinalizedDose(resumeStartTime: dose.startDate)
  176. case .suspend:
  177. return UnfinalizedDose(suspendStartTime: dose.startDate)
  178. case .tempBasal:
  179. return UnfinalizedDose(tempBasalRate: dose.unitsPerHour, startTime: dose.startDate, duration: duration)
  180. }
  181. }
  182. return nil
  183. }
  184. }
  185. extension DoseEntry {
  186. init (_ dose: UnfinalizedDose) {
  187. switch dose.doseType {
  188. case .bolus:
  189. self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.units, unit: .units)
  190. case .tempBasal:
  191. self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits)
  192. case .suspend:
  193. self = DoseEntry(suspendDate: dose.startTime)
  194. case .resume:
  195. self = DoseEntry(resumeDate: dose.startTime)
  196. }
  197. }
  198. }