MealStatsSetup.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import CoreData
  2. import Foundation
  3. /// Represents statistical data about meal macronutrients for a specific day
  4. struct MealStats: Identifiable {
  5. let id = UUID()
  6. /// The date representing this time period
  7. let date: Date
  8. /// Total carbohydrates in grams
  9. let carbs: Double
  10. /// Total fat in grams
  11. let fat: Double
  12. /// Total protein in grams
  13. let protein: Double
  14. }
  15. extension Stat.StateModel {
  16. /// Initializes and fetches meal statistics
  17. ///
  18. /// This function:
  19. /// 1. Fetches carbohydrate records from CoreData
  20. /// 2. Groups and processes the records into meal statistics
  21. /// 3. Updates the mealStats array on the main thread
  22. func setupMealStats() {
  23. Task {
  24. let stats = await fetchMealStats()
  25. await MainActor.run {
  26. self.mealStats = stats
  27. }
  28. }
  29. }
  30. /// Fetches and processes meal statistics for a specific duration
  31. /// - Returns: Array of MealStats containing daily meal statistics, sorted by date
  32. ///
  33. /// This function:
  34. /// 1. Fetches carbohydrate entries from CoreData
  35. /// 2. Groups entries by day or hour based on selected duration
  36. /// 3. Calculates total macronutrients for each time period
  37. ///
  38. /// The grouping logic:
  39. /// - For Day view: Groups by hour to show meal distribution
  40. /// - For other views: Groups by day to show daily totals
  41. private func fetchMealStats() async -> [MealStats] {
  42. // Fetch CarbEntryStored entries from Core Data
  43. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  44. ofType: CarbEntryStored.self,
  45. onContext: mealTaskContext,
  46. predicate: NSPredicate.carbsForStats,
  47. key: "date",
  48. ascending: true,
  49. batchSize: 100
  50. )
  51. return await mealTaskContext.perform {
  52. // Safely unwrap the fetched results, return empty array if nil
  53. guard let fetchedResults = results as? [CarbEntryStored] else { return [] }
  54. let calendar = Calendar.current
  55. // Group entries by day or hour depending on selected duration
  56. let groupedEntries = Dictionary(grouping: fetchedResults) { entry in
  57. if self.selectedDurationForMealStats == .Day {
  58. // For Day view, group by hour
  59. let components = calendar.dateComponents([.year, .month, .day, .hour], from: entry.date ?? Date())
  60. return calendar.date(from: components) ?? Date()
  61. } else {
  62. // For other views, group by day
  63. return calendar.startOfDay(for: entry.date ?? Date())
  64. }
  65. }
  66. // Get all unique dates/hours from the entries
  67. let timePoints = groupedEntries.keys.sorted()
  68. // Calculate statistics for each time point
  69. return timePoints.map { timePoint in
  70. let entries = groupedEntries[timePoint, default: []]
  71. let carbsTotal = entries.reduce(0.0) { $0 + $1.carbs }
  72. let fatTotal = entries.reduce(0.0) { $0 + $1.fat }
  73. let proteinTotal = entries.reduce(0.0) { $0 + $1.protein }
  74. return MealStats(
  75. date: timePoint,
  76. carbs: carbsTotal,
  77. fat: fatTotal,
  78. protein: proteinTotal
  79. )
  80. }
  81. }
  82. }
  83. /// Calculates average meal statistics for a specified date range
  84. /// - Parameters:
  85. /// - startDate: Start date of the range
  86. /// - endDate: End date of the range
  87. /// - Returns: Tuple containing average values for carbs, fat, and protein
  88. ///
  89. /// The calculation process:
  90. /// 1. Filters meal records within the date range
  91. /// 2. Calculates total values for each macronutrient
  92. /// 3. Divides totals by number of records to get averages
  93. /// 4. Returns (0,0,0) if no records are found
  94. func calculateAverageMealStats(
  95. from startDate: Date,
  96. to endDate: Date
  97. ) async -> (carbs: Double, fat: Double, protein: Double) {
  98. let filteredStats = mealStats.filter { stat in
  99. stat.date >= startDate && stat.date <= endDate
  100. }
  101. guard !filteredStats.isEmpty else { return (0, 0, 0) }
  102. let totalCarbs = filteredStats.reduce(0.0) { $0 + $1.carbs }
  103. let totalFat = filteredStats.reduce(0.0) { $0 + $1.fat }
  104. let totalProtein = filteredStats.reduce(0.0) { $0 + $1.protein }
  105. let count = Double(filteredStats.count)
  106. return (
  107. carbs: totalCarbs / count,
  108. fat: totalFat / count,
  109. protein: totalProtein / count
  110. )
  111. }
  112. }