v1.5.0 Phase 2b: lead-time / time-to-impact display (#84) #113

Merged
claude-bot merged 1 commit from feat/v1.5.0-phase2b-leadtime into main 2026-07-19 15:18:38 +00:00
Contributor

Surfaces "arrives in ~40 min" wherever an NWS alert's onset is in the future. onset was already parsed onto NWSAlert but never shown, and the dashboard reads SentAlert, which had no onset — so this persists it.

  • Schema: SentAlert.onset (nullable timestamptz) + migration 0026; _record_and_dispatch populates it on new and refreshed records.
  • Helper: time_formatting.lead_time_text(onset, now)"in ~40 min" / "in ~1.5 hr" / None (past/absent), UTC-safe; arrives_line(alert) wraps it for message bodies.
  • Notifications: an "Arrives: in ~X min" line in all 9 notifiers (discord embed + text, webhook embed + a lead_time JSON key, email, matrix, signal, sms segment-budgeted, pushover, webex, ntfy). Omitted (empty, dropped by compose) when onset is absent/past → bodies byte-for-byte unchanged in that common case.
  • Dashboard: build_dashboard_context computes _lead_time per active alert; the card shows a " arrives in ~40 min" span (server-side + SSE JS), and the SSE payload carries onset/onset_local/lead_time. Static (computed at page-load / dispatch), matching the existing issued/expires rendering.

Bonus: SentAlert.onset now also lets the #75 feeds expose real onset in a later pass (they currently null it).

Testing (dev server)

ruff clean; targeted tests 70 passed; bulk suite 709 passed; migration 0026 applied on a restored copy of the real dev DB (onset column present). Most alerts have onset == sent-time or in the past → they correctly show no lead time; the feature only fires for genuinely future onsets (pre-issued watches, timed warnings).

Closes #84

🤖 Generated with Claude Code

Surfaces "arrives in ~40 min" wherever an NWS alert's `onset` is in the future. `onset` was already parsed onto `NWSAlert` but never shown, and the dashboard reads `SentAlert`, which had no `onset` — so this persists it. - **Schema**: `SentAlert.onset` (nullable timestamptz) + migration **0026**; `_record_and_dispatch` populates it on new and refreshed records. - **Helper**: `time_formatting.lead_time_text(onset, now)` → `"in ~40 min"` / `"in ~1.5 hr"` / `None` (past/absent), UTC-safe; `arrives_line(alert)` wraps it for message bodies. - **Notifications**: an "Arrives: in ~X min" line in all 9 notifiers (discord embed + text, webhook embed + a `lead_time` JSON key, email, matrix, signal, sms segment-budgeted, pushover, webex, ntfy). Omitted (empty, dropped by `compose`) when onset is absent/past → **bodies byte-for-byte unchanged** in that common case. - **Dashboard**: `build_dashboard_context` computes `_lead_time` per active alert; the card shows a "⏳ arrives in ~40 min" span (server-side + SSE JS), and the SSE payload carries `onset`/`onset_local`/`lead_time`. Static (computed at page-load / dispatch), matching the existing issued/expires rendering. Bonus: `SentAlert.onset` now also lets the #75 feeds expose real onset in a later pass (they currently null it). ## Testing (dev server) ruff clean; targeted tests 70 passed; bulk suite **709 passed**; migration 0026 applied on a **restored copy of the real dev DB** (onset column present). Most alerts have onset == sent-time or in the past → they correctly show no lead time; the feature only fires for genuinely future onsets (pre-issued watches, timed warnings). Closes #84 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Show lead-time / time-to-impact ("arrives in ~40 min") (#84)
All checks were successful
CI / test (pull_request) Successful in 3m59s
10458d2223
onset is parsed onto NWSAlert but was never surfaced. Persist it and show a
friendly time-to-impact wherever an alert's onset is in the future.

- SentAlert gains a nullable onset column (migration 0026); _record_and_dispatch
  populates it on new and refreshed records.
- New time_formatting.lead_time_text(onset, now) -> "in ~40 min" / "in ~1.5 hr" /
  None (past/absent), UTC-safe; arrives_line(alert) wraps it for message bodies.
- Notifications: an "Arrives: in ~X min" line added to all 9 notifiers' alert
  bodies (discord embed + text, webhook embed + a lead_time JSON key, email,
  matrix, signal, sms (segment-budgeted), pushover, webex, ntfy). Omitted (empty,
  dropped by compose/fit) when onset is absent/past — bodies are byte-for-byte
  unchanged in that common case.
- Dashboard: build_dashboard_context computes _lead_time per active alert; the
  alert card renders a " arrives in ~40 min" span (server-side + SSE JS), and the
  SSE payload carries onset/onset_local/lead_time. Static (computed at
  page-load/dispatch), matching the existing issued/expires rendering.

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

Closes #84

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude-bot deleted branch feat/v1.5.0-phase2b-leadtime 2026-07-19 15:18:38 +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!113
No description provided.