import CoreData import Foundation /// Represents statistical data about bolus insulin delivery for a specific day struct BolusStats: Identifiable { let id = UUID() /// The date representing this time period let date: Date /// Total amount of manual boluses (excluding SMB and external) let manualBolus: Double /// Total amount of Super Micro Boluses (SMB) let smb: Double /// Total amount of external boluses (e.g., from pump directly) let external: Double } extension Stat.StateModel { /// Initializes and fetches bolus statistics /// /// This function: /// 1. Fetches bolus records from CoreData /// 2. Groups and processes the records into bolus statistics /// 3. Updates the bolusStats array on the main thread func setupBolusStats() { Task { let stats = await fetchBolusStats() await MainActor.run { self.bolusStats = stats } } } /// Fetches and processes bolus statistics for a specific date range /// - Returns: Array of BolusStats containing daily bolus statistics private func fetchBolusStats() async -> [BolusStats] { let calendar = Calendar.current // Fetch bolus records from Core Data let results = await CoreDataStack.shared.fetchEntitiesAsync( ofType: BolusStored.self, onContext: bolusTaskContext, predicate: NSPredicate.pumpHistoryForStats, key: "pumpEvent.timestamp", ascending: true, batchSize: 100 ) return await bolusTaskContext.perform { guard let fetchedResults = results as? [BolusStored] else { return [] } // Group boluses by day or hour depending on selected duration let groupedByTime = Dictionary(grouping: fetchedResults) { bolus -> Date in guard let timestamp = bolus.pumpEvent?.timestamp else { return Date() } if self.selectedDurationForInsulinStats == .Day { // For Day view, group by hour let components = calendar.dateComponents([.year, .month, .day, .hour], from: timestamp) return calendar.date(from: components) ?? Date() } else { // For other views, group by day return calendar.startOfDay(for: timestamp) } } // Get all unique time points let timePoints = groupedByTime.keys.sorted() // Calculate totals for each time point return timePoints.map { timePoint in let boluses = groupedByTime[timePoint, default: []] // Calculate total manual boluses (excluding SMB and external) let manualBolus = boluses .filter { !($0.isExternal || $0.isSMB) } .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue } // Calculate total SMB let smb = boluses .filter { $0.isSMB } .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue } // Calculate total external boluses let external = boluses .filter { $0.isExternal } .reduce(0.0) { $0 + (($1.amount as? Decimal) ?? 0).doubleValue } return BolusStats( date: timePoint, manualBolus: manualBolus, smb: smb, external: external ) } } } /// Calculates the average daily insulin amounts for manual boluses, SMB (Super Micro Boluses), and external boluses /// within the specified date range /// - Parameters: /// - startDate: The beginning date of the period to calculate averages for (inclusive) /// - endDate: The ending date of the period to calculate averages for (inclusive) /// - Returns: A tuple containing three values: /// - manual: Average daily amount of manual boluses /// - smb: Average daily amount of Super Micro Boluses (SMB) /// - external: Average daily amount of external boluses (entered directly on pump) /// - Note: Returns (0, 0, 0) if no data exists for the specified date range func calculateAverageBolus(from startDate: Date, to endDate: Date) -> (manual: Double, smb: Double, external: Double) { let visibleStats = bolusStats.filter { stat in stat.date >= startDate && stat.date <= endDate } guard !visibleStats.isEmpty else { return (0, 0, 0) } let count = Double(visibleStats.count) let manualSum = visibleStats.reduce(0.0) { $0 + $1.manualBolus } let smbSum = visibleStats.reduce(0.0) { $0 + $1.smb } let externalSum = visibleStats.reduce(0.0) { $0 + $1.external } return ( manualSum / count, smbSum / count, externalSum / count ) } } /// Extension to convert Decimal to Double private extension Decimal { var doubleValue: Double { NSDecimalNumber(decimal: self).doubleValue } }