DataAvailabilityCalculator.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. // LoopFollow
  2. // DataAvailabilityCalculator.swift
  3. import Foundation
  4. struct DataAvailabilityInfo {
  5. let totalExpectedReadings: Int
  6. let actualReadings: Int
  7. let coveragePercentage: Double
  8. let missingIntervals: Int
  9. let dataQuality: DataQuality
  10. enum DataQuality {
  11. case excellent // >= 95%
  12. case good // >= 70%
  13. case fair // >= 50%
  14. case poor // < 50%
  15. var color: String {
  16. switch self {
  17. case .excellent: return "green"
  18. case .good: return "green"
  19. case .fair: return "orange"
  20. case .poor: return "red"
  21. }
  22. }
  23. var description: String {
  24. switch self {
  25. case .excellent: return "Excellent"
  26. case .good: return "Good"
  27. case .fair: return "Fair"
  28. case .poor: return "Poor"
  29. }
  30. }
  31. }
  32. var displayText: String {
  33. return String(format: "%.1f%% (%d/%d readings)",
  34. coveragePercentage,
  35. actualReadings,
  36. totalExpectedReadings)
  37. }
  38. }
  39. class DataAvailabilityCalculator {
  40. /// Calculates data availability based on expected CGM readings every 5 minutes
  41. /// - Parameters:
  42. /// - bgData: Array of glucose readings
  43. /// - startDate: Start of the analysis period
  44. /// - endDate: End of the analysis period
  45. /// - Returns: DataAvailabilityInfo with coverage statistics
  46. static func calculateAvailability(
  47. bgData: [ShareGlucoseData],
  48. startDate: Date,
  49. endDate: Date
  50. ) -> DataAvailabilityInfo {
  51. let intervalMinutes = 5.0
  52. let totalMinutes = endDate.timeIntervalSince(startDate) / 60.0
  53. let expectedReadings = Int(totalMinutes / intervalMinutes)
  54. let startTimestamp = startDate.timeIntervalSince1970
  55. let endTimestamp = endDate.timeIntervalSince1970
  56. let relevantData = bgData.filter { reading in
  57. reading.date >= startTimestamp && reading.date <= endTimestamp
  58. }.sorted { $0.date < $1.date }
  59. guard !relevantData.isEmpty else {
  60. return DataAvailabilityInfo(
  61. totalExpectedReadings: max(expectedReadings, 0),
  62. actualReadings: 0,
  63. coveragePercentage: 0.0,
  64. missingIntervals: max(expectedReadings, 0),
  65. dataQuality: .poor
  66. )
  67. }
  68. var coveredIntervals = Set<Int>()
  69. for reading in relevantData {
  70. let readingTime = Date(timeIntervalSince1970: reading.date)
  71. let minutesSinceStart = readingTime.timeIntervalSince(startDate) / 60.0
  72. let intervalIndex = Int(minutesSinceStart / intervalMinutes)
  73. if intervalIndex >= 0 && intervalIndex < expectedReadings {
  74. coveredIntervals.insert(intervalIndex)
  75. }
  76. }
  77. let actualCoveredIntervals = coveredIntervals.count
  78. let coveragePercentage = expectedReadings > 0
  79. ? (Double(actualCoveredIntervals) / Double(expectedReadings)) * 100.0
  80. : 0.0
  81. let missingIntervals = expectedReadings - actualCoveredIntervals
  82. let quality: DataAvailabilityInfo.DataQuality
  83. if coveragePercentage >= 95.0 {
  84. quality = .excellent
  85. } else if coveragePercentage >= 70.0 {
  86. quality = .good
  87. } else if coveragePercentage >= 50.0 {
  88. quality = .fair
  89. } else {
  90. quality = .poor
  91. }
  92. return DataAvailabilityInfo(
  93. totalExpectedReadings: expectedReadings,
  94. actualReadings: actualCoveredIntervals,
  95. coveragePercentage: coveragePercentage,
  96. missingIntervals: missingIntervals,
  97. dataQuality: quality
  98. )
  99. }
  100. }