| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import CoreData
- import Foundation
- /// Represents statistical data about meal macronutrients for a specific day
- struct MealStats: Identifiable {
- let id = UUID()
- /// The date representing this time period
- let date: Date
- /// Total carbohydrates in grams
- let carbs: Double
- /// Total fat in grams
- let fat: Double
- /// Total protein in grams
- let protein: Double
- }
- extension Stat.StateModel {
- /// Initializes and fetches meal statistics
- ///
- /// This function:
- /// 1. Fetches carbohydrate records from CoreData
- /// 2. Groups and processes the records into meal statistics
- /// 3. Updates the mealStats array on the main thread
- func setupMealStats() {
- Task {
- let stats = await fetchMealStats()
- await MainActor.run {
- self.mealStats = stats
- }
- }
- }
- /// Fetches and processes meal statistics for a specific duration
- /// - Returns: Array of MealStats containing daily meal statistics, sorted by date
- ///
- /// This function:
- /// 1. Fetches carbohydrate entries from CoreData
- /// 2. Groups entries by day or hour based on selected duration
- /// 3. Calculates total macronutrients for each time period
- ///
- /// The grouping logic:
- /// - For Day view: Groups by hour to show meal distribution
- /// - For other views: Groups by day to show daily totals
- private func fetchMealStats() async -> [MealStats] {
- // Fetch CarbEntryStored entries from Core Data
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
- ofType: CarbEntryStored.self,
- onContext: mealTaskContext,
- predicate: NSPredicate.carbsForStats,
- key: "date",
- ascending: true,
- batchSize: 100
- )
- return await mealTaskContext.perform {
- // Safely unwrap the fetched results, return empty array if nil
- guard let fetchedResults = results as? [CarbEntryStored] else { return [] }
- let calendar = Calendar.current
- // Group entries by day or hour depending on selected duration
- let groupedEntries = Dictionary(grouping: fetchedResults) { entry in
- if self.selectedDurationForMealStats == .Day {
- // For Day view, group by hour
- let components = calendar.dateComponents([.year, .month, .day, .hour], from: entry.date ?? Date())
- return calendar.date(from: components) ?? Date()
- } else {
- // For other views, group by day
- return calendar.startOfDay(for: entry.date ?? Date())
- }
- }
- // Get all unique dates/hours from the entries
- let timePoints = groupedEntries.keys.sorted()
- // Calculate statistics for each time point
- return timePoints.map { timePoint in
- let entries = groupedEntries[timePoint, default: []]
- let carbsTotal = entries.reduce(0.0) { $0 + $1.carbs }
- let fatTotal = entries.reduce(0.0) { $0 + $1.fat }
- let proteinTotal = entries.reduce(0.0) { $0 + $1.protein }
- return MealStats(
- date: timePoint,
- carbs: carbsTotal,
- fat: fatTotal,
- protein: proteinTotal
- )
- }
- }
- }
- /// Calculates average meal statistics for a specified date range
- /// - Parameters:
- /// - startDate: Start date of the range
- /// - endDate: End date of the range
- /// - Returns: Tuple containing average values for carbs, fat, and protein
- ///
- /// The calculation process:
- /// 1. Filters meal records within the date range
- /// 2. Calculates total values for each macronutrient
- /// 3. Divides totals by number of records to get averages
- /// 4. Returns (0,0,0) if no records are found
- func calculateAverageMealStats(
- from startDate: Date,
- to endDate: Date
- ) async -> (carbs: Double, fat: Double, protein: Double) {
- let filteredStats = mealStats.filter { stat in
- stat.date >= startDate && stat.date <= endDate
- }
- guard !filteredStats.isEmpty else { return (0, 0, 0) }
- let totalCarbs = filteredStats.reduce(0.0) { $0 + $1.carbs }
- let totalFat = filteredStats.reduce(0.0) { $0 + $1.fat }
- let totalProtein = filteredStats.reduce(0.0) { $0 + $1.protein }
- let count = Double(filteredStats.count)
- return (
- carbs: totalCarbs / count,
- fat: totalFat / count,
- protein: totalProtein / count
- )
- }
- }
|