TherapySettingsViewModel.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //
  2. // TherapySettingsViewModel.swift
  3. // LoopKitUI
  4. //
  5. // Created by Rick Pasetto on 7/13/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import Combine
  9. import LoopKit
  10. import HealthKit
  11. import SwiftUI
  12. public protocol TherapySettingsViewModelDelegate: AnyObject {
  13. func syncBasalRateSchedule(items: [RepeatingScheduleValue<Double>], completion: @escaping (Result<BasalRateSchedule, Error>) -> Void)
  14. func syncDeliveryLimits(deliveryLimits: DeliveryLimits, completion: @escaping (Result<DeliveryLimits, Error>) -> Void)
  15. func saveCompletion(therapySettings: TherapySettings)
  16. func pumpSupportedIncrements() -> PumpSupportedIncrements?
  17. }
  18. public class TherapySettingsViewModel: ObservableObject {
  19. @Published public var therapySettings: TherapySettings
  20. private let initialTherapySettings: TherapySettings
  21. let sensitivityOverridesEnabled: Bool
  22. let adultChildInsulinModelSelectionEnabled: Bool
  23. public var prescription: Prescription?
  24. private weak var delegate: TherapySettingsViewModelDelegate?
  25. public init(therapySettings: TherapySettings,
  26. pumpSupportedIncrements: (() -> PumpSupportedIncrements?)? = nil,
  27. sensitivityOverridesEnabled: Bool = false,
  28. adultChildInsulinModelSelectionEnabled: Bool = false,
  29. prescription: Prescription? = nil,
  30. delegate: TherapySettingsViewModelDelegate? = nil) {
  31. self.therapySettings = therapySettings
  32. self.initialTherapySettings = therapySettings
  33. self.sensitivityOverridesEnabled = sensitivityOverridesEnabled
  34. self.adultChildInsulinModelSelectionEnabled = adultChildInsulinModelSelectionEnabled
  35. self.prescription = prescription
  36. self.delegate = delegate
  37. }
  38. var deliveryLimits: DeliveryLimits {
  39. return DeliveryLimits(maximumBasalRate: therapySettings.maximumBasalRatePerHour.map { HKQuantity(unit: .internationalUnitsPerHour, doubleValue: $0) },
  40. maximumBolus: therapySettings.maximumBolus.map { HKQuantity(unit: .internationalUnit(), doubleValue: $0) } )
  41. }
  42. var suspendThreshold: GlucoseThreshold? {
  43. return therapySettings.suspendThreshold
  44. }
  45. var glucoseTargetRangeSchedule: GlucoseRangeSchedule? {
  46. return therapySettings.glucoseTargetRangeSchedule
  47. }
  48. func glucoseTargetRangeSchedule(for glucoseUnit: HKUnit) -> GlucoseRangeSchedule? {
  49. return glucoseTargetRangeSchedule?.schedule(for: glucoseUnit)
  50. }
  51. var correctionRangeOverrides: CorrectionRangeOverrides {
  52. return CorrectionRangeOverrides(preMeal: therapySettings.correctionRangeOverrides?.preMeal,
  53. workout: therapySettings.correctionRangeOverrides?.workout)
  54. }
  55. var correctionRangeScheduleRange: ClosedRange<HKQuantity> {
  56. precondition(therapySettings.glucoseTargetRangeSchedule != nil)
  57. return therapySettings.glucoseTargetRangeSchedule!.scheduleRange()
  58. }
  59. var insulinSensitivitySchedule: InsulinSensitivitySchedule? {
  60. return therapySettings.insulinSensitivitySchedule
  61. }
  62. func insulinSensitivitySchedule(for glucoseUnit: HKUnit) -> InsulinSensitivitySchedule? {
  63. return insulinSensitivitySchedule?.schedule(for: glucoseUnit)
  64. }
  65. /// Reset to initial
  66. public func reset() {
  67. therapySettings = initialTherapySettings
  68. }
  69. }
  70. // MARK: Passing along to the delegate
  71. extension TherapySettingsViewModel {
  72. public var maximumBasalScheduleEntryCount: Int? {
  73. pumpSupportedIncrements()?.maximumBasalScheduleEntryCount
  74. }
  75. public func pumpSupportedIncrements() -> PumpSupportedIncrements? {
  76. return delegate?.pumpSupportedIncrements()
  77. }
  78. public func syncBasalRateSchedule(items: [RepeatingScheduleValue<Double>], completion: @escaping (Result<BasalRateSchedule, Error>) -> Void) {
  79. delegate?.syncBasalRateSchedule(items: items, completion: completion)
  80. }
  81. public func syncDeliveryLimits(deliveryLimits: DeliveryLimits, completion: @escaping (Result<DeliveryLimits, Error>) -> Void) {
  82. delegate?.syncDeliveryLimits(deliveryLimits: deliveryLimits, completion: completion)
  83. }
  84. }
  85. // MARK: Saving
  86. extension TherapySettingsViewModel {
  87. public func saveCorrectionRange(range: GlucoseRangeSchedule) {
  88. therapySettings.glucoseTargetRangeSchedule = range
  89. delegate?.saveCompletion(therapySettings: therapySettings)
  90. }
  91. public func saveCorrectionRangeOverride(preset: CorrectionRangeOverrides.Preset,
  92. correctionRangeOverrides: CorrectionRangeOverrides) {
  93. therapySettings.correctionRangeOverrides = correctionRangeOverrides
  94. delegate?.saveCompletion(therapySettings: therapySettings)
  95. }
  96. public func saveSuspendThreshold(quantity: HKQuantity, withDisplayGlucoseUnit displayGlucoseUnit: HKUnit) {
  97. therapySettings.suspendThreshold = GlucoseThreshold(unit: displayGlucoseUnit, value: quantity.doubleValue(for: displayGlucoseUnit))
  98. // TODO: Eventually target editors should support conflicting initial values
  99. // But for now, ensure target ranges do not conflict with suspend threshold.
  100. if let targetSchedule = therapySettings.glucoseTargetRangeSchedule {
  101. let threshold = quantity.doubleValue(for: targetSchedule.unit)
  102. let newItems = targetSchedule.items.map { item in
  103. return RepeatingScheduleValue<DoubleRange>.init(
  104. startTime: item.startTime,
  105. value: DoubleRange(
  106. minValue: max(threshold, item.value.minValue),
  107. maxValue: max(threshold, item.value.maxValue)))
  108. }
  109. therapySettings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: targetSchedule.unit, dailyItems: newItems)
  110. }
  111. if let overrides = therapySettings.correctionRangeOverrides {
  112. let adjusted = [overrides.preMeal, overrides.workout].map { item -> ClosedRange<HKQuantity>? in
  113. guard let item = item else {
  114. return nil
  115. }
  116. return ClosedRange<HKQuantity>.init(
  117. uncheckedBounds: (
  118. lower: max(quantity, item.lowerBound),
  119. upper: max(quantity, item.upperBound)))
  120. }
  121. therapySettings.correctionRangeOverrides = CorrectionRangeOverrides(
  122. preMeal: adjusted[0],
  123. workout: adjusted[1])
  124. }
  125. if let presets = therapySettings.overridePresets {
  126. therapySettings.overridePresets = presets.map { preset in
  127. if let targetRange = preset.settings.targetRange {
  128. var newPreset = preset
  129. newPreset.settings = TemporaryScheduleOverrideSettings(
  130. targetRange: ClosedRange<HKQuantity>.init(
  131. uncheckedBounds: (
  132. lower: max(quantity, targetRange.lowerBound),
  133. upper: max(quantity, targetRange.upperBound))),
  134. insulinNeedsScaleFactor: preset.settings.insulinNeedsScaleFactor)
  135. return newPreset
  136. } else {
  137. return preset
  138. }
  139. }
  140. }
  141. delegate?.saveCompletion(therapySettings: therapySettings)
  142. }
  143. public func saveBasalRates(basalRates: BasalRateSchedule) {
  144. therapySettings.basalRateSchedule = basalRates
  145. delegate?.saveCompletion(therapySettings: therapySettings)
  146. }
  147. public func saveDeliveryLimits(limits: DeliveryLimits) {
  148. therapySettings.maximumBasalRatePerHour = limits.maximumBasalRate?.doubleValue(for: .internationalUnitsPerHour)
  149. therapySettings.maximumBolus = limits.maximumBolus?.doubleValue(for: .internationalUnit())
  150. delegate?.saveCompletion(therapySettings: therapySettings)
  151. }
  152. public func saveInsulinModel(insulinModelPreset: ExponentialInsulinModelPreset) {
  153. therapySettings.defaultRapidActingModel = insulinModelPreset
  154. delegate?.saveCompletion(therapySettings: therapySettings)
  155. }
  156. public func saveCarbRatioSchedule(carbRatioSchedule: CarbRatioSchedule) {
  157. therapySettings.carbRatioSchedule = carbRatioSchedule
  158. delegate?.saveCompletion(therapySettings: therapySettings)
  159. }
  160. public func saveInsulinSensitivitySchedule(insulinSensitivitySchedule: InsulinSensitivitySchedule) {
  161. therapySettings.insulinSensitivitySchedule = insulinSensitivitySchedule
  162. delegate?.saveCompletion(therapySettings: therapySettings)
  163. }
  164. }