| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- // LoopFollow
- // AlarmCondition.swift
- import Foundation
- protocol AlarmCondition {
- static var type: AlarmType { get }
- init()
- /// pure, per-alarm logic against `AlarmData`
- func evaluate(alarm: Alarm, data: AlarmData, now: Date) -> Bool
- }
- extension AlarmCondition {
- /// Returns `true` when the alarm is allowed to continue evaluating
- /// after BG-limit checks; `false` blocks it immediately.
- func passesBGLimits(alarm: Alarm, data: AlarmData) -> Bool {
- let bgReading = data.bgReadings.last?.sgv
- let haveBG = (bgReading ?? 0) > 0
- let bgValue = Double(bgReading ?? 0)
- // ────────────────────────────────────
- // 1. BG-based alarms always need data
- // ────────────────────────────────────
- if alarm.type.isBGBased && !haveBG { return false }
- // ────────────────────────────────────
- // 2. No limits? we’re done.
- // ────────────────────────────────────
- if alarm.belowBG == nil && alarm.aboveBG == nil { return true }
- // If we reach here, there *are* limits.
- // Non-BG alarms without a reading must fail;
- // BG-based alarms already bailed out above.
- guard haveBG else { return false }
- switch (alarm.belowBG, alarm.aboveBG) {
- case let (lo?, hi?):
- return lo < hi ? (bgValue <= lo || bgValue >= hi) // fire outside band
- : (hi <= bgValue && bgValue <= lo) // fire inside band
- case let (lo?, nil):
- return bgValue <= lo
- case let (nil, hi?):
- return bgValue >= hi
- default:
- return true
- }
- }
- /// applies every global & per-alarm guard exactly once
- func shouldFire(alarm: Alarm, data: AlarmData, now: Date, config: AlarmConfiguration) -> Bool {
- // master on/off
- guard alarm.isEnabled else { return false }
- // per-alarm snooze
- if let snooze = alarm.snoozedUntil, snooze > now { return false }
- if !passesBGLimits(alarm: alarm, data: data) { return false }
- // time-of-day guard
- let comps = Calendar.current.dateComponents([.hour, .minute], from: now)
- let nowMin = (comps.hour! * 60) + comps.minute!
- let dStart = config.dayStart.minutesSinceMidnight
- let nStart = config.nightStart.minutesSinceMidnight
- let isNight = (nowMin < dStart) || (nowMin >= nStart)
- switch alarm.activeOption {
- case .always:
- break
- case .day:
- // only fire in day
- guard !isNight else { return false }
- case .night:
- // only fire in night
- guard isNight else { return false }
- }
- // finally, run the type-specific logic
- return evaluate(alarm: alarm, data: data, now: now)
- }
- }
|