| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- import CoreData
- import Foundation
- extension Stat {
- /// Represents a single data point in the glucose range distribution
- /// - hour: The hour of the day (0-23)
- /// - count: The percentage of readings in this range for the given hour (0-100)
- struct GlucoseRangeValue {
- let hour: Int
- let count: Double
- }
- /// Represents the distribution of glucose values within specific ranges for each hour
- ///
- /// This struct is used to visualize how glucose values are distributed across different
- /// ranges (e.g., low, target, high) throughout the day. Each range has a name and
- /// corresponding hourly values showing the percentage of readings in that range.
- ///
- /// Example ranges and their meanings:
- /// - "<54": Urgent low
- /// - "54-70": Low
- /// - "70-140": Target range
- /// - "140-180": High
- /// - "180-200": Very high
- /// - "200-220": Very high+
- /// - ">220": Urgent high
- struct GlucoseRangeStats: Identifiable {
- let id = UUID()
- /// The name of the glucose range (e.g., "70-140", "<54")
- let name: String
- /// Array of hourly values containing percentages for this range
- let values: [GlucoseRangeValue]
- }
- }
- extension Stat.StateModel {
- /// Calculates range 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 range statistics as values
- ///
- /// This function processes glucose readings grouped by date to calculate the distribution
- /// of values across different ranges for each hour of the day.
- func calculateRangeStats(
- for groupedValues: [Date: [GlucoseStored]]
- ) -> [Date: [Stat.GlucoseRangeStats]] {
- groupedValues.mapValues { values in
- calculateGlucoseRangeStats(from: values.map(\.objectID))
- }
- }
- /// Calculates the distribution of glucose values across different ranges for each hour
- /// - Parameter ids: Array of NSManagedObjectIDs for glucose readings
- /// - Returns: Array of GlucoseRangeStats containing percentage distributions
- ///
- /// The calculation process:
- /// 1. Groups readings by hour of day
- /// 2. Defines glucose ranges and their conditions
- /// 3. For each range and hour:
- /// - Counts readings that fall within the range
- /// - Calculates percentage of total readings in that range
- ///
- /// The results are used to create stacked area charts showing:
- /// - Distribution of glucose values across ranges
- /// - Patterns in glucose control throughout the day
- /// - Time spent in different ranges for each hour
- func calculateGlucoseRangeStats(from ids: [NSManagedObjectID]) -> [Stat.GlucoseRangeStats] {
- let calendar = Calendar.current
- // Group glucose values by hour
- let hourlyGroups = Dictionary(
- grouping: fetchGlucoseValues(from: ids),
- by: { calendar.component(.hour, from: $0.date ?? Date()) }
- )
- // Prepare hourly values for processing
- let hourlyValues = (0 ... 23).map { hour -> (hour: Int, values: [Double]) in
- let values = hourlyGroups[hour]?.compactMap { Double($0.glucose) } ?? []
- return (hour, values)
- }
- // Define glucose ranges and their conditions
- let ranges: [(name: String, filter: (Double) -> Bool)] = [
- ("<54", { [self] in $0 < Double(self.lowLimit - 20) }),
- ("54-70", { [self] in $0 >= Double(self.lowLimit - 20) && $0 < Double(self.lowLimit) }),
- ("70-140", { [self] in $0 >= Double(self.lowLimit) && $0 <= 140 }),
- ("140-180", { [self] in $0 > 140 && $0 <= Double(self.highLimit) }),
- ("180-200", { [self] in $0 > Double(self.highLimit) && $0 <= 200 }),
- ("200-220", { $0 > 200 && $0 <= 220 }),
- (">220", { $0 > 220 })
- ]
- // Calculate percentage distribution for each range
- return ranges.map { range in
- Stat.GlucoseRangeStats(
- name: range.name,
- values: hourlyValues.map { hour, values in
- let total = Double(values.count)
- let count = values.filter(range.filter).count
- return Stat.GlucoseRangeValue(
- hour: hour,
- count: total > 0 ? Double(count) / total : 0
- )
- }
- )
- }
- }
- }
|