StatsDataService.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. // Also update daysToAnalyze for compatibility with existing code
  28. let daysBetween = dateTimeUtils.displayCalendar().dateComponents([.day], from: start, to: end).day ?? 14
  29. daysToAnalyze = max(daysBetween, 1)
  30. }
  31. func ensureDataAvailable(onProgress: @escaping () -> Void, completion: @escaping () -> Void) {
  32. guard let mainVC = mainViewController else {
  33. completion()
  34. return
  35. }
  36. let cutoffTime = startDate.timeIntervalSince1970
  37. let endTime = endDate.timeIntervalSince1970
  38. let oldestBG = mainVC.statsBGData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  39. let oldestBolus = mainVC.statsBolusData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  40. let oldestCarb = mainVC.statsCarbData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  41. let oldestBasal = mainVC.statsBasalData.filter { $0.date >= cutoffTime && $0.date <= endTime }.min(by: { $0.date < $1.date })?.date
  42. let bgDataCount = mainVC.statsBGData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  43. let bolusDataCount = mainVC.statsBolusData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  44. let carbDataCount = mainVC.statsCarbData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  45. let basalDataCount = mainVC.statsBasalData.filter { $0.date >= cutoffTime && $0.date <= endTime }.count
  46. let minExpectedBGEntries = max(daysToAnalyze * 6, 12)
  47. let hasEnoughBGData = bgDataCount >= minExpectedBGEntries && (oldestBG ?? endTime) <= cutoffTime + (24 * 60 * 60)
  48. let minExpectedTreatmentEntries = max(daysToAnalyze, 1)
  49. let hasEnoughTreatmentData = (bolusDataCount + carbDataCount + basalDataCount) >= minExpectedTreatmentEntries &&
  50. (oldestBolus ?? endTime) <= cutoffTime + (24 * 60 * 60) &&
  51. (oldestCarb ?? endTime) <= cutoffTime + (24 * 60 * 60) &&
  52. (oldestBasal ?? endTime) <= cutoffTime + (24 * 60 * 60)
  53. if !hasEnoughBGData {
  54. dataFetcher.fetchBGData(days: daysToAnalyze) {
  55. DispatchQueue.main.async {
  56. onProgress()
  57. }
  58. if !hasEnoughTreatmentData {
  59. self.dataFetcher.fetchTreatmentsData(days: self.daysToAnalyze) {
  60. DispatchQueue.main.async {
  61. onProgress()
  62. completion()
  63. }
  64. }
  65. } else {
  66. completion()
  67. }
  68. }
  69. } else if !hasEnoughTreatmentData {
  70. dataFetcher.fetchTreatmentsData(days: daysToAnalyze) {
  71. DispatchQueue.main.async {
  72. onProgress()
  73. completion()
  74. }
  75. }
  76. } else {
  77. completion()
  78. }
  79. }
  80. func getBGData() -> [ShareGlucoseData] {
  81. guard let mainVC = mainViewController else { return [] }
  82. let startTime = startDate.timeIntervalSince1970
  83. let endTime = endDate.timeIntervalSince1970
  84. return mainVC.statsBGData.filter { $0.date >= startTime && $0.date <= endTime }
  85. }
  86. func getBolusData() -> [MainViewController.bolusGraphStruct] {
  87. guard let mainVC = mainViewController else { return [] }
  88. let startTime = startDate.timeIntervalSince1970
  89. let endTime = endDate.timeIntervalSince1970
  90. return mainVC.statsBolusData.filter { $0.date >= startTime && $0.date <= endTime }
  91. }
  92. func getSMBData() -> [MainViewController.bolusGraphStruct] {
  93. guard let mainVC = mainViewController else { return [] }
  94. let startTime = startDate.timeIntervalSince1970
  95. let endTime = endDate.timeIntervalSince1970
  96. return mainVC.statsSMBData.filter { $0.date >= startTime && $0.date <= endTime }
  97. }
  98. func getCarbData() -> [MainViewController.carbGraphStruct] {
  99. guard let mainVC = mainViewController else { return [] }
  100. let startTime = startDate.timeIntervalSince1970
  101. let endTime = endDate.timeIntervalSince1970
  102. return mainVC.statsCarbData.filter { $0.date >= startTime && $0.date <= endTime }
  103. }
  104. func getBasalData() -> [MainViewController.basalGraphStruct] {
  105. guard let mainVC = mainViewController else { return [] }
  106. let startTime = startDate.timeIntervalSince1970
  107. let endTime = endDate.timeIntervalSince1970
  108. return mainVC.statsBasalData.filter { $0.date >= startTime && $0.date <= endTime }
  109. }
  110. func getBasalProfile() -> [MainViewController.basalProfileStruct] {
  111. guard let mainVC = mainViewController else { return [] }
  112. return mainVC.basalProfile
  113. }
  114. func getTempBasalData() -> [TempBasalEntry] {
  115. let startTime = startDate.timeIntervalSince1970
  116. let endTime = endDate.timeIntervalSince1970
  117. return tempBasalEntries.filter { $0.startTime >= startTime && $0.startTime <= endTime }
  118. }
  119. /// Calculate data availability for the current date range
  120. func getDataAvailability() -> DataAvailabilityInfo {
  121. let bgData = getBGData()
  122. return DataAvailabilityCalculator.calculateAvailability(
  123. bgData: bgData,
  124. startDate: startDate,
  125. endDate: endDate
  126. )
  127. }
  128. }