ProfileManager.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // ProfileManager.swift
  2. // LoopFollow
  3. // Created by Jonas Björkert on 2024-07-12.
  4. // Copyright © 2024 Jon Fawcett. All rights reserved.
  5. import Foundation
  6. import HealthKit
  7. struct ProfileManager {
  8. var isfSchedule: [TimeValue<HKQuantity>]
  9. var basalSchedule: [TimeValue<Double>]
  10. var carbRatioSchedule: [TimeValue<Double>]
  11. var targetLowSchedule: [TimeValue<HKQuantity>]
  12. var targetHighSchedule: [TimeValue<HKQuantity>]
  13. var overrides: [Override]
  14. var units: HKUnit
  15. var timezone: TimeZone
  16. var defaultProfile: String
  17. struct TimeValue<T> {
  18. let timeAsSeconds: Int
  19. let value: T
  20. }
  21. struct Override {
  22. let name: String
  23. let targetRange: [HKQuantity]
  24. let duration: Int
  25. let insulinNeedsScaleFactor: Double
  26. let symbol: String
  27. }
  28. init() {
  29. self.isfSchedule = []
  30. self.basalSchedule = []
  31. self.carbRatioSchedule = []
  32. self.targetLowSchedule = []
  33. self.targetHighSchedule = []
  34. self.overrides = []
  35. self.units = .millimolesPerLiter
  36. self.timezone = TimeZone.current
  37. self.defaultProfile = ""
  38. }
  39. mutating func loadProfile(from profileData: NSProfile) {
  40. guard let store = profileData.store[profileData.defaultProfile] else {
  41. return
  42. }
  43. self.units = store.units.lowercased() == "mg/dl" ? .milligramsPerDeciliter : .millimolesPerLiter
  44. self.defaultProfile = profileData.defaultProfile
  45. self.timezone = getTimeZone(from: store.timezone)
  46. self.isfSchedule = store.sens.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) }
  47. self.basalSchedule = store.basal.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: $0.value) }
  48. self.carbRatioSchedule = store.carbratio.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: $0.value) }
  49. self.targetLowSchedule = store.target_low?.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) } ?? []
  50. self.targetHighSchedule = store.target_high?.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) } ?? []
  51. if let overrides = store.overrides {
  52. self.overrides = overrides.map { Override(name: $0.name ?? "", targetRange: $0.targetRange?.map { HKQuantity(unit: self.units, doubleValue: $0) } ?? [], duration: $0.duration ?? 0, insulinNeedsScaleFactor: $0.insulinNeedsScaleFactor ?? 1.0, symbol: $0.symbol ?? "") }
  53. } else {
  54. self.overrides = []
  55. }
  56. }
  57. func currentISF() -> HKQuantity? {
  58. return getCurrentValue(from: isfSchedule)
  59. }
  60. func currentBasal() -> String? {
  61. if let basal = getCurrentValue(from: basalSchedule) {
  62. return Localizer.formatToLocalizedString(basal, maxFractionDigits: 2, minFractionDigits: 0)
  63. }
  64. return nil
  65. }
  66. func currentCarbRatio() -> Double? {
  67. return getCurrentValue(from: carbRatioSchedule)
  68. }
  69. func currentTargetLow() -> HKQuantity? {
  70. return getCurrentValue(from: targetLowSchedule)
  71. }
  72. func currentTargetHigh() -> HKQuantity? {
  73. return getCurrentValue(from: targetHighSchedule)
  74. }
  75. private func getCurrentValue<T>(from schedule: [TimeValue<T>]) -> T? {
  76. guard !schedule.isEmpty else { return nil }
  77. let now = Date()
  78. var calendar = Calendar.current
  79. calendar.timeZone = self.timezone
  80. let currentTimeInSeconds = calendar.component(.hour, from: now) * 3600 +
  81. calendar.component(.minute, from: now) * 60 +
  82. calendar.component(.second, from: now)
  83. var lastValue: T?
  84. for timeValue in schedule {
  85. if currentTimeInSeconds >= timeValue.timeAsSeconds {
  86. lastValue = timeValue.value
  87. } else {
  88. break
  89. }
  90. }
  91. return lastValue
  92. }
  93. private func getTimeZone(from identifier: String) -> TimeZone {
  94. if let timeZone = TimeZone(identifier: identifier) {
  95. return timeZone
  96. }
  97. let adjustedIdentifier = identifier.replacingOccurrences(of: "ETC/", with: "Etc/")
  98. if let timeZone = TimeZone(identifier: adjustedIdentifier) {
  99. return timeZone
  100. }
  101. if identifier.uppercased().contains("GMT") {
  102. let components = identifier.uppercased().components(separatedBy: "GMT")
  103. if components.count > 1 {
  104. let offsetString = components[1]
  105. if let offsetHours = Int(offsetString) {
  106. let correctedOffsetHours = -offsetHours
  107. let secondsFromGMT = correctedOffsetHours * 3600
  108. if let timeZone = TimeZone(secondsFromGMT: secondsFromGMT) {
  109. return timeZone
  110. }
  111. }
  112. }
  113. }
  114. return TimeZone.current
  115. }
  116. mutating func clear() {
  117. self.isfSchedule = []
  118. self.basalSchedule = []
  119. self.carbRatioSchedule = []
  120. self.targetLowSchedule = []
  121. self.targetHighSchedule = []
  122. self.overrides = []
  123. self.units = .millimolesPerLiter
  124. self.timezone = TimeZone.current
  125. self.defaultProfile = ""
  126. }
  127. }