GlucoseRangeSchedule.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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 timeZone: TimeZone {
  117. get {
  118. return rangeSchedule.timeZone
  119. }
  120. set {
  121. rangeSchedule.timeZone = newValue
  122. }
  123. }
  124. public var unit: HKUnit {
  125. return rangeSchedule.unit
  126. }
  127. public var rawValue: RawValue {
  128. return rangeSchedule.rawValue
  129. }
  130. public func minLowerBound() -> HKQuantity {
  131. let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
  132. return HKQuantity(unit: unit, doubleValue: minDoubleValue)
  133. }
  134. public func scheduleRange() -> ClosedRange<HKQuantity> {
  135. let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
  136. let lowerBound = HKQuantity(unit: unit, doubleValue: minDoubleValue)
  137. let maxDoubleValue = items.lazy.map { $0.value.maxValue }.max()!
  138. let upperBound = HKQuantity(unit: unit, doubleValue: maxDoubleValue)
  139. return lowerBound...upperBound
  140. }
  141. }
  142. extension GlucoseRangeSchedule: Codable {}
  143. extension GlucoseRangeSchedule.Override: Codable {}
  144. extension DoubleRange {
  145. public func quantityRange(for unit: HKUnit) -> ClosedRange<HKQuantity> {
  146. let lowerBound = HKQuantity(unit: unit, doubleValue: minValue)
  147. let upperBound = HKQuantity(unit: unit, doubleValue: maxValue)
  148. return lowerBound...upperBound
  149. }
  150. }
  151. extension ClosedRange where Bound == HKQuantity {
  152. public func doubleRange(for unit: HKUnit) -> DoubleRange {
  153. return DoubleRange(minValue: lowerBound.doubleValue(for: unit), maxValue: upperBound.doubleValue(for: unit))
  154. }
  155. }
  156. public extension DoubleRange {
  157. init(_ val: ClosedRange<Double>) {
  158. self.init(minValue: val.lowerBound, maxValue: val.upperBound)
  159. }
  160. }