BloodGlucose.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import Foundation
  2. import HealthKit
  3. import LoopKit
  4. struct BloodGlucose: JSON, Identifiable, Hashable {
  5. enum Direction: String, JSON {
  6. case tripleUp = "TripleUp"
  7. case doubleUp = "DoubleUp"
  8. case singleUp = "SingleUp"
  9. case fortyFiveUp = "FortyFiveUp"
  10. case flat = "Flat"
  11. case fortyFiveDown = "FortyFiveDown"
  12. case singleDown = "SingleDown"
  13. case doubleDown = "DoubleDown"
  14. case tripleDown = "TripleDown"
  15. case none = "NONE"
  16. case notComputable = "NOT COMPUTABLE"
  17. case rateOutOfRange = "RATE OUT OF RANGE"
  18. init?(from string: String) {
  19. switch string {
  20. case "\u{2191}\u{2191}\u{2191}",
  21. "TripleUp":
  22. self = .tripleUp
  23. case "\u{2191}\u{2191}",
  24. "DoubleUp":
  25. self = .doubleUp
  26. case "\u{2191}",
  27. "SingleUp":
  28. self = .singleUp
  29. case "\u{2197}",
  30. "FortyFiveUp":
  31. self = .fortyFiveUp
  32. case "\u{2192}",
  33. "Flat":
  34. self = .flat
  35. case "\u{2198}",
  36. "FortyFiveDown":
  37. self = .fortyFiveDown
  38. case "\u{2193}",
  39. "SingleDown":
  40. self = .singleDown
  41. case "\u{2193}\u{2193}",
  42. "DoubleDown":
  43. self = .doubleDown
  44. case "\u{2193}\u{2193}\u{2193}",
  45. "TripleDown":
  46. self = .tripleDown
  47. case "\u{2194}",
  48. "NONE":
  49. self = .none
  50. case "NOT COMPUTABLE":
  51. self = .notComputable
  52. case "RATE OUT OF RANGE":
  53. self = .rateOutOfRange
  54. default:
  55. return nil
  56. }
  57. }
  58. }
  59. var _id: String?
  60. var id: String {
  61. _id ?? UUID().uuidString
  62. }
  63. var sgv: Int?
  64. var direction: Direction?
  65. let date: Decimal
  66. let dateString: Date
  67. let unfiltered: Decimal?
  68. let filtered: Decimal?
  69. let noise: Int?
  70. var glucose: Int?
  71. var type: String? = nil
  72. var activationDate: Date? = nil
  73. var sessionStartDate: Date? = nil
  74. var transmitterID: String? = nil
  75. var isStateValid: Bool { sgv ?? 0 >= 39 && noise ?? 1 != 4 }
  76. static func == (lhs: BloodGlucose, rhs: BloodGlucose) -> Bool {
  77. lhs.dateString == rhs.dateString
  78. }
  79. func hash(into hasher: inout Hasher) {
  80. hasher.combine(dateString)
  81. }
  82. }
  83. enum GlucoseUnits: String, JSON, Equatable {
  84. case mgdL = "mg/dL"
  85. case mmolL = "mmol/L"
  86. static let exchangeRate: Decimal = 0.0555
  87. }
  88. extension Int {
  89. var asMmolL: Decimal {
  90. FreeAPS.rounded(Decimal(self) * GlucoseUnits.exchangeRate, scale: 1, roundingMode: .plain)
  91. }
  92. var formattedAsMmolL: String {
  93. NumberFormatter.glucoseFormatter.string(from: asMmolL as NSDecimalNumber) ?? "\(asMmolL)"
  94. }
  95. }
  96. extension Decimal {
  97. var asMmolL: Decimal {
  98. FreeAPS.rounded(self * GlucoseUnits.exchangeRate, scale: 1, roundingMode: .plain)
  99. }
  100. var asMgdL: Decimal {
  101. FreeAPS.rounded(self / GlucoseUnits.exchangeRate, scale: 0, roundingMode: .plain)
  102. }
  103. var formattedAsMmolL: String {
  104. NumberFormatter.glucoseFormatter.string(from: asMmolL as NSDecimalNumber) ?? "\(asMmolL)"
  105. }
  106. }
  107. extension Double {
  108. var asMmolL: Decimal {
  109. FreeAPS.rounded(Decimal(self) * GlucoseUnits.exchangeRate, scale: 1, roundingMode: .plain)
  110. }
  111. var asMgdL: Decimal {
  112. FreeAPS.rounded(Decimal(self) / GlucoseUnits.exchangeRate, scale: 0, roundingMode: .plain)
  113. }
  114. var formattedAsMmolL: String {
  115. NumberFormatter.glucoseFormatter.string(from: asMmolL as NSDecimalNumber) ?? "\(asMmolL)"
  116. }
  117. }
  118. extension NumberFormatter {
  119. static let glucoseFormatter: NumberFormatter = {
  120. let formatter = NumberFormatter()
  121. formatter.locale = Locale.current
  122. formatter.numberStyle = .decimal
  123. formatter.minimumFractionDigits = 1
  124. formatter.maximumFractionDigits = 1
  125. return formatter
  126. }()
  127. }
  128. extension BloodGlucose: SavitzkyGolaySmoothable {
  129. var value: Double {
  130. get {
  131. Double(glucose ?? 0)
  132. }
  133. set {
  134. glucose = Int(newValue)
  135. sgv = Int(newValue)
  136. }
  137. }
  138. }
  139. extension BloodGlucose {
  140. func convertStoredGlucoseSample(device: HKDevice?) -> StoredGlucoseSample {
  141. StoredGlucoseSample(
  142. syncIdentifier: id,
  143. startDate: dateString.date,
  144. quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucose!)),
  145. device: device
  146. )
  147. }
  148. }