AGPCalculator.swift 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. // LoopFollow
  2. // AGPCalculator.swift
  3. import Foundation
  4. class AGPCalculator {
  5. static func calculate(bgData: [ShareGlucoseData]) -> [AGPDataPoint] {
  6. guard !bgData.isEmpty else { return [] }
  7. var hourData: [Int: [Double]] = [:]
  8. let calendar = dateTimeUtils.displayCalendar()
  9. for reading in bgData {
  10. let date = Date(timeIntervalSince1970: reading.date)
  11. let components = calendar.dateComponents([.hour], from: date)
  12. let hour = components.hour ?? 0
  13. let glucose = Double(reading.sgv)
  14. let glucoseMgdL = Storage.shared.units.value == "mg/dL" ? glucose : glucose * GlucoseConversion.mmolToMgDl
  15. if hourData[hour] == nil {
  16. hourData[hour] = []
  17. }
  18. hourData[hour]?.append(glucoseMgdL)
  19. }
  20. var agpPoints: [AGPDataPoint] = []
  21. for hour in 0 ..< 24 {
  22. guard let values = hourData[hour], !values.isEmpty else { continue }
  23. let sorted = values.sorted()
  24. let p5 = PercentileCalculator.percentile(sorted, p: 0.05)
  25. let p25 = PercentileCalculator.percentile(sorted, p: 0.25)
  26. let p50 = PercentileCalculator.percentile(sorted, p: 0.50)
  27. let p75 = PercentileCalculator.percentile(sorted, p: 0.75)
  28. let p95 = PercentileCalculator.percentile(sorted, p: 0.95)
  29. let convert: (Double) -> Double = { value in
  30. Storage.shared.units.value == "mg/dL" ? value : value * GlucoseConversion.mgDlToMmolL
  31. }
  32. let minutesSinceMidnight = hour * 60
  33. agpPoints.append(AGPDataPoint(
  34. timeOfDay: minutesSinceMidnight,
  35. p5: convert(p5),
  36. p25: convert(p25),
  37. p50: convert(p50),
  38. p75: convert(p75),
  39. p95: convert(p95)
  40. ))
  41. }
  42. return agpPoints.sorted { $0.timeOfDay < $1.timeOfDay }
  43. }
  44. }
  45. class PercentileCalculator {
  46. static func percentile(_ sorted: [Double], p: Double) -> Double {
  47. guard !sorted.isEmpty else { return 0.0 }
  48. if sorted.count == 1 { return sorted[0] }
  49. let index = p * Double(sorted.count - 1)
  50. let lower = Int(index.rounded(.down))
  51. let upper = min(lower + 1, sorted.count - 1)
  52. let weight = index - Double(lower)
  53. return sorted[lower] * (1.0 - weight) + sorted[upper] * weight
  54. }
  55. }