Commit graph

5 commits

Author SHA1 Message Date
fc3eb58910
Refresh propagation algo against expanded prod corpus (2026-04-13)
Direct queries against prod (75M HRRR profiles, 16,864 soundings, 392k
surface obs, new hrrr_native_profiles table) drive these changes:

* Reinstate shallow-BL bonus as an HPBL multiplier on the refractivity
  score (1.10× <200m → 0.78× ≥2000m). The Apr 11 "shallow BL bonus
  removed" conclusion was an artifact of the prior matching strategy;
  with the cleaner contact↔HRRR join (n=680) the binned data goes 230 km
  avg at HPBL <200m vs 100 km at ≥2000m, monotonic across all bins.
* Add 6 missing bands (142, 145, 288, 322, 403, 411 GHz) so contacts in
  those bands stop being silently dropped. Coefficients extrapolate
  ITU-R P.676/P.838 trends from the existing 134/241 GHz entries.
* Bump ERA5 poll timeout 10 min → 1 hour and Era5MonthBatchWorker
  max_attempts 3 → 5 so CDS slowness stops discarding tiles. Also dump
  the full failure body when CDS omits the message field — the prior
  "ERA5 job failed: nil" was masking real error reasons in oban_jobs.
* Add scripts/recalibrate_algo.py so this analysis can be re-run any
  time new data lands without manual SQL. Reads PROP_PROD_DB_URL from
  .envrc, drops a Markdown report into docs/algo-reports/.
* Append a dated Part 2c to algo.md documenting the corpus expansion,
  the honest accounting of historical HRRR coverage (only ~1,020 of 58k
  contacts are precision-matchable), and the empty era5/rtma/climatology
  tables. Update the gaseous-absorption and rain-attenuation tables to
  include the new bands.

Test suite: 1,335 tests, 0 failures.
2026-04-13 10:59:01 -05:00
51e390959c Add dialyzer specs and types across the codebase
277 @spec/@type annotations added to 58 files covering all public
APIs: contexts (propagation, radio, weather, terrain, beacons,
commercial), GRIB2 decoders, terrain analysis, duct detection,
rain scatter, CSV/ADIF import, weather clients, and all Ecto
schemas. Dialyzer passes with 0 errors.
2026-04-12 08:55:04 -05:00
8637253fda Batch ERA5 fetches by month and 2° tile
Per-point ERA5 fetches were tragically slow because every point-hour
triggered its own asynchronous CDS job (submit → poll → assemble →
download). For backfill this meant thousands of independent jobs
queued against Copernicus. The new path groups requests by calendar
month and a 2° × 2° lat/lon tile so one CDS cycle populates ~60k
profiles at once, and Oban uniqueness on (year, month, tile_lat,
tile_lon) collapses every duplicate enqueue.

- Era5BatchClient builds the monthly CDS requests, extracts every
  (lat, lon, hour) from the GRIB2 blob with wgrib2, derives
  refractivity params, and bulk-inserts in 2k-row chunks with
  on_conflict: :nothing. fetch_month_into_db/1 short-circuits when
  the month-tile already has any cached profile.
- Era5MonthBatchWorker runs the batch on the :era5 queue with a
  generous backoff (10m → 1d) and the uniqueness key above.
- Era5FetchWorker is now a thin router: cache hit → :ok, cache miss
  → enqueue the month-batch for the point's tile-month and return.
  No more per-point CDS calls.
- Wgrib2 grows extract_grid_messages/3 which preserves per-message
  datetimes by parsing `d=YYYYMMDDHH[MMSS]` from the inventory, so a
  single GRIB2 file carrying a whole month decodes correctly.
- The era5_backfill mix task enqueues month-tile batches directly.
2026-04-09 13:01:49 -05:00
fbf24622fa Fix ERA5 CDS API endpoint for new v1 API
- Submit: /api/retrieve/v1/processes/{dataset}/execution
- Request body wrapped in {"inputs": ...}
- Poll: /api/retrieve/v1/jobs/{jobId}
- Status field: "status" not "state", values: accepted/running/successful/failed
- Download: GET {job_url}/results, follow asset href link
- Add ERA5 credentials to k8s secret manifest
2026-04-07 12:32:21 -05:00
dea407c8ca Add ERA5 reanalysis and RTMA data sources
ERA5 (Copernicus CDS API):
- Era5Profile schema matching HRRR profile structure for interop
- Era5Client with async job submission, polling, GRIB2 download
- Era5FetchWorker (Oban queue: era5, max_attempts: 5)
- Unified lookup: Weather.best_profile_for_contact/1 tries HRRR
  first, falls back to ERA5 for pre-2014 contacts

RTMA (NOAA S3, 2.5km/15-min):
- RtmaObservation schema for surface-only fields
- RtmaClient with byte-range GRIB2 requests (same pattern as HRRR)
- RtmaFetchWorker (Oban queue: rtma, max_attempts: 10)
- Weather.find_nearest_rtma/3 for surface condition lookup

Both queues added to production Oban config (2 workers each).
2026-04-07 12:04:16 -05:00