StatStateModel.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import CoreData
  2. import Foundation
  3. import Observation
  4. import SwiftUI
  5. import Swinject
  6. extension Stat {
  7. @Observable final class StateModel: BaseStateModel<Provider> {
  8. @ObservationIgnored @Injected() var settings: SettingsManager!
  9. var highLimit: Decimal = 180
  10. var lowLimit: Decimal = 70
  11. var hbA1cDisplayUnit: HbA1cDisplayUnit = .percent
  12. var timeInRangeChartStyle: TimeInRangeChartStyle = .vertical
  13. var units: GlucoseUnits = .mgdL
  14. var glucoseFromPersistence: [GlucoseStored] = []
  15. var loopStatRecords: [LoopStatRecord] = []
  16. var groupedLoopStats: [LoopStatsByPeriod] = []
  17. var mealStats: [MealStats] = []
  18. var selectedDuration: Duration = .Today {
  19. didSet {
  20. setupGlucoseArray(for: selectedDuration)
  21. }
  22. }
  23. var selectedDurationForLoopStats: Duration = .Today {
  24. didSet {
  25. setupLoopStatRecords()
  26. }
  27. }
  28. var selectedDurationForMealStats: Duration = .Today {
  29. didSet {
  30. setupMealStats(for: selectedDurationForMealStats)
  31. }
  32. }
  33. /// TDD-related properties
  34. /// Total insulin dose for the last 24 hours
  35. var currentTDD: Decimal = 0
  36. /// Total insulin dose for yesterday (previous calendar day)
  37. var ytdTDDValue: Decimal = 0
  38. /// Average TDD for the selected time period
  39. var averageTDD: Decimal = 0
  40. /// Array of daily total doses for the selected period
  41. var dailyTotalDoses: [TDD] = []
  42. /// Configuration for TDD display and calculations
  43. private(set) var tddConfig = TDDConfiguration() {
  44. didSet {
  45. if oldValue.requestedDays != tddConfig.requestedDays ||
  46. oldValue.endDate != tddConfig.endDate
  47. {
  48. Task {
  49. await updateTDDValues()
  50. }
  51. }
  52. }
  53. }
  54. /// Number of days to display in the TDD chart
  55. var requestedDaysTDD: Int {
  56. get { tddConfig.requestedDays }
  57. set { tddConfig.requestedDays = newValue }
  58. }
  59. /// End date for the TDD chart
  60. var requestedEndDayTDD: Date {
  61. get { tddConfig.endDate }
  62. set {
  63. if let adjustedDate = Calendar.current.date(
  64. bySettingHour: 23,
  65. minute: 59,
  66. second: 59,
  67. of: newValue
  68. ) {
  69. tddConfig.endDate = adjustedDate
  70. }
  71. }
  72. }
  73. let context = CoreDataStack.shared.newTaskContext()
  74. let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  75. let determinationFetchContext = CoreDataStack.shared.newTaskContext()
  76. let loopTaskContext = CoreDataStack.shared.newTaskContext()
  77. let mealTaskContext = CoreDataStack.shared.newTaskContext()
  78. let bolusTaskContext = CoreDataStack.shared.newTaskContext()
  79. enum Duration: String, CaseIterable, Identifiable {
  80. case Today
  81. case Day = "D"
  82. case Week = "W"
  83. case Month = "M"
  84. case Total = "3 M."
  85. var id: Self { self }
  86. }
  87. var hourlyStats: [HourlyStats] = []
  88. var glucoseRangeStats: [GlucoseRangeStats] = []
  89. var bolusStats: [BolusStats] = []
  90. override func subscribe() {
  91. setupGlucoseArray(for: .Today)
  92. setupTDDs()
  93. setupLoopStatRecords()
  94. setupMealStats(for: selectedDurationForMealStats)
  95. updateBolusStats()
  96. highLimit = settingsManager.settings.high
  97. lowLimit = settingsManager.settings.low
  98. units = settingsManager.settings.units
  99. hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
  100. timeInRangeChartStyle = settingsManager.settings.timeInRangeChartStyle
  101. }
  102. func setupGlucoseArray(for duration: Duration) {
  103. Task {
  104. let ids = await fetchGlucose(for: duration)
  105. await updateGlucoseArray(with: ids)
  106. // Calculate hourly stats and glucose range stats asynchronously with fetched glucose IDs
  107. async let hourlyStats: () = calculateHourlyStatsForGlucoseAreaChart(from: ids)
  108. async let glucoseRangeStats: () = calculateGlucoseRangeStatsForStackedChart(from: ids)
  109. _ = await (hourlyStats, glucoseRangeStats)
  110. }
  111. }
  112. func setupTDDs() {
  113. Task {
  114. await updateTDDValues()
  115. }
  116. }
  117. private func fetchGlucose(for duration: Duration) async -> [NSManagedObjectID] {
  118. let predicate: NSPredicate
  119. switch duration {
  120. case .Day:
  121. predicate = NSPredicate.glucoseForStatsDay
  122. case .Week:
  123. predicate = NSPredicate.glucoseForStatsWeek
  124. case .Today:
  125. predicate = NSPredicate.glucoseForStatsToday
  126. case .Month:
  127. predicate = NSPredicate.glucoseForStatsMonth
  128. case .Total:
  129. predicate = NSPredicate.glucoseForStatsTotal
  130. }
  131. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  132. ofType: GlucoseStored.self,
  133. onContext: context,
  134. predicate: predicate,
  135. key: "date",
  136. ascending: false,
  137. batchSize: 100,
  138. propertiesToFetch: ["glucose", "objectID"]
  139. )
  140. return await context.perform {
  141. guard let fetchedResults = results as? [[String: Any]] else { return [] }
  142. return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }
  143. }
  144. }
  145. @MainActor private func updateGlucoseArray(with IDs: [NSManagedObjectID]) {
  146. do {
  147. let glucoseObjects = try IDs.compactMap { id in
  148. try viewContext.existingObject(with: id) as? GlucoseStored
  149. }
  150. glucoseFromPersistence = glucoseObjects
  151. } catch {
  152. debugPrint(
  153. "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
  154. )
  155. }
  156. }
  157. }
  158. }