AlarmCondition.swift 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. // LoopFollow
  2. // AlarmCondition.swift
  3. import Foundation
  4. protocol AlarmCondition {
  5. static var type: AlarmType { get }
  6. init()
  7. /// pure, per-alarm logic against `AlarmData`
  8. func evaluate(alarm: Alarm, data: AlarmData, now: Date) -> Bool
  9. }
  10. extension AlarmCondition {
  11. /// Returns `true` when the alarm is allowed to continue evaluating
  12. /// after BG-limit checks; `false` blocks it immediately.
  13. func passesBGLimits(alarm: Alarm, data: AlarmData) -> Bool {
  14. let bgReading = data.bgReadings.last?.sgv
  15. let haveBG = (bgReading ?? 0) > 0
  16. let bgValue = Double(bgReading ?? 0)
  17. // ────────────────────────────────────
  18. // 1. BG-based alarms always need data
  19. // ────────────────────────────────────
  20. if alarm.type.isBGBased && !haveBG { return false }
  21. // ────────────────────────────────────
  22. // 2. No limits? we’re done.
  23. // ────────────────────────────────────
  24. if alarm.belowBG == nil && alarm.aboveBG == nil { return true }
  25. // If we reach here, there *are* limits.
  26. // Non-BG alarms without a reading must fail;
  27. // BG-based alarms already bailed out above.
  28. guard haveBG else { return false }
  29. switch (alarm.belowBG, alarm.aboveBG) {
  30. case let (lo?, hi?):
  31. return lo < hi ? (bgValue <= lo || bgValue >= hi) // fire outside band
  32. : (hi <= bgValue && bgValue <= lo) // fire inside band
  33. case let (lo?, nil):
  34. return bgValue <= lo
  35. case let (nil, hi?):
  36. return bgValue >= hi
  37. default:
  38. return true
  39. }
  40. }
  41. /// applies every global & per-alarm guard exactly once
  42. func shouldFire(alarm: Alarm, data: AlarmData, now: Date, config: AlarmConfiguration) -> Bool {
  43. // master on/off
  44. guard alarm.isEnabled else { return false }
  45. // per-alarm snooze
  46. if let snooze = alarm.snoozedUntil, snooze > now { return false }
  47. if !passesBGLimits(alarm: alarm, data: data) { return false }
  48. // time-of-day guard
  49. let comps = Calendar.current.dateComponents([.hour, .minute], from: now)
  50. let nowMin = (comps.hour! * 60) + comps.minute!
  51. let dStart = config.dayStart.minutesSinceMidnight
  52. let nStart = config.nightStart.minutesSinceMidnight
  53. let isNight = (nowMin < dStart) || (nowMin >= nStart)
  54. switch alarm.activeOption {
  55. case .always:
  56. break
  57. case .day:
  58. // only fire in day
  59. guard !isNight else { return false }
  60. case .night:
  61. // only fire in night
  62. guard isNight else { return false }
  63. }
  64. // finally, run the type-specific logic
  65. return evaluate(alarm: alarm, data: data, now: now)
  66. }
  67. }