StatsDataService.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // LoopFollow
  2. // StatsDataService.swift
  3. import Foundation
  4. class StatsDataService {
  5. weak var mainViewController: MainViewController?
  6. var daysToAnalyze: Int = 14 // Keep for backward compatibility
  7. var startDate: Date = dateTimeUtils.displayCalendar().date(byAdding: .day, value: -14, to: Date()) ?? Date()
  8. var endDate: Date = .init()
  9. private let dataFetcher: StatsDataFetcher
  10. /// Stores raw temp basal entries with rate and duration
  11. var tempBasalEntries: [TempBasalEntry] = []
  12. /// Structure to hold temp basal data for calculations
  13. struct TempBasalEntry {
  14. let rate: Double // U/hr (absolute rate)
  15. let startTime: TimeInterval // Unix timestamp
  16. let durationMinutes: Double // Duration in minutes
  17. }
  18. init(mainViewController: MainViewController?) {
  19. self.mainViewController = mainViewController
  20. dataFetcher = StatsDataFetcher(mainViewController: mainViewController)
  21. dataFetcher.dataService = self
  22. }
  23. /// Update the date range for analysis
  24. func updateDateRange(start: Date, end: Date) {
  25. startDate = start
  26. endDate = end
  27. let calendar = dateTimeUtils.displayCalendar()
  28. let startDay = calendar.startOfDay(for: start)
  29. let endDay = calendar.startOfDay(for: end)
  30. let daysBetween = calendar.dateComponents([.day], from: startDay, to: endDay).day ?? 13
  31. daysToAnalyze = daysBetween + 1
  32. }
  33. func ensureDataAvailable(onProgress: @escaping () -> Void, completion: @escaping () -> Void) {
  34. guard let mainVC = mainViewController else {
  35. completion()
  36. return
  37. }
  38. let cutoffTime = startDate.timeIntervalSince1970
  39. let endTime = endDate.timeIntervalSince1970
  40. let oldestBG = mainVC.statsBGData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  41. let oldestBolus = mainVC.statsBolusData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  42. let oldestCarb = mainVC.statsCarbData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  43. let oldestBasal = mainVC.statsBasalData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  44. let bgDataCount = mainVC.statsBGData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  45. let bolusDataCount = mainVC.statsBolusData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  46. let carbDataCount = mainVC.statsCarbData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  47. let basalDataCount = mainVC.statsBasalData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  48. let minExpectedBGEntries = max(daysToAnalyze * 6, 12)
  49. let hasEnoughBGData = bgDataCount >= minExpectedBGEntries && (oldestBG ?? endTime) <= cutoffTime + (24 * 60 * 60)
  50. let minExpectedTreatmentEntries = max(daysToAnalyze, 1)
  51. let hasEnoughTreatmentData = (bolusDataCount + carbDataCount + basalDataCount) >= minExpectedTreatmentEntries &&
  52. (oldestBolus ?? endTime) <= cutoffTime + (24 * 60 * 60) &&
  53. (oldestCarb ?? endTime) <= cutoffTime + (24 * 60 * 60) &&
  54. (oldestBasal ?? endTime) <= cutoffTime + (24 * 60 * 60)
  55. if !hasEnoughBGData {
  56. dataFetcher.fetchBGData(days: daysToAnalyze) {
  57. DispatchQueue.main.async {
  58. onProgress()
  59. }
  60. if !hasEnoughTreatmentData {
  61. self.dataFetcher.fetchTreatmentsData(days: self.daysToAnalyze) {
  62. DispatchQueue.main.async {
  63. onProgress()
  64. completion()
  65. }
  66. }
  67. } else {
  68. completion()
  69. }
  70. }
  71. } else if !hasEnoughTreatmentData {
  72. dataFetcher.fetchTreatmentsData(days: daysToAnalyze) {
  73. DispatchQueue.main.async {
  74. onProgress()
  75. completion()
  76. }
  77. }
  78. } else {
  79. completion()
  80. }
  81. }
  82. func getBGData() -> [ShareGlucoseData] {
  83. guard let mainVC = mainViewController else { return [] }
  84. let startTime = startDate.timeIntervalSince1970
  85. let endTime = endDate.timeIntervalSince1970
  86. return mainVC.statsBGData.filter { $0.date >= startTime && $0.date <= endTime }
  87. }
  88. func getBolusData() -> [MainViewController.bolusGraphStruct] {
  89. guard let mainVC = mainViewController else { return [] }
  90. let startTime = startDate.timeIntervalSince1970
  91. let endTime = endDate.timeIntervalSince1970
  92. return mainVC.statsBolusData.filter { $0.date >= startTime && $0.date <= endTime }
  93. }
  94. func getSMBData() -> [MainViewController.bolusGraphStruct] {
  95. guard let mainVC = mainViewController else { return [] }
  96. let startTime = startDate.timeIntervalSince1970
  97. let endTime = endDate.timeIntervalSince1970
  98. return mainVC.statsSMBData.filter { $0.date >= startTime && $0.date <= endTime }
  99. }
  100. func getCarbData() -> [MainViewController.carbGraphStruct] {
  101. guard let mainVC = mainViewController else { return [] }
  102. let startTime = startDate.timeIntervalSince1970
  103. let endTime = endDate.timeIntervalSince1970
  104. return mainVC.statsCarbData.filter { $0.date >= startTime && $0.date <= endTime }
  105. }
  106. func getBasalData() -> [MainViewController.basalGraphStruct] {
  107. guard let mainVC = mainViewController else { return [] }
  108. let startTime = startDate.timeIntervalSince1970
  109. let endTime = endDate.timeIntervalSince1970
  110. return mainVC.statsBasalData.filter { $0.date >= startTime && $0.date <= endTime }
  111. }
  112. func getBasalProfile() -> [MainViewController.basalProfileStruct] {
  113. guard let mainVC = mainViewController else { return [] }
  114. return mainVC.basalProfile
  115. }
  116. func getTempBasalData() -> [TempBasalEntry] {
  117. let startTime = startDate.timeIntervalSince1970
  118. let endTime = endDate.timeIntervalSince1970
  119. return tempBasalEntries.filter { $0.startTime >= startTime && $0.startTime <= endTime }
  120. }
  121. /// Calculate data availability for the current date range
  122. func getDataAvailability() -> DataAvailabilityInfo {
  123. let bgData = getBGData()
  124. return DataAvailabilityCalculator.calculateAvailability(
  125. bgData: bgData,
  126. startDate: startDate,
  127. endDate: endDate
  128. )
  129. }
  130. }