Capture periodic radar frames for active alerts (#130) #144
No reviewers
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot!144
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/radar-frames"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #130. Second capture issue of the v2.0.0 Historical Explorer epic (#22).
Problem
Radar-at-alert-time capture (#85) produced exactly one image per alert lifecycle —
_radar_snapshot_filenameis deterministic per(lifecycle_id, location_id)so re-fires overwrite, andSentAlertcarries a singleradar_snapshot_path. A radar loop (#140) needs many frames. Since uncaptured history is unrecoverable, capture had to become periodic before #140 is built, or every event predating it would be a single still.It was also off by default (
ALERT_RADAR_SNAPSHOT_ENABLED = False), and image lifetime was only transitively tied tosent_alerts— a never-cleared row kept its image forever.Cadence decision
Periodic frames with a hard per-event cap. Sizing at ~120 KB/PNG and ~150 alerts/location/year:
~10x, judged affordable. The cap is the guardrail that matters: it bounds disk growth and outbound load on NOAA's MapServer during an outbreak, when many alerts are active at once. Captures within a cycle also run sequentially rather than fanned out, for the same reason.
Change
alert_radar_framestable (migration0031) keyed to the dashboardsent_alertsrow, with a timestamped filename so frames accumulate.poll_alert_radar_framesscheduler job appending a frame to each active dashboard alert, gated byALERT_RADAR_FRAME_INTERVAL_MINUTES(5) and capped byALERT_RADAR_FRAME_MAX_PER_EVENT(24 → ~2h coverage). Excludes synthetic SPC rows, cleared and expired alerts.ALERT_RADAR_SNAPSHOT_ENABLEDnow defaults to true; the first-fire capture additionally records frame #1, reusing the bytes already fetched rather than a second upstream call.RETENTION_ALERT_RADAR_FRAMES_DAYS, 400) instead of a transitive lifetime.GET /media/alert-radar/frames/{frame_id}.png, mirroring the existing route's validation and cache posture.SentAlert.radar_snapshot_path/radar_snapshot_atare unchanged as the representative first frame, so/media/alert-radar/{id}.pngand the public alert page keep working. Frames are purely additive.Two file-ownership hazards, both fixed and tested
The first frame deliberately shares its filename with
radar_snapshot_path(one file on disk, not two), and frames andsent_alertshave independent retention windows. So each sweep could delete a file the other still owns:_cleanup_alert_radar_snapshotsnow treats any filename referenced by a liveAlertRadarFrame.pathas referenced;_cleanup_alert_radar_framesdrops the row but leaves the file when it is still pointed at by a survivingSentAlert.radar_snapshot_path, which would otherwise 404/media/alert-radar/{id}.pngfor a still-retained alert.Both directions have a regression test.
Test-suite guard
With capture enabled by default, any test dispatching a dashboard alert would schedule a live NOAA fetch. A new autouse conftest fixture patches
radar._fetch_radarto the "no image" path — exercised fail-soft, no network. Tests asserting on capture override it.Verification
Run locally in Docker against every CI gate:
ruff check .python -m compileall appalembic upgrade headon fresh Postgres 1600310031downgrade → re-upgrade round tripDeferred
Routing writes through the media storage abstraction is left to #131, which moves every media call site at once — doing it here would mean writing the same code twice.
🤖 Generated with Claude Code