| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- import CoreData
- import Foundation
- /// Represents statistical values for glucose readings grouped by hour of the day.
- ///
- /// This struct contains various percentile calculations that help visualize
- /// glucose distribution patterns throughout the day:
- ///
- /// - The median (50th percentile) shows the central tendency
- /// - The 25th and 75th percentiles form the interquartile range (IQR)
- /// - The 10th and 90th percentiles show the wider range of values
- ///
- /// The data is used to create area charts with:
- /// - A dark blue area for the interquartile range (25th-75th percentile)
- /// - A light blue area for the wider range (10th-90th percentile)
- /// - A solid blue line for the median
- ///
- /// Example usage:
- /// ```swift
- /// let stats = HourlyStats(
- /// hour: 14, // 2 PM
- /// median: 120, // Center line
- /// percentile25: 100, // Lower bound of dark band
- /// percentile75: 140, // Upper bound of dark band
- /// percentile10: 80, // Lower bound of light band
- /// percentile90: 160 // Upper bound of light band
- /// )
- /// ```
- ///
- /// This data structure is used to create area charts showing glucose
- /// variability patterns across different times of day.
- public struct HourlyStats: Equatable {
- /// The hour of day (0-23) these statistics represent
- let hour: Int
- /// The median (50th percentile) glucose value for this hour
- let median: Double
- /// The 25th percentile glucose value (lower quartile)
- let percentile25: Double
- /// The 75th percentile glucose value (upper quartile)
- let percentile75: Double
- /// The 10th percentile glucose value (lower whisker)
- let percentile10: Double
- /// The 90th percentile glucose value (upper whisker)
- let percentile90: Double
- }
- extension Double {
- /// Helper property to check if a number is even
- var isEven: Bool {
- truncatingRemainder(dividingBy: 2) == 0
- }
- }
- extension Stat.StateModel {
- /// Calculates hourly statistics for grouped glucose values
- /// - Parameter groupedValues: Dictionary with dates as keys and arrays of glucose readings as values
- /// - Returns: Dictionary with dates as keys and arrays of hourly statistics as values
- ///
- /// This function processes glucose readings grouped by date to calculate hourly statistics
- /// for each group. The statistics include median and various percentiles that show
- /// the distribution of glucose values throughout the day.
- func calculateStats(
- for groupedValues: [Date: [GlucoseStored]]
- ) -> [Date: [HourlyStats]] {
- groupedValues.mapValues { values in
- calculateHourlyStats(from: values.map(\.objectID))
- }
- }
- /// Calculates detailed hourly statistics for a set of glucose readings
- /// - Parameter ids: Array of NSManagedObjectIDs for glucose readings
- /// - Returns: Array of HourlyStats containing percentile calculations for each hour
- ///
- /// The calculation process:
- /// 1. Groups readings by hour of day (0-23)
- /// 2. For each hour:
- /// - Sorts glucose values
- /// - Calculates median (50th percentile)
- /// - Calculates 10th, 25th, 75th, and 90th percentiles
- ///
- /// These statistics are used to show:
- /// - The typical glucose range for each hour
- /// - The variability of glucose values
- /// - Patterns in glucose behavior throughout the day
- func calculateHourlyStats(from ids: [NSManagedObjectID]) -> [HourlyStats] {
- let calendar = Calendar.current
- // Fetch glucose values and group them by hour
- let hourlyGroups = Dictionary(
- grouping: fetchGlucoseValues(from: ids),
- by: { calendar.component(.hour, from: $0.date ?? Date()) }
- )
- // Calculate stats for each hour (0-23)
- return (0 ... 23).map { hour in
- let values = hourlyGroups[hour]?.compactMap { Double($0.glucose) }.sorted() ?? []
- guard !values.isEmpty else {
- return HourlyStats(hour: hour, median: 0, percentile25: 0, percentile75: 0, percentile10: 0, percentile90: 0)
- }
- // Calculate percentiles using array indices
- let count = values.count
- return HourlyStats(
- hour: hour,
- median: values[count * 50 / 100],
- percentile25: values[count * 25 / 100],
- percentile75: values[count * 75 / 100],
- percentile10: values[count * 10 / 100],
- percentile90: values[count * 90 / 100]
- )
- }
- }
- }
|