import CoreData import SwiftDate import SwiftUI struct StatsView: View { @FetchRequest var fetchRequest: FetchedResults @FetchRequest var fetchRequestReadings: FetchedResults @State var headline: Color = .secondary @Binding var highLimit: Decimal @Binding var lowLimit: Decimal @Binding var units: GlucoseUnits @Binding var overrideUnit: Bool private let conversionFactor = 0.0555 var body: some View { VStack(spacing: 10) { loops Divider() hba1c Divider() bloodGlucose } } init( filter: NSDate, _ highLimit: Binding, _ lowLimit: Binding, _ units: Binding, _ overrideUnit: Binding ) { _fetchRequest = FetchRequest( sortDescriptors: [NSSortDescriptor(key: "start", ascending: false)], predicate: NSPredicate(format: "interval > 0 AND start > %@", filter) ) _fetchRequestReadings = FetchRequest( sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(format: "glucose > 0 AND date > %@", filter) ) _highLimit = highLimit _lowLimit = lowLimit _units = units _overrideUnit = overrideUnit } var loops: some View { let loops = fetchRequest // First date let previous = loops.last?.end ?? Date() // Last date (recent) let current = loops.first?.start ?? Date() // Total time in days let totalTime = (current - previous).timeInterval / 8.64E4 let durationArray = loops.compactMap({ each in each.duration }) let durationArrayCount = durationArray.count // var durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount) let medianDuration = medianCalculationDouble(array: durationArray) let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") }).count let errorNR = durationArrayCount - successsNR let total = Double(successsNR + errorNR) == 0 ? 1 : Double(successsNR + errorNR) let successRate: Double? = (Double(successsNR) / total) * 100 let loopNr = totalTime <= 1 ? total : round(total / (totalTime != 0 ? totalTime : 1)) let intervalArray = loops.compactMap({ each in each.interval as Double }) let count = intervalArray.count != 0 ? intervalArray.count : 1 let intervalAverage = intervalArray.reduce(0, +) / Double(count) // let maximumInterval = intervalArray.max() // let minimumInterval = intervalArray.min() return VStack(spacing: 10) { HStack(spacing: 35) { VStack(spacing: 5) { Text("Loops").font(.subheadline).foregroundColor(headline) Text(loopNr.formatted()) } VStack(spacing: 5) { Text("Interval").font(.subheadline).foregroundColor(headline) Text(intervalAverage.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " min") } VStack(spacing: 5) { Text("Duration").font(.subheadline).foregroundColor(headline) Text( (medianDuration * 60) .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " s" ) } VStack(spacing: 5) { Text("Success").font(.subheadline).foregroundColor(headline) Text( ((successRate ?? 100) / 100) .formatted(.percent.grouping(.never).rounded().precision(.fractionLength(1))) ) } } } } private func medianCalculation(array: [Int]) -> Double { guard !array.isEmpty else { return 0 } let sorted = array.sorted() let length = array.count if length % 2 == 0 { return Double((sorted[length / 2 - 1] + sorted[length / 2]) / 2) } return Double(sorted[length / 2]) } private func medianCalculationDouble(array: [Double]) -> Double { guard !array.isEmpty else { return 0 } let sorted = array.sorted() let length = array.count if length % 2 == 0 { return (sorted[length / 2 - 1] + sorted[length / 2]) / 2 } return sorted[length / 2] } var hba1c: some View { HStack(spacing: 50) { let useUnit: GlucoseUnits = (units == .mmolL && overrideUnit) ? .mgdL : (units == .mgdL && overrideUnit || units == .mmolL) ? .mmolL : .mgdL let hba1cs = glucoseStats() let glucose = fetchRequestReadings // First date let previous = glucose.last?.date ?? Date() // Last date (recent) let current = glucose.first?.date ?? Date() // Total time in days let numberOfDays = (current - previous).timeInterval / 8.64E4 let hba1cString = ( useUnit == .mmolL ? hba1cs.ifcc .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) : hba1cs.ngsp .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " %" ) VStack(spacing: 5) { Text("HbA1C").font(.subheadline).foregroundColor(headline) Text(hba1cString) } VStack(spacing: 5) { Text("SD").font(.subheadline).foregroundColor(.secondary) Text( hba1cs.sd .formatted( .number.grouping(.never).rounded() .precision(.fractionLength(units == .mmolL ? 1 : 0)) ) ) } VStack(spacing: 5) { Text("CV").font(.subheadline).foregroundColor(.secondary) Text(hba1cs.cv.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))) } VStack(spacing: 5) { Text("Days").font(.subheadline).foregroundColor(.secondary) Text(numberOfDays.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))) } } } var bloodGlucose: some View { HStack(spacing: 30) { let bgs = glucoseStats() let glucose = fetchRequestReadings // First date let previous = glucose.last?.date ?? Date() // Last date (recent) let current = glucose.first?.date ?? Date() // Total time in days let numberOfDays = (current - previous).timeInterval / 8.64E4 VStack(spacing: 5) { Text(numberOfDays < 1 ? "Readings" : "Readings / 24h").font(.subheadline) .foregroundColor(.secondary) Text(bgs.readings.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))) } VStack(spacing: 5) { Text("Average").font(.subheadline).foregroundColor(headline) Text( bgs.average .formatted( .number.grouping(.never).rounded() .precision(.fractionLength(units == .mmolL ? 1 : 0)) ) ) } VStack(spacing: 5) { Text("Median").font(.subheadline).foregroundColor(.secondary) Text( bgs.median .formatted( .number.grouping(.never).rounded() .precision(.fractionLength(units == .mmolL ? 1 : 0)) ) ) } } } private func glucoseStats() -> (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double) { let glucose = fetchRequestReadings // First date let previous = glucose.last?.date ?? Date() // Last date (recent) let current = glucose.first?.date ?? Date() // Total time in days let numberOfDays = (current - previous).timeInterval / 8.64E4 let denominator = numberOfDays < 1 ? 1 : numberOfDays let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) }) let sumReadings = justGlucoseArray.reduce(0, +) let countReadings = justGlucoseArray.count let glucoseAverage = Double(sumReadings) / Double(countReadings) let medianGlucose = medianCalculation(array: justGlucoseArray) var NGSPa1CStatisticValue = 0.0 var IFCCa1CStatisticValue = 0.0 if numberOfDays > 0 { NGSPa1CStatisticValue = (glucoseAverage + 46.7) / 28.7 // NGSP (%) IFCCa1CStatisticValue = 10.929 * (NGSPa1CStatisticValue - 2.152) // IFCC (mmol/mol) A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15) } var sumOfSquares = 0.0 for array in justGlucoseArray { sumOfSquares += pow(Double(array) - Double(glucoseAverage), 2) } var sd = 0.0 var cv = 0.0 // Avoid division by zero if glucoseAverage > 0 { sd = sqrt(sumOfSquares / Double(countReadings)) cv = sd / Double(glucoseAverage) * 100 } var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double) output = ( ifcc: IFCCa1CStatisticValue, ngsp: NGSPa1CStatisticValue, average: glucoseAverage * (units == .mmolL ? conversionFactor : 1), median: medianGlucose * (units == .mmolL ? conversionFactor : 1), sd: sd * (units == .mmolL ? conversionFactor : 1), cv: cv, readings: Double(countReadings) / denominator ) return output } private func tir() -> [(decimal: Decimal, string: String)] { let glucose = fetchRequestReadings let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) }) let totalReadings = justGlucoseArray.count let hyperArray = glucose.filter({ $0.glucose >= Int(highLimit) }) let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100 let hypoArray = glucose.filter({ $0.glucose <= Int(lowLimit) }) let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100 let tir = 100 - (hypoPercentage + hyperPercentage) var array: [(decimal: Decimal, string: String)] = [] array.append((decimal: Decimal(hypoPercentage), string: "Low")) array.append((decimal: Decimal(tir), string: "NormaL")) array.append((decimal: Decimal(hyperPercentage), string: "High")) return array } }