v1.5.0 Phase 5: outbreak digest mode (#76) #116

Merged
claude-bot merged 1 commit from feat/v1.5.0-phase5-digest into main 2026-07-19 16:38:30 +00:00
Contributor

Per-subscription opt-in batching for high-activity days. Off by default (null digest_threshold = today's behavior, byte-for-byte).

  • ChannelLocationSub.digest_threshold (nullable int) + SentAlert.digest_pending (bool) — migration 0028. DIGEST_FLUSH_MINUTES (30) + DIGEST_WINDOW_MINUTES (60).
  • Defer decision: once a (channel, location) sub has sent digest_threshold alerts within the rolling window, further advisory/watch alerts get their dedup record with digest_pending=True but no immediate send/outbox enqueue.
  • ⚠️ Warning-class alerts are never batched — they always dispatch immediately. Digest is a noise-reduction opt-in; holding a tornado/flash-flood warning up to the flush interval is unacceptable for a life-safety tool, and an operator taming advisory spam wouldn't expect it. (If you'd instead prefer everything batched during an outbreak, it's a one-line change — say so and I'll flip it.)
  • Flush job (every DIGEST_FLUSH_MINUTES): groups pending rows by (channel, location), builds a synthetic "Weather Alert Digest" NWSAlert summarizing the batch (counts by event, highest severity), and dispatches it through the existing per-notifier send path — so all 9 notifiers render it with no new code. Clears digest_pending atomically with a kind="digest" ledger row (which the alert outbox never re-drives) to prevent double-send; skip_locked select; respects quiet hours; fail-soft per group.
  • Subscription add/edit UI + schema validation + config import/export round-trip.

Testing (dev server)

ruff clean; 7 digest tests (incl. test_warning_class_never_defers); bulk suite 746 passed; migration 0028 applied on a restored copy of the real dev DB (both columns present).

Closes #76

🤖 Generated with Claude Code

Per-subscription opt-in batching for high-activity days. Off by default (null `digest_threshold` = today's behavior, byte-for-byte). - `ChannelLocationSub.digest_threshold` (nullable int) + `SentAlert.digest_pending` (bool) — migration **0028**. `DIGEST_FLUSH_MINUTES` (30) + `DIGEST_WINDOW_MINUTES` (60). - **Defer decision**: once a `(channel, location)` sub has sent `digest_threshold` alerts within the rolling window, further **advisory/watch** alerts get their dedup record with `digest_pending=True` but no immediate send/outbox enqueue. - **⚠️ Warning-class alerts are never batched** — they always dispatch immediately. Digest is a noise-reduction opt-in; holding a tornado/flash-flood warning up to the flush interval is unacceptable for a life-safety tool, and an operator taming advisory spam wouldn't expect it. (If you'd instead prefer *everything* batched during an outbreak, it's a one-line change — say so and I'll flip it.) - **Flush job** (every `DIGEST_FLUSH_MINUTES`): groups pending rows by `(channel, location)`, builds a synthetic "Weather Alert Digest" `NWSAlert` summarizing the batch (counts by event, highest severity), and dispatches it through the **existing per-notifier send path** — so all 9 notifiers render it with no new code. Clears `digest_pending` atomically with a `kind="digest"` ledger row (which the alert outbox never re-drives) to prevent double-send; `skip_locked` select; respects quiet hours; fail-soft per group. - Subscription add/edit UI + schema validation + config import/export round-trip. ## Testing (dev server) ruff clean; 7 digest tests (incl. `test_warning_class_never_defers`); bulk suite **746 passed**; migration 0028 applied on a **restored copy of the real dev DB** (both columns present). Closes #76 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add outbreak digest mode (#76)
All checks were successful
CI / test (pull_request) Successful in 4m26s
c9ee2e5dab
Per-subscription opt-in: once a (channel, location) sub has sent digest_threshold
alerts within a rolling window, further NON-critical alerts are batched into a
periodic digest instead of individual messages. Off by default (null threshold =
byte-for-byte unchanged behavior).

- ChannelLocationSub.digest_threshold (nullable int) + SentAlert.digest_pending
  (bool) — migration 0028. DIGEST_FLUSH_MINUTES (30) + DIGEST_WINDOW_MINUTES (60).
- Defer decision in _process_single_alert: over-threshold advisory/watch alerts get
  their dedup record with digest_pending=True but no immediate send/outbox enqueue.
  WARNING-class alerts are NEVER deferred — they always dispatch immediately, since
  digest is a noise-reduction opt-in and delaying a life-safety warning up to the
  flush interval is unacceptable.
- flush_alert_digests job (every DIGEST_FLUSH_MINUTES): groups pending rows by
  (channel, location), builds a synthetic "Weather Alert Digest" NWSAlert
  summarizing the batch, and dispatches it through the existing per-notifier send
  path (no new per-notifier code). Clears digest_pending atomically with the ledger
  row (kind="digest", which the alert outbox never re-drives) to prevent
  double-send; skip_locked select; respects quiet hours; fail-soft per group.
- Subscription add/edit UI + schema validation + config import/export round-trip.

Full suite green on the dev server: 746 passed; migration 0028 verified on a
real-DB copy.

Closes #76

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude-bot deleted branch feat/v1.5.0-phase5-digest 2026-07-19 16:38:31 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/WeatherBot!116
No description provided.