MealStatsSetup.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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. /// Initiates the process of fetching and processing meal statistics
  17. /// - Parameter duration: The time period to fetch records for
  18. func setupMealStats() {
  19. Task {
  20. let stats = await fetchMealStats()
  21. await MainActor.run {
  22. self.mealStats = stats
  23. }
  24. }
  25. }
  26. /// Fetches and processes meal statistics for a specific duration
  27. /// - Parameter duration: The time period to fetch records for (Today, 24h, 7 Days, 30 Days, or All)
  28. /// - Returns: Array of MealStats containing daily meal statistics, sorted by date
  29. private func fetchMealStats() async -> [MealStats] {
  30. // Fetch CarbEntryStored entries from Core Data
  31. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  32. ofType: CarbEntryStored.self,
  33. onContext: mealTaskContext,
  34. predicate: NSPredicate.carbsForStats,
  35. key: "date",
  36. ascending: true,
  37. batchSize: 100
  38. )
  39. return await mealTaskContext.perform {
  40. // Safely unwrap the fetched results, return empty array if nil
  41. guard let fetchedResults = results as? [CarbEntryStored] else { return [] }
  42. let calendar = Calendar.current
  43. // Group entries by day or hour depending on selected duration
  44. let groupedEntries = Dictionary(grouping: fetchedResults) { entry in
  45. if self.selectedDurationForMealStats == .Day {
  46. // For Day view, group by hour
  47. let components = calendar.dateComponents([.year, .month, .day, .hour], from: entry.date ?? Date())
  48. return calendar.date(from: components) ?? Date()
  49. } else {
  50. // For other views, group by day
  51. return calendar.startOfDay(for: entry.date ?? Date())
  52. }
  53. }
  54. // Get all unique dates/hours from the entries
  55. let timePoints = groupedEntries.keys.sorted()
  56. // Calculate statistics for each time point
  57. return timePoints.map { timePoint in
  58. let entries = groupedEntries[timePoint, default: []]
  59. let carbsTotal = entries.reduce(0.0) { $0 + $1.carbs }
  60. let fatTotal = entries.reduce(0.0) { $0 + $1.fat }
  61. let proteinTotal = entries.reduce(0.0) { $0 + $1.protein }
  62. return MealStats(
  63. date: timePoint,
  64. carbs: carbsTotal,
  65. fat: fatTotal,
  66. protein: proteinTotal
  67. )
  68. }
  69. }
  70. }
  71. func calculateAverageMealStats(
  72. from startDate: Date,
  73. to endDate: Date
  74. ) async -> (carbs: Double, fat: Double, protein: Double) {
  75. let filteredStats = mealStats.filter { stat in
  76. stat.date >= startDate && stat.date <= endDate
  77. }
  78. guard !filteredStats.isEmpty else { return (0, 0, 0) }
  79. let totalCarbs = filteredStats.reduce(0.0) { $0 + $1.carbs }
  80. let totalFat = filteredStats.reduce(0.0) { $0 + $1.fat }
  81. let totalProtein = filteredStats.reduce(0.0) { $0 + $1.protein }
  82. let count = Double(filteredStats.count)
  83. return (
  84. carbs: totalCarbs / count,
  85. fat: totalFat / count,
  86. protein: totalProtein / count
  87. )
  88. }
  89. }