BolusStatsSetup.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import CoreData
  2. import Foundation
  3. /// Represents statistical data about bolus insulin delivery for a specific day
  4. struct BolusStats: Identifiable {
  5. let id = UUID()
  6. /// The date representing this time period
  7. let date: Date
  8. /// Total amount of manual boluses (excluding SMB and external)
  9. let manualBolus: Double
  10. /// Total amount of Super Micro Boluses (SMB)
  11. let smb: Double
  12. /// Total amount of external boluses (e.g., from pump directly)
  13. let external: Double
  14. }
  15. extension Stat.StateModel {
  16. /// Initializes and fetches bolus statistics
  17. ///
  18. /// This function:
  19. /// 1. Fetches bolus records from CoreData
  20. /// 2. Groups and processes the records into bolus statistics
  21. /// 3. Updates the bolusStats array on the main thread
  22. func setupBolusStats() {
  23. Task {
  24. let stats = await fetchBolusStats()
  25. await MainActor.run {
  26. self.bolusStats = stats
  27. }
  28. }
  29. }
  30. /// Fetches and processes bolus statistics for a specific date range
  31. /// - Returns: Array of BolusStats containing daily bolus statistics
  32. private func fetchBolusStats() async -> [BolusStats] {
  33. let calendar = Calendar.current
  34. // Fetch bolus records from Core Data
  35. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  36. ofType: BolusStored.self,
  37. onContext: bolusTaskContext,
  38. predicate: NSPredicate.pumpHistoryForStats,
  39. key: "pumpEvent.timestamp",
  40. ascending: true,
  41. batchSize: 100
  42. )
  43. return await bolusTaskContext.perform {
  44. guard let fetchedResults = results as? [BolusStored] else { return [] }
  45. // Group boluses by day or hour depending on selected duration
  46. let groupedByTime = Dictionary(grouping: fetchedResults) { bolus -> Date in
  47. guard let timestamp = bolus.pumpEvent?.timestamp else { return Date() }
  48. if self.selectedDurationForInsulinStats == .Day {
  49. // For Day view, group by hour
  50. let components = calendar.dateComponents([.year, .month, .day, .hour], from: timestamp)
  51. return calendar.date(from: components) ?? Date()
  52. } else {
  53. // For other views, group by day
  54. return calendar.startOfDay(for: timestamp)
  55. }
  56. }
  57. // Get all unique time points
  58. let timePoints = groupedByTime.keys.sorted()
  59. // Calculate totals for each time point
  60. return timePoints.map { timePoint in
  61. let boluses = groupedByTime[timePoint, default: []]
  62. // Calculate total manual boluses (excluding SMB and external)
  63. let manualBolus = boluses
  64. .filter { !($0.isExternal || $0.isSMB) }
  65. .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue }
  66. // Calculate total SMB
  67. let smb = boluses
  68. .filter { $0.isSMB }
  69. .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue }
  70. // Calculate total external boluses
  71. let external = boluses
  72. .filter { $0.isExternal }
  73. .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue }
  74. return BolusStats(
  75. date: timePoint,
  76. manualBolus: manualBolus,
  77. smb: smb,
  78. external: external
  79. )
  80. }
  81. }
  82. }
  83. /// Calculates the average daily insulin amounts for manual boluses, SMB (Super Micro Boluses), and external boluses
  84. /// within the specified date range
  85. /// - Parameters:
  86. /// - startDate: The beginning date of the period to calculate averages for (inclusive)
  87. /// - endDate: The ending date of the period to calculate averages for (inclusive)
  88. /// - Returns: A tuple containing three values:
  89. /// - manual: Average daily amount of manual boluses
  90. /// - smb: Average daily amount of Super Micro Boluses (SMB)
  91. /// - external: Average daily amount of external boluses (entered directly on pump)
  92. /// - Note: Returns (0, 0, 0) if no data exists for the specified date range
  93. func calculateAverageBolus(from startDate: Date, to endDate: Date) -> (manual: Double, smb: Double, external: Double) {
  94. let visibleStats = bolusStats.filter { stat in
  95. stat.date >= startDate && stat.date <= endDate
  96. }
  97. guard !visibleStats.isEmpty else { return (0, 0, 0) }
  98. let count = Double(visibleStats.count)
  99. let manualSum = visibleStats.reduce(0.0) { $0 + $1.manualBolus }
  100. let smbSum = visibleStats.reduce(0.0) { $0 + $1.smb }
  101. let externalSum = visibleStats.reduce(0.0) { $0 + $1.external }
  102. return (
  103. manualSum / count,
  104. smbSum / count,
  105. externalSum / count
  106. )
  107. }
  108. }
  109. /// Extension to convert Decimal to Double
  110. private extension Decimal {
  111. var doubleValue: Double {
  112. NSDecimalNumber(decimal: self).doubleValue
  113. }
  114. }