polscm32 aka Marvout 1 год назад
Родитель
Сommit
7f7e5a224a

+ 2 - 0
FreeAPS/Sources/Modules/Stat/StatStateModel+Setup/BolusStatsSetup.swift

@@ -67,6 +67,8 @@ extension Stat.StateModel {
             let calendar = Calendar.current
 
             // Group entries by hour for hourly statistics
+            // TODO: - Introduce paging to also be able to show complete history
+
             let now = Date()
             let twentyDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: now) ?? now
 

+ 2 - 0
FreeAPS/Sources/Modules/Stat/StatStateModel+Setup/MealStatsSetup.swift

@@ -61,6 +61,8 @@ extension Stat.StateModel {
             let calendar = Calendar.current
 
             // Group entries by hour for hourly statistics
+            // TODO: - Introduce paging to also be able to show complete history
+
             let now = Date()
             let twentyDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: now) ?? now
 

+ 125 - 133
FreeAPS/Sources/Modules/Stat/StatStateModel+Setup/TDDSetup.swift

@@ -1,173 +1,165 @@
 import CoreData
 import Foundation
 
+/// Represents statistical data about Total Daily Dose for a specific time period
+struct TDDStats: Identifiable {
+    let id = UUID()
+    /// The date representing this time period
+    let date: Date
+    /// Total insulin in units
+    let amount: Double
+}
+
 extension Stat.StateModel {
-    /// Initializes and fetches Total Daily Dose (TDD) statistics
-    ///
-    /// This function:
-    /// 1. Fetches TDD determinations from CoreData
-    /// 2. Maps the determinations to TDD records
-    /// 3. Updates the tddStats array on the main thread
-    func setupTDDs() {
+    /// Sets up TDD statistics by fetching and processing insulin data
+    func setupTDDStats() {
         Task {
-            let tddStats = await fetchAndMapDeterminations()
+            let (hourly, daily) = await fetchTDDStats()
+
             await MainActor.run {
-                self.tddStats = tddStats
+                self.hourlyTDDStats = hourly
+                self.dailyTDDStats = daily
             }
+
+            // Initially calculate and cache daily averages
+            await calculateAndCacheTDDAverages()
         }
     }
 
-    /// Fetches and processes OpenAPS determinations to calculate Total Daily Doses
-    /// - Returns: Array of TDD records sorted by date
-    ///
-    /// This function:
-    /// 1. Fetches OpenAPS determinations from CoreData
-    /// 2. Groups determinations by time period (day or hour based on selected duration)
-    /// 3. Calculates average insulin doses for each time period
-    ///
-    /// The grouping logic:
-    /// - For Day view: Groups by hour to show hourly distribution
-    /// - For other views: Groups by day to show daily totals
-    func fetchAndMapDeterminations() async -> [TDD] {
+    /// Fetches and processes Total Daily Dose (TDD) statistics from CoreData
+    /// - Returns: A tuple containing hourly and daily TDD statistics arrays
+    /// - Note: Processes both hourly statistics for the last 10 days and complete daily statistics
+    private func fetchTDDStats() async -> (hourly: [TDDStats], daily: [TDDStats]) {
+        // Fetch temp basal records from CoreData
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: OrefDetermination.self,
-            onContext: determinationFetchContext,
-            predicate: NSPredicate.determinationsForStats,
-            key: "deliverAt",
+            ofType: TempBasalStored.self,
+            onContext: tddTaskContext,
+            predicate: NSPredicate.pumpHistoryForStats,
+            key: "pumpEvent.timestamp",
             ascending: true,
-            propertiesToFetch: ["objectID", "timestamp", "deliverAt", "totalDailyDose"]
+            batchSize: 100
         )
 
-        return await determinationFetchContext.perform {
-            guard let fetchedResults = results as? [[String: Any]] else { return [] }
-
-            let calendar = Calendar.current
+        var hourlyStats: [TDDStats] = []
+        var dailyStats: [TDDStats] = []
 
-            // Group determinations by day or hour
-            let groupedByTime = Dictionary(grouping: fetchedResults) { result -> Date in
-                guard let deliverAt = result["deliverAt"] as? Date else { return Date() }
-
-                if self.selectedDurationForInsulinStats == .Day {
-                    // For Day view, group by hour
-                    let components = calendar.dateComponents([.year, .month, .day, .hour], from: deliverAt)
-                    return calendar.date(from: components) ?? Date()
-                } else {
-                    // For other views, group by day
-                    return calendar.startOfDay(for: deliverAt)
-                }
+        await tddTaskContext.perform {
+            guard let fetchedResults = results as? [TempBasalStored] else {
+                return
             }
 
-            // Get all unique time points
-            let timePoints = groupedByTime.keys.sorted()
+            let calendar = Calendar.current
 
-            // Calculate totals for each time point
-            return timePoints.map { timePoint in
-                let determinations = groupedByTime[timePoint, default: []]
+            // Calculate date range for hourly statistics (last 10 days)
+            // TODO: - Introduce paging to also be able to show complete history
+            let now = Date()
+            let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: now) ?? now
+
+            // Group entries by hour for hourly statistics, filtering for last 10 days only
+            let hourlyGrouped = Dictionary(grouping: fetchedResults.filter { entry in
+                guard let date = entry.pumpEvent?.timestamp else { return false }
+                return date >= tenDaysAgo && date <= now
+            }) { entry in
+                // Create date components for hour-level grouping
+                let components = calendar.dateComponents(
+                    [.year, .month, .day, .hour],
+                    from: entry.pumpEvent?.timestamp ?? Date()
+                )
+                return calendar.date(from: components) ?? Date()
+            }
 
-                let totalDose = determinations.reduce(Decimal.zero) { sum, determination in
-                    sum + (determination["totalDailyDose"] as? Decimal ?? 0)
-                }
+            // Group entries by day for complete daily statistics
+            let dailyGrouped = Dictionary(grouping: fetchedResults) { entry in
+                calendar.startOfDay(for: entry.pumpEvent?.timestamp ?? Date())
+            }
 
-                // Calculate average dose for the time period
-                let count = Decimal(determinations.count)
-                let averageDose = count > 0 ? totalDose / count : 0
+            // Process hourly statistics
+            hourlyStats = hourlyGrouped.keys.sorted().map { timePoint in
+                let entries = hourlyGrouped[timePoint, default: []]
+                // Calculate total insulin for each hour
+                return TDDStats(
+                    date: timePoint,
+                    amount: entries.reduce(0.0) { sum, entry in
+                        sum + (entry.rate?.doubleValue ?? 0) * Double(entry.duration) / 60.0
+                    }
+                )
+            }
 
-                return TDD(
-                    totalDailyDose: averageDose,
-                    timestamp: timePoint
+            // Process daily statistics
+            dailyStats = dailyGrouped.keys.sorted().map { timePoint in
+                let entries = dailyGrouped[timePoint, default: []]
+                // Calculate total insulin for each day
+                return TDDStats(
+                    date: timePoint,
+                    amount: entries.reduce(0.0) { sum, entry in
+                        sum + (entry.rate?.doubleValue ?? 0) * Double(entry.duration) / 60.0
+                    }
                 )
             }
         }
+
+        return (hourlyStats, dailyStats)
     }
 
-    /// Calculates the average Total Daily Dose for the currently selected time period
-    ///
-    /// Time periods and their ranges:
-    /// - Day: Last 3 days
-    /// - Week: Last 7 days
-    /// - Month: Last 30 days
-    /// - Total: Last 3 months
-    ///
-    /// Returns 0 if no TDD records are available for the selected period
-    var averageTDD: Decimal {
+    /// Calculates and caches the daily averages of Total Daily Dose (TDD) insulin values
+    /// - Note: This function runs asynchronously and updates the tddAveragesCache on the main actor
+    private func calculateAndCacheTDDAverages() async {
+        // Get calendar for date calculations
         let calendar = Calendar.current
-        let now = Date()
-
-        // Filter TDDs based on selected time frame
-        let filteredTDDs: [TDD] = tddStats.filter { tdd in
-            guard let timestamp = tdd.timestamp else { return false }
-
-            switch selectedDurationForInsulinStats {
-            case .Day:
-                // Last 3 days
-                let threeDaysAgo = calendar.date(byAdding: .day, value: -3, to: now)!
-                return timestamp >= threeDaysAgo
-            case .Week:
-                // Last week
-                let weekAgo = calendar.date(byAdding: .weekOfYear, value: -1, to: now)!
-                return timestamp >= weekAgo
-            case .Month:
-                // Last month
-                let monthAgo = calendar.date(byAdding: .month, value: -1, to: now)!
-                return timestamp >= monthAgo
-            case .Total:
-                // Last 3 months
-                let threeMonthsAgo = calendar.date(byAdding: .month, value: -3, to: now)!
-                return timestamp >= threeMonthsAgo
+
+        // Calculate daily averages on background context
+        let dailyAverages = await tddTaskContext.perform { [dailyTDDStats] in
+            // Group TDD stats by calendar day
+            let groupedByDay = Dictionary(grouping: dailyTDDStats) { stat in
+                calendar.startOfDay(for: stat.date)
             }
-        }
 
-        let sum = filteredTDDs.reduce(Decimal.zero) { $0 + ($1.totalDailyDose ?? 0) }
-        return filteredTDDs.isEmpty ? 0 : sum / Decimal(filteredTDDs.count)
-    }
+            // Calculate average TDD for each day
+            var averages: [Date: Double] = [:]
+            for (day, stats) in groupedByDay {
+                // Sum up all TDD values for the day
+                let total = stats.reduce(0.0) { $0 + $1.amount }
+                let count = Double(stats.count)
+                // Store average in dictionary
+                averages[day] = total / count
+            }
+            return averages
+        }
 
-    /// Calculates the average Total Daily Dose for a specified date range
-    /// - Parameters:
-    ///   - startDate: Start date of the range
-    ///   - endDate: End date of the range
-    /// - Returns: Average TDD value for the period
-    ///
-    /// The function:
-    /// 1. Filters TDD records within the specified date range
-    /// 2. Calculates the sum of all TDDs in the range
-    /// 3. Returns the average (sum divided by number of records)
-    /// 4. Returns 0 if no records are found
-    func calculateAverageTDD(from startDate: Date, to endDate: Date) async -> Decimal {
-        let filteredTDDs = tddStats.filter { tdd in
-            guard let timestamp = tdd.timestamp else { return false }
-            return timestamp >= startDate && timestamp <= endDate
+        // Update cache on main actor
+        await MainActor.run {
+            self.tddAveragesCache = dailyAverages
         }
+    }
 
-        let sum = filteredTDDs.reduce(Decimal.zero) { $0 + ($1.totalDailyDose ?? 0) }
-        return filteredTDDs.isEmpty ? 0 : sum / Decimal(filteredTDDs.count)
+    /// Gets the cached average Total Daily Dose (TDD) of insulin for a specified date range
+    /// - Parameter range: A tuple containing the start and end dates to get averages for
+    /// - Returns: The average TDD in units for the specified date range
+    func getCachedTDDAverages(for range: (start: Date, end: Date)) -> Double {
+        // Calculate and return the TDD averages for the given date range using cached values
+        calculateTDDAveragesForDateRange(from: range.start, to: range.end)
     }
 
-    /// Calculates the median Total Daily Dose for a specified date range
+    /// Calculates the average Total Daily Dose (TDD) of insulin for a specified date range
     /// - Parameters:
-    ///   - startDate: Start date of the range
-    ///   - endDate: End date of the range
-    /// - Returns: Median TDD value for the period
-    ///
-    /// The calculation process:
-    /// 1. Filters TDD records within the date range
-    /// 2. Sorts all TDD values
-    /// 3. For odd number of values: Returns the middle value
-    /// 4. For even number of values: Returns average of two middle values
-    /// 5. Returns 0 if no records are found
-    func calculateMedianTDD(from startDate: Date, to endDate: Date) async -> Decimal {
-        let filteredTDDs = tddStats.filter { tdd in
-            guard let timestamp = tdd.timestamp else { return false }
-            return timestamp >= startDate && timestamp <= endDate
+    ///   - startDate: The start date of the range to calculate averages for
+    ///   - endDate: The end date of the range to calculate averages for
+    /// - Returns: The average TDD in units for the specified date range. Returns 0.0 if no data exists.
+    private func calculateTDDAveragesForDateRange(from startDate: Date, to endDate: Date) -> Double {
+        // Filter cached TDD values to only include those within the date range
+        let relevantStats = tddAveragesCache.filter { date, _ in
+            date >= startDate && date <= endDate
         }
 
-        let sortedDoses = filteredTDDs.compactMap(\.totalDailyDose).sorted()
-        guard !sortedDoses.isEmpty else { return 0 }
+        // Return 0 if no data exists for the specified range
+        guard !relevantStats.isEmpty else { return 0.0 }
 
-        let middle = sortedDoses.count / 2
-        if sortedDoses.count % 2 == 0 {
-            return (sortedDoses[middle - 1] + sortedDoses[middle]) / 2
-        } else {
-            return sortedDoses[middle]
-        }
+        // Calculate total TDD by summing all values
+        let total = relevantStats.values.reduce(0.0, +)
+        // Convert count to Double for floating point division
+        let count = Double(relevantStats.count)
+
+        // Return average TDD
+        return total / count
     }
 }

+ 7 - 2
FreeAPS/Sources/Modules/Stat/StatStateModel.swift

@@ -75,6 +75,11 @@ extension Stat {
         var dailyMealStats: [MealStats] = []
         var dailyAveragesCache: [Date: (carbs: Double, fat: Double, protein: Double)] = [:]
 
+        // Cache for TDD Stats
+        var hourlyTDDStats: [TDDStats] = []
+        var dailyTDDStats: [TDDStats] = []
+        var tddAveragesCache: [Date: Double] = [:]
+
         // Cache for Bolus Stats
         var hourlyBolusStats: [BolusStats] = []
         var dailyBolusStats: [BolusStats] = []
@@ -124,7 +129,7 @@ extension Stat {
         // Fetching Contexts
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
-        let determinationFetchContext = CoreDataStack.shared.newTaskContext()
+        let tddTaskContext = CoreDataStack.shared.newTaskContext()
         let loopTaskContext = CoreDataStack.shared.newTaskContext()
         let mealTaskContext = CoreDataStack.shared.newTaskContext()
         let bolusTaskContext = CoreDataStack.shared.newTaskContext()
@@ -184,7 +189,7 @@ extension Stat {
 
         override func subscribe() {
             setupGlucoseArray()
-            setupTDDs()
+            setupTDDStats()
             setupBolusStats()
             setupLoopStatRecords()
             setupMealStats()

+ 4 - 8
FreeAPS/Sources/Modules/Stat/View/StatRootView.swift

@@ -182,7 +182,7 @@ extension Stat {
             StatCard {
                 switch state.selectedInsulinChartType {
                 case .totalDailyDose:
-                    if state.tddStats.isEmpty {
+                    if state.dailyTDDStats.isEmpty {
                         ContentUnavailableView(
                             "No TDD Data",
                             systemImage: "chart.bar.xaxis",
@@ -191,13 +191,9 @@ extension Stat {
                     } else {
                         TDDChartView(
                             selectedDuration: $state.selectedDurationForInsulinStats,
-                            tddStats: state.tddStats,
-                            calculateAverage: { start, end in
-                                await state.calculateAverageTDD(from: start, to: end)
-                            },
-                            calculateMedian: { start, end in
-                                await state.calculateMedianTDD(from: start, to: end)
-                            }
+                            tddStats: state.selectedDurationForInsulinStats == .Day ?
+                                state.hourlyTDDStats : state.dailyTDDStats,
+                            state: state
                         )
                     }
 

+ 4 - 4
FreeAPS/Sources/Modules/Stat/View/ViewElements/BolusStatsView.swift

@@ -211,7 +211,7 @@ struct BolusStatsView: View {
                 y: .value("Amount", stat.manualBolus)
             )
             .foregroundStyle(by: .value("Type", "Manual"))
-            .position(by: .value("Type", "Manual"))
+            .position(by: .value("Type", "Boluses"))
 
             // Carb Bolus Bar
             BarMark(
@@ -219,7 +219,7 @@ struct BolusStatsView: View {
                 y: .value("Amount", stat.smb)
             )
             .foregroundStyle(by: .value("Type", "SMB"))
-            .position(by: .value("Type", "SMB"))
+            .position(by: .value("Type", "Boluses"))
 
             // Correction Bolus Bar
             BarMark(
@@ -227,7 +227,7 @@ struct BolusStatsView: View {
                 y: .value("Amount", stat.external)
             )
             .foregroundStyle(by: .value("Type", "External"))
-            .position(by: .value("Type", "External"))
+            .position(by: .value("Type", "Boluses"))
 
             if let selectedDate,
                let selectedBolus = getBolusForDate(selectedDate)
@@ -246,8 +246,8 @@ struct BolusStatsView: View {
             }
         }
         .chartForegroundStyleScale([
-            "Manual": Color.teal,
             "SMB": Color.blue,
+            "Manual": Color.teal,
             "External": Color.purple
         ])
         .chartLegend(position: .bottom, alignment: .leading, spacing: 12)

+ 29 - 34
FreeAPS/Sources/Modules/Stat/View/ViewElements/MealStatsView.swift

@@ -257,32 +257,30 @@ struct MealStatsView: View {
     }
 
     private var chartsView: some View {
-        Chart {
-            ForEach(mealStats) { stat in
-                // Carbs Bar
-                BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
-                    y: .value("Amount", stat.carbs)
-                )
-                .foregroundStyle(by: .value("Type", "Carbs"))
-                .position(by: .value("Type", "Carbs"))
+        Chart(mealStats) { stat in
+            // Carbs Bar (bottom)
+            BarMark(
+                x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                y: .value("Amount", stat.carbs)
+            )
+            .foregroundStyle(by: .value("Type", "Carbs"))
+            .position(by: .value("Type", "Macros"))
 
-                // Fat Bar
-                BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
-                    y: .value("Amount", stat.fat)
-                )
-                .foregroundStyle(by: .value("Type", "Fat"))
-                .position(by: .value("Type", "Fat"))
+            // Fat Bar (middle)
+            BarMark(
+                x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                y: .value("Amount", stat.fat)
+            )
+            .foregroundStyle(by: .value("Type", "Fat"))
+            .position(by: .value("Type", "Macros"))
 
-                // Protein Bar
-                BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
-                    y: .value("Amount", stat.protein)
-                )
-                .foregroundStyle(by: .value("Type", "Protein"))
-                .position(by: .value("Type", "Protein"))
-            }
+            // Protein Bar (top)
+            BarMark(
+                x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                y: .value("Amount", stat.protein)
+            )
+            .foregroundStyle(by: .value("Type", "Protein"))
+            .position(by: .value("Type", "Macros"))
 
             if let selectedDate,
                let selectedMeal = getMealForDate(selectedDate)
@@ -302,8 +300,8 @@ struct MealStatsView: View {
         }
         .chartForegroundStyleScale([
             "Carbs": Color.orange,
-            "Fat": Color.blue,
-            "Protein": Color.green
+            "Fat": Color.green,
+            "Protein": Color.blue
         ])
         .chartLegend(position: .bottom, alignment: .leading, spacing: 12)
         .chartYAxis {
@@ -324,17 +322,16 @@ struct MealStatsView: View {
 
                     switch selectedDuration {
                     case .Day:
-                        if hour % 6 == 0 { // Show only every 6 hours (0, 6, 12, 18)
+                        if hour % 6 == 0 {
                             AxisValueLabel(format: dateFormat, centered: true)
                             AxisGridLine()
                         }
                     case .Month:
-                        if day % 5 == 0 { // Only show every 5th day
+                        if day % 5 == 0 {
                             AxisValueLabel(format: dateFormat, centered: true)
                             AxisGridLine()
                         }
                     case .Total:
-                        // Only show January, April, July, October
                         if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
                             AxisValueLabel(format: dateFormat, centered: true)
                             AxisGridLine()
@@ -352,11 +349,9 @@ struct MealStatsView: View {
         .chartScrollTargetBehavior(
             .valueAligned(
                 matching: selectedDuration == .Day ?
-                    DateComponents(minute: 0) : // Align to next hour for Day view
-                    DateComponents(hour: 0), // Align to start of day for other views
-                majorAlignment: .matching(
-                    alignmentComponents
-                )
+                    DateComponents(minute: 0) :
+                    DateComponents(hour: 0),
+                majorAlignment: .matching(alignmentComponents)
             )
         )
         .chartXVisibleDomain(length: visibleDomainLength)

+ 192 - 197
FreeAPS/Sources/Modules/Stat/View/ViewElements/TDDChart.swift

@@ -3,34 +3,27 @@ import SwiftUI
 
 struct TDDChartView: View {
     @Binding var selectedDuration: Stat.StateModel.StatsTimeInterval
-    let tddStats: [TDD]
-    let calculateAverage: @Sendable(Date, Date) async -> Decimal
-    let calculateMedian: @Sendable(Date, Date) async -> Decimal
+    let tddStats: [TDDStats]
+    let state: Stat.StateModel
 
     @State private var scrollPosition = Date()
-    @State private var currentAverageTDD: Decimal = 0
-    @State private var currentMedianTDD: Decimal = 0
     @State private var selectedDate: Date?
-    @State private var isScrolling = false
-
+    @State private var currentAverage: Double = 0
     @State private var updateTimer = Stat.UpdateTimer()
 
     private var visibleDomainLength: TimeInterval {
         switch selectedDuration {
-        case .Day: return 3 * 24 * 3600 // 3 days
-        case .Week: return 7 * 24 * 3600 // 1 week
-        case .Month: return 30 * 24 * 3600 // 1 month
-        case .Total: return 90 * 24 * 3600 // 3 months
+        case .Day: return 24 * 3600
+        case .Week: return 7 * 24 * 3600
+        case .Month: return 30 * 24 * 3600
+        case .Total: return 90 * 24 * 3600
         }
     }
 
-    private var scrollTargetDuration: TimeInterval {
-        switch selectedDuration {
-        case .Day: return 3 * 24 * 3600 // Scroll by 3 days
-        case .Week: return 7 * 24 * 3600 // Scroll by 1 week
-        case .Month: return 30 * 24 * 3600 // Scroll by 1 month
-        case .Total: return 90 * 24 * 3600 // Scroll by 3 months
-        }
+    private var visibleDateRange: (start: Date, end: Date) {
+        let start = scrollPosition
+        let end = start.addingTimeInterval(visibleDomainLength)
+        return (start, end)
     }
 
     private var dateFormat: Date.FormatStyle {
@@ -49,228 +42,230 @@ struct TDDChartView: View {
     private var alignmentComponents: DateComponents {
         switch selectedDuration {
         case .Day:
-            return DateComponents(hour: 0) // Align to start of day
+            return DateComponents(hour: 0)
         case .Week:
-            return DateComponents(weekday: 2) // 2 = Monday in Calendar
+            return DateComponents(weekday: 2)
         case .Month,
              .Total:
-            return DateComponents(day: 1) // Align to first day of month
+            return DateComponents(day: 1)
         }
     }
 
-    private var visibleDateRange: (start: Date, end: Date) {
-        let halfDomain = visibleDomainLength / 2
-        let start = scrollPosition.addingTimeInterval(-halfDomain)
-        let end = scrollPosition.addingTimeInterval(halfDomain)
-        return (start, end)
+    private func getTDDForDate(_ date: Date) -> TDDStats? {
+        tddStats.first { stat in
+            Calendar.current.isDate(stat.date, inSameDayAs: date)
+        }
+    }
+
+    private func updateAverages() {
+        currentAverage = state.getCachedTDDAverages(for: visibleDateRange)
     }
 
-    private func updateStats() {
-        Task.detached(priority: .userInitiated) {
-            let dateRange = await MainActor.run { visibleDateRange }
-            let avgTDD = await calculateAverage(dateRange.start, dateRange.end)
-            let medTDD = await calculateMedian(dateRange.start, dateRange.end)
+    /// Formats the visible date range into a human-readable string
+    private func formatVisibleDateRange() -> String {
+        let start = visibleDateRange.start
+        let end = visibleDateRange.end
+        let calendar = Calendar.current
+        let today = Date()
 
-            await MainActor.run {
-                currentAverageTDD = avgTDD
-                currentMedianTDD = medTDD
+        let timeFormat = start.formatted(.dateTime.hour().minute())
+
+        // Special handling for Day view with relative dates
+        if selectedDuration == .Day {
+            let startDateText: String
+            let endDateText: String
+
+            // Format start date
+            if calendar.isDate(start, inSameDayAs: today) {
+                startDateText = "Today"
+            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
+                startDateText = "Yesterday"
+            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
+                startDateText = "Tomorrow"
+            } else {
+                startDateText = start.formatted(.dateTime.day().month())
+            }
+
+            // Format end date
+            if calendar.isDate(end, inSameDayAs: today) {
+                endDateText = "Today"
+            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
+                endDateText = "Yesterday"
+            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
+                endDateText = "Tomorrow"
+            } else {
+                endDateText = end.formatted(.dateTime.day().month())
             }
+
+            // If start and end are on the same day, show date only once
+            if calendar.isDate(start, inSameDayAs: end) {
+                return "\(startDateText), \(timeFormat) - \(end.formatted(.dateTime.hour().minute()))"
+            }
+
+            return "\(startDateText), \(timeFormat) - \(endDateText), \(end.formatted(.dateTime.hour().minute()))"
         }
+
+        // Standard format for other views
+        return "\(start.formatted()) - \(end.formatted())"
     }
 
-    private func getTDDForDate(_ date: Date) -> TDD? {
-        tddStats.first { tdd in
-            guard let timestamp = tdd.timestamp else { return false }
-            return Calendar.current.isDate(timestamp, inSameDayAs: date)
+    private func getInitialScrollPosition() -> Date {
+        let calendar = Calendar.current
+        let now = Date()
+
+        switch selectedDuration {
+        case .Day:
+            return calendar.date(byAdding: .day, value: -1, to: now)!
+        case .Week:
+            return calendar.date(byAdding: .day, value: -7, to: now)!
+        case .Month:
+            return calendar.date(byAdding: .month, value: -1, to: now)!
+        case .Total:
+            return calendar.date(byAdding: .month, value: -3, to: now)!
         }
     }
 
     var body: some View {
-        chartCard
-            .onAppear {
-                updateStats()
-            }
-            .onChange(of: scrollPosition) {
-                isScrolling = true
-                updateTimer.scheduleUpdate {
-                    updateStats()
-                    isScrolling = false
-                }
+        VStack(alignment: .leading, spacing: 8) {
+            statsView
+            chartsView
+        }
+        .onAppear {
+            scrollPosition = getInitialScrollPosition()
+            updateAverages()
+        }
+        .onChange(of: scrollPosition) {
+            updateTimer.scheduleUpdate {
+                updateAverages()
             }
-            .onChange(of: selectedDuration) {
-                updateStats()
-                scrollPosition = Date()
+        }
+        .onChange(of: selectedDuration) {
+            Task {
+                scrollPosition = getInitialScrollPosition()
+                updateAverages()
             }
+        }
     }
 
-    // MARK: - Views
+    private var statsView: some View {
+        HStack {
+            Text("Average:")
+                .font(.headline)
+                .foregroundStyle(.secondary)
+            Text(currentAverage.formatted(.number.precision(.fractionLength(1))))
+                .font(.headline)
+                .foregroundStyle(.secondary)
+            Text("U")
+                .font(.headline)
+                .foregroundStyle(.secondary)
 
-    private var chartCard: some View {
-        VStack(alignment: .leading, spacing: 8) {
-            statsView
+            Spacer()
 
-            Chart {
-                ForEach(tddStats) { entry in
-                    BarMark(
-                        x: .value("Date", entry.timestamp ?? Date(), unit: selectedDuration == .Day ? .hour : .day),
-                        y: .value("TDD", entry.totalDailyDose ?? 0)
-                    )
-                    .foregroundStyle(Color.insulin)
-                }
+            Text(formatVisibleDateRange())
+                .font(.subheadline)
+                .foregroundStyle(.secondary)
+        }
+    }
 
-                if let selectedDate,
-                   let selectedTDD = getTDDForDate(selectedDate)
-                {
-                    RuleMark(
-                        x: .value("Selected Date", selectedDate)
-                    )
-                    .foregroundStyle(.secondary.opacity(0.3))
-                    .annotation(
-                        position: .top,
-                        spacing: 0,
-                        overflowResolution: .init(x: .fit, y: .disabled)
-                    ) {
-                        TDDSelectionPopover(date: selectedDate, tdd: selectedTDD)
-                    }
+    private var chartsView: some View {
+        Chart(tddStats) { stat in
+            BarMark(
+                x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                y: .value("Amount", stat.amount)
+            )
+            .foregroundStyle(Color.insulin)
+
+            if let selectedDate,
+               let selectedTDD = getTDDForDate(selectedDate)
+            {
+                RuleMark(
+                    x: .value("Selected Date", selectedDate)
+                )
+                .foregroundStyle(.secondary.opacity(0.3))
+                .annotation(
+                    position: .top,
+                    spacing: 0,
+                    overflowResolution: .init(x: .fit, y: .disabled)
+                ) {
+                    TDDSelectionPopover(date: selectedDate, tdd: selectedTDD)
                 }
             }
-            .chartYAxis {
-                AxisMarks { _ in
-                    AxisValueLabel()
+        }
+        .chartYAxis {
+            AxisMarks(position: .trailing) { value in
+                if let amount = value.as(Double.self) {
+                    AxisValueLabel {
+                        Text(amount.formatted(.number.precision(.fractionLength(0))) + " U")
+                    }
                     AxisGridLine()
                 }
             }
-            .chartXAxis {
-                AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .Day ? .hour : .day)) { value in
-                    if let date = value.as(Date.self) {
-                        let day = Calendar.current.component(.day, from: date)
-                        let hour = Calendar.current.component(.hour, from: date)
+        }
+        .chartXAxis {
+            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .Day ? .hour : .day)) { value in
+                if let date = value.as(Date.self) {
+                    let day = Calendar.current.component(.day, from: date)
+                    let hour = Calendar.current.component(.hour, from: date)
 
-                        switch selectedDuration {
-                        case .Day:
-                            if hour % 6 == 0 { // Show only every 6 hours
-                                AxisValueLabel(format: dateFormat, centered: true)
-                                AxisGridLine()
-                            }
-                        case .Month:
-                            if day % 5 == 0 { // Only show every 5th day
-                                AxisValueLabel(format: dateFormat, centered: true)
-                                AxisGridLine()
-                            }
-                        case .Total:
-                            if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
-                                AxisValueLabel(format: dateFormat, centered: true)
-                                AxisGridLine()
-                            }
-                        default:
+                    switch selectedDuration {
+                    case .Day:
+                        if hour % 6 == 0 {
+                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisGridLine()
+                        }
+                    case .Month:
+                        if day % 5 == 0 {
                             AxisValueLabel(format: dateFormat, centered: true)
                             AxisGridLine()
                         }
+                    case .Total:
+                        if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
+                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisGridLine()
+                        }
+                    default:
+                        AxisValueLabel(format: dateFormat, centered: true)
+                        AxisGridLine()
                     }
                 }
             }
-            .chartXSelection(value: $selectedDate)
-            .chartScrollableAxes(.horizontal)
-            .chartScrollPosition(x: $scrollPosition)
-            .chartScrollTargetBehavior(
-                .valueAligned(
-                    matching: selectedDuration == .Day ?
-                        DateComponents(minute: 0) : // Align to next hour for Day view
-                        DateComponents(hour: 0), // Align to start of day for other views
-                    majorAlignment: .matching(alignmentComponents)
-                )
-            )
-            .chartXVisibleDomain(length: visibleDomainLength)
-            .frame(height: 200)
         }
+        .chartXSelection(value: $selectedDate)
+        .chartScrollableAxes(.horizontal)
+        .chartScrollPosition(x: $scrollPosition)
+        .chartScrollTargetBehavior(
+            .valueAligned(
+                matching: selectedDuration == .Day ?
+                    DateComponents(minute: 0) :
+                    DateComponents(hour: 0),
+                majorAlignment: .matching(alignmentComponents)
+            )
+        )
+        .chartXVisibleDomain(length: visibleDomainLength)
+        .frame(height: 200)
     }
+}
 
-    private var statsView: some View {
-        HStack {
-            Grid(alignment: .leading) {
-                GridRow {
-                    Text("Average:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                    Text(currentAverageTDD.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
-                    Text("U")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                }
-                GridRow {
-                    Text("Median:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                    Text(currentMedianTDD.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
-                    Text("U")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                }
-            }
-
-            Spacer()
+private struct TDDSelectionPopover: View {
+    let date: Date
+    let tdd: TDDStats
 
-            Text(formatVisibleDateRange(showTimeRange: isScrolling))
-                .font(.subheadline)
+    var body: some View {
+        VStack(alignment: .leading, spacing: 4) {
+            Text(date.formatted(.dateTime.month().day()))
+                .font(.caption)
                 .foregroundStyle(.secondary)
-        }
-    }
-
-    private func formatVisibleDateRange(showTimeRange: Bool = false) -> String {
-        let start = visibleDateRange.start
-        let end = visibleDateRange.end
-        let calendar = Calendar.current
-
-        switch selectedDuration {
-        case .Day:
-            let today = Date()
-            let isToday = calendar.isDate(start, inSameDayAs: today)
-            let isYesterday = calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!)
-
-            if isToday || isYesterday, !showTimeRange {
-                return isToday ? "Today" : "Yesterday"
-            }
-
-            let timeRange =
-                "\(start.formatted(.dateTime.hour(.twoDigits(amPM: .wide)))) - \(end.formatted(.dateTime.hour(.twoDigits(amPM: .wide))))"
-
-            if isToday {
-                return "Today, \(timeRange)"
-            } else if isYesterday {
-                return "Yesterday, \(timeRange)"
-            } else {
-                return "\(start.formatted(.dateTime.month().day())), \(timeRange)"
-            }
 
-        default:
-            return "\(start.formatted(.dateTime.month().day())) - \(end.formatted(.dateTime.month().day()))"
+            Text(tdd.amount.formatted(.number.precision(.fractionLength(1))) + " U")
+                .font(.caption)
+                .bold()
         }
-    }
-
-    private struct TDDSelectionPopover: View {
-        let date: Date
-        let tdd: TDD
-
-        var body: some View {
-            VStack(alignment: .center, spacing: 4) {
-                Text(date.formatted(.dateTime.month().day()))
-                    .font(.caption)
-                    .foregroundStyle(.secondary)
-                Text("\(tdd.totalDailyDose?.formatted(.number.precision(.fractionLength(1))) ?? "0") U")
-                    .font(.callout.bold())
-            }
-            .padding(8)
-            .background(
-                RoundedRectangle(cornerRadius: 8)
-                    .fill(Color(.systemBackground))
-                    .shadow(radius: 2)
-            )
+        .padding(.horizontal, 8)
+        .padding(.vertical, 4)
+        .background {
+            RoundedRectangle(cornerRadius: 8)
+                .fill(.background)
+                .shadow(radius: 2)
         }
     }
 }