IOBCondition.swift 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. // LoopFollow
  2. // IOBCondition.swift
  3. //
  4. // IOBCondition.swift
  5. // LoopFollow
  6. //
  7. // Created by Jonas Björkert on 2025-05-17.
  8. //
  9. import Foundation
  10. /// Fires when **any** of three rules is true
  11. /// ──────────────────────────────────────────
  12. /// 1. latest IOB ≥ `threshold`
  13. /// 2. within the last `lookbackMinutes`, the **count** of boluses
  14. /// ≥ `monitoringWindow` **and** each bolus ≥ `delta` units
  15. /// 3. within that same window, the **sum** of boluses ≥ `threshold`
  16. struct IOBCondition: AlarmCondition {
  17. static let type: AlarmType = .iob
  18. init() {}
  19. /// Convenience accessors to keep the code tidy
  20. private struct Params {
  21. let iobMax: Double // alarm.threshold (units)
  22. let minBolus: Double // alarm.delta (units)
  23. let countNeeded: Int // alarm.monitoringWindow (bolus count)
  24. let lookbackMin: Int // alarm.predictiveMinutes (minutes)
  25. init?(alarm: Alarm) {
  26. guard
  27. let iobMax = alarm.threshold,
  28. let minBolus = alarm.delta,
  29. let count = alarm.monitoringWindow,
  30. let mins = alarm.predictiveMinutes,
  31. iobMax > 0, minBolus > 0, count > 0, mins > 0
  32. else { return nil }
  33. self.iobMax = iobMax
  34. self.minBolus = minBolus
  35. countNeeded = count
  36. lookbackMin = mins
  37. }
  38. }
  39. func evaluate(alarm: Alarm, data: AlarmData, now _: Date) -> Bool {
  40. // ── 0. pull and sanity-check parameters ────────────────────────────
  41. guard let p = Params(alarm: alarm) else { return false }
  42. // ── 1. latest IOB alone high enough? ───────────────────────────────
  43. if let iob = data.IOB, iob >= p.iobMax { return true }
  44. // ── 2. look at the recent boluses ──────────────────────────────────
  45. let cutoff = Date().addingTimeInterval(-Double(p.lookbackMin) * 60)
  46. var count = 0
  47. var total = 0.0
  48. for b in data.recentBoluses where b.date >= cutoff && b.units >= p.minBolus {
  49. count += 1
  50. total += b.units
  51. if count >= p.countNeeded || total >= p.iobMax { return true }
  52. }
  53. return false
  54. }
  55. }