Commit graph

3 commits

Author SHA1 Message Date
c318c3a932
Nudge HRRR scores with live ASOS obs between runs
Adds Microwaveprop.Propagation.AsosNudge: a pure IDW bias-field module
that takes ASOS observations + HRRR profiles and returns re-scored grid
rows for every cell within 250km of a reporting station. Upper-air
fields (min_refractivity_gradient, pwat_mm, hpbl_m, profile, duct
metadata) pass through unchanged so HRRR's signal isn't clobbered.

The old AsosAdjustmentWorker was unwired and buggy — nil'd out ~22% of
the scoring weight and wrote orphan timestamps. Replaced with a slim
worker that queries the latest HRRR valid_time, fetches live ASOS
currents, calls AsosNudge.compute/3, and upserts onto
(lat, lon, valid_time, band_mhz) so nudged values overwrite the HRRR
hour cleanly instead of polluting available_valid_times. After each
upsert it warms ScoreCache and broadcasts propagation:updated so live
/map clients refresh.

Cron hooked up every 10 minutes in config.exs and dev.exs. Also cleaned
up the stale "dev has propagation disabled" note in CLAUDE.md.

13 new AsosNudge unit tests cover: residual computation (co-located,
out-of-grid, nil fields), IDW weighting (single station, far station,
two equidistant stations, nil component handling), upper-air
preservation, and the compute/3 entry point's shape and radius filter.

Drive-by Styler formatting touched a handful of unrelated files from
`mix format`.
2026-04-12 14:27:27 -05:00
250709a1b2 Add more caching to make the map feel instant
- ScoreCache stores {band, valid_time} as %{{lat, lon} => score} map so
  point lookups are O(1); adds fetch_point/4 and valid_times/1
- available_valid_times/1 reads directly from ScoreCache when warm,
  falls back to DB on cold start
- point_forecast/3 iterates cached valid_times and uses fetch_point/4
  instead of hitting the DB per click
- NexradCache: node-local ETS cache of decoded n0q PNG pixel buffers
  keyed by 5-minute rounded timestamp; skips ~1-5s HTTP+decode on
  concurrent/repeat clicks within the same window
- MapLive: start_async the rain_scatter fetch so point_detail renders
  immediately with a pending marker; push rain_scatter_update when
  NEXRAD resolves
- MapLive: preload all 18 remaining forecast hours for the current
  viewport after mount/band change/propagation_updated; client caches
  them and renders timeline scrubs instantly without a server roundtrip.
  Adds set_selected_time event for fast-path state sync.
- Propagation map JS: forecastCache map + drawScatterMarkers helper,
  timeline click uses preloaded cache when available
2026-04-12 12:26:25 -05:00
d880201713 Cache propagation scores in ETS, broadcast across cluster
- Add ScoreCache GenServer with node-local ETS table keyed by
  {band, valid_time}, subscribed to "propagation:cache" PubSub topic so
  every pod stays in sync with a single hourly compute
- scores_at/3 checks cache first, falls back to DB and populates on miss
- PropagationGridWorker warms and broadcasts the cache for each band
  after every forecast hour upsert; prunes >2h old entries
- Replace per-pixel string-keyed Map with flat Int8Array over the CONUS
  grid in propagation_map_hook.ts to eliminate allocations in the tile
  rasterization hot loop (interpolateScore / propagationReach)
2026-04-12 12:11:26 -05:00