LowBGCondition.swift 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. // LoopFollow
  2. // LowBGCondition.swift
  3. import Foundation
  4. /// Fires when:
  5. /// • every BG in `persistentMinutes` (if set) **and** the latest BG are ≤ `threshold`; **or**
  6. /// • any predicted BG within `predictiveMinutes` is ≤ `threshold`.
  7. struct LowBGCondition: AlarmCondition {
  8. static let type: AlarmType = .low
  9. init() {}
  10. func evaluate(alarm: Alarm, data: AlarmData, now _: Date) -> Bool {
  11. // ────────────────────────────────
  12. // 0. sanity checks
  13. // ────────────────────────────────
  14. guard let belowBG = alarm.belowBG else { return false }
  15. func isLow(_ g: GlucoseValue) -> Bool {
  16. g.sgv > 0 && Double(g.sgv) <= belowBG
  17. }
  18. // ────────────────────────────────
  19. // 1. predictive low?
  20. // ────────────────────────────────
  21. var predictiveTrigger = false
  22. if let predictiveMinutes = alarm.predictiveMinutes,
  23. predictiveMinutes > 0,
  24. !data.predictionData.isEmpty
  25. {
  26. let lookAhead = min(
  27. data.predictionData.count,
  28. Int(ceil(Double(predictiveMinutes) / 5.0))
  29. )
  30. for i in 0 ..< lookAhead where isLow(data.predictionData[i]) {
  31. predictiveTrigger = true
  32. break
  33. }
  34. }
  35. // ────────────────────────────────
  36. // 2. persistent low window (ALL readings must be low)
  37. // ────────────────────────────────
  38. var persistentOK = true
  39. if let persistentMinutes = alarm.persistentMinutes,
  40. persistentMinutes > 0
  41. {
  42. let window = Int(ceil(Double(persistentMinutes) / 5.0))
  43. if data.bgReadings.count >= window {
  44. let recent = data.bgReadings.suffix(window)
  45. persistentOK = recent.allSatisfy(isLow)
  46. } else {
  47. // not enough samples to prove persistence ⇒ don’t alarm yet
  48. persistentOK = false
  49. }
  50. }
  51. // ────────────────────────────────
  52. // 3. rising BG suppression (optional)
  53. // Stay silent while the latest reading is above the previous one;
  54. // only alarm when BG is flat or still falling.
  55. // ────────────────────────────────
  56. if alarm.suppressIfRising, data.bgReadings.count >= 2 {
  57. let last = data.bgReadings[data.bgReadings.count - 1]
  58. let previous = data.bgReadings[data.bgReadings.count - 2]
  59. if last.sgv > 0, previous.sgv > 0, last.sgv > previous.sgv {
  60. return false
  61. }
  62. }
  63. // ────────────────────────────────
  64. // 4. final decision
  65. // ────────────────────────────────
  66. return persistentOK || predictiveTrigger
  67. }
  68. }