GlucoseRangeSchedule.swift 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. //
  2. // GlucoseRangeSchedule.swift
  3. // Naterade
  4. //
  5. // Created by Nathan Racklyeft on 2/13/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import Foundation
  9. import HealthKit
  10. public struct DoubleRange {
  11. public let minValue: Double
  12. public let maxValue: Double
  13. public init(minValue: Double, maxValue: Double) {
  14. self.minValue = minValue
  15. self.maxValue = maxValue
  16. }
  17. public var isZero: Bool {
  18. return abs(minValue) < .ulpOfOne && abs(maxValue) < .ulpOfOne
  19. }
  20. }
  21. extension DoubleRange: RawRepresentable {
  22. public typealias RawValue = [Double]
  23. public init?(rawValue: RawValue) {
  24. guard rawValue.count == 2 else {
  25. return nil
  26. }
  27. minValue = rawValue[0]
  28. maxValue = rawValue[1]
  29. }
  30. public var rawValue: RawValue {
  31. return [minValue, maxValue]
  32. }
  33. }
  34. extension DoubleRange: Equatable {
  35. public static func ==(lhs: DoubleRange, rhs: DoubleRange) -> Bool {
  36. return abs(lhs.minValue - rhs.minValue) < .ulpOfOne &&
  37. abs(lhs.maxValue - rhs.maxValue) < .ulpOfOne
  38. }
  39. }
  40. extension DoubleRange: Hashable {}
  41. extension DoubleRange: Codable {}
  42. /// Defines a daily schedule of glucose ranges
  43. public struct GlucoseRangeSchedule: DailySchedule, Equatable {
  44. public typealias RawValue = [String: Any]
  45. /// A time-based value overriding the rangeSchedule
  46. public struct Override: Equatable {
  47. public let start: Date
  48. public let end: Date
  49. public let value: DoubleRange
  50. /// Initializes a new override
  51. ///
  52. /// - Parameters:
  53. /// - value: The value to return when active
  54. /// - start: The date at which the override starts
  55. /// - end: The date at which the override ends, or nil for an indefinite override
  56. public init(value: DoubleRange, start: Date, end: Date? = nil) {
  57. self.value = value
  58. self.start = start
  59. self.end = end ?? .distantFuture
  60. }
  61. public var activeDates: DateInterval {
  62. return DateInterval(start: start, end: end)
  63. }
  64. public func isActive(at date: Date = Date()) -> Bool {
  65. return activeDates.contains(date) && !value.isZero
  66. }
  67. }
  68. /// An enabled override of the range schedule; only "active" between start and end, but when
  69. /// active, it overrides the entire schedule. Not persisted
  70. public private(set) var override: Override?
  71. var rangeSchedule: DailyQuantitySchedule<DoubleRange>
  72. public init(rangeSchedule: DailyQuantitySchedule<DoubleRange>, override: Override? = nil) {
  73. self.rangeSchedule = rangeSchedule
  74. self.override = override
  75. }
  76. public init?(unit: HKUnit, dailyItems: [RepeatingScheduleValue<DoubleRange>], timeZone: TimeZone? = nil) {
  77. guard let rangeSchedule = DailyQuantitySchedule<DoubleRange>(unit: unit, dailyItems: dailyItems, timeZone: timeZone) else {
  78. return nil
  79. }
  80. self.rangeSchedule = rangeSchedule
  81. }
  82. public init?(rawValue: RawValue) {
  83. guard let rangeSchedule = DailyQuantitySchedule<DoubleRange>(rawValue: rawValue) else {
  84. return nil
  85. }
  86. self.rangeSchedule = rangeSchedule
  87. }
  88. public func between(start startDate: Date, end endDate: Date) -> [AbsoluteScheduleValue<DoubleRange>] {
  89. return rangeSchedule.between(start: startDate, end: endDate)
  90. }
  91. public func quantityBetween(start: Date, end: Date) -> [AbsoluteScheduleValue<ClosedRange<HKQuantity>>] {
  92. var quantitySchedule = [AbsoluteScheduleValue<ClosedRange<HKQuantity>>]()
  93. for schedule in between(start: start, end: end) {
  94. quantitySchedule.append(AbsoluteScheduleValue(
  95. startDate: schedule.startDate,
  96. endDate: schedule.endDate,
  97. value: schedule.value.quantityRange(for: unit)
  98. ))
  99. }
  100. return quantitySchedule
  101. }
  102. /// Returns the underlying values in `unit`
  103. /// Consider using quantity(at:) instead
  104. public func value(at time: Date) -> DoubleRange {
  105. if let override = override, time >= override.start && Date() < override.end {
  106. return override.value
  107. }
  108. return rangeSchedule.value(at: time)
  109. }
  110. public func quantityRange(at time: Date) -> ClosedRange<HKQuantity> {
  111. return value(at: time).quantityRange(for: unit)
  112. }
  113. public var items: [RepeatingScheduleValue<DoubleRange>] {
  114. return rangeSchedule.items
  115. }
  116. public var quantityRanges: [RepeatingScheduleValue<ClosedRange<HKQuantity>>] {
  117. return self.items.map {
  118. RepeatingScheduleValue<ClosedRange<HKQuantity>>(startTime: $0.startTime,
  119. value: $0.value.quantityRange(for: unit))
  120. }
  121. }
  122. public var timeZone: TimeZone {
  123. get {
  124. return rangeSchedule.timeZone
  125. }
  126. set {
  127. rangeSchedule.timeZone = newValue
  128. }
  129. }
  130. public var unit: HKUnit {
  131. return rangeSchedule.unit
  132. }
  133. public var rawValue: RawValue {
  134. return rangeSchedule.rawValue
  135. }
  136. public func minLowerBound() -> HKQuantity {
  137. let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
  138. return HKQuantity(unit: unit, doubleValue: minDoubleValue)
  139. }
  140. public func scheduleRange() -> ClosedRange<HKQuantity> {
  141. let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
  142. let lowerBound = HKQuantity(unit: unit, doubleValue: minDoubleValue)
  143. let maxDoubleValue = items.lazy.map { $0.value.maxValue }.max()!
  144. let upperBound = HKQuantity(unit: unit, doubleValue: maxDoubleValue)
  145. return lowerBound...upperBound
  146. }
  147. private func convertTo(unit: HKUnit) -> GlucoseRangeSchedule? {
  148. guard unit != self.unit else {
  149. return self
  150. }
  151. let convertedDailyItems: [RepeatingScheduleValue<DoubleRange>] = rangeSchedule.items.map {
  152. RepeatingScheduleValue(startTime: $0.startTime,
  153. value: $0.value.quantityRange(for: self.unit).doubleRange(for: unit)
  154. )
  155. }
  156. return GlucoseRangeSchedule(unit: unit,
  157. dailyItems: convertedDailyItems,
  158. timeZone: timeZone)
  159. }
  160. public func schedule(for glucoseUnit: HKUnit) -> GlucoseRangeSchedule? {
  161. precondition(glucoseUnit == .millimolesPerLiter || glucoseUnit == .milligramsPerDeciliter)
  162. return self.convertTo(unit: glucoseUnit)
  163. }
  164. }
  165. extension GlucoseRangeSchedule: Codable {}
  166. extension GlucoseRangeSchedule.Override: Codable {}
  167. extension DoubleRange {
  168. public func quantityRange(for unit: HKUnit) -> ClosedRange<HKQuantity> {
  169. let lowerBound = HKQuantity(unit: unit, doubleValue: minValue)
  170. let upperBound = HKQuantity(unit: unit, doubleValue: maxValue)
  171. return lowerBound...upperBound
  172. }
  173. }
  174. extension ClosedRange where Bound == HKQuantity {
  175. public func doubleRange(for unit: HKUnit) -> DoubleRange {
  176. return DoubleRange(minValue: lowerBound.doubleValue(for: unit), maxValue: upperBound.doubleValue(for: unit))
  177. }
  178. public func glucoseRange(for unit: HKUnit) -> GlucoseRange {
  179. GlucoseRange(range: self.doubleRange(for: unit), unit: unit)
  180. }
  181. }
  182. public extension DoubleRange {
  183. init(_ val: ClosedRange<Double>) {
  184. self.init(minValue: val.lowerBound, maxValue: val.upperBound)
  185. }
  186. }