Commit graph

607 commits

Author SHA1 Message Date
f2efdd4ece Stream HRRR native downloads to disk to prevent OOM
Instead of holding ~530MB GRIB binary in BEAM memory, download
ranges directly to a temp file and run wgrib2 on it. Peak memory
drops from ~530MB to just HTTP chunk buffers.
2026-04-10 15:44:36 -05:00
FluxCD
1c896ec676 chore: update prop image to git.mcintire.me/graham/prop:main-1775848424-575a9af [skip ci] 2026-04-10 19:15:22 +00:00
4487fc22e9 Fix NexradWorker UUID type: use generate() not bingenerate() for insert_all 2026-04-10 14:13:38 -05:00
FluxCD
1f8fc4d4aa chore: update prop image to git.mcintire.me/graham/prop:main-1775848082-4708df9 [skip ci] 2026-04-10 19:09:19 +00:00
0edb1bd95a Fix NexradWorker crash when minute key is missing from args
Jobs enqueued via backfill only have year/month/day/hour — default
minute to 0 instead of requiring it in the pattern match.
2026-04-10 14:07:55 -05:00
FluxCD
147ef7dd6b chore: update prop image to git.mcintire.me/graham/prop:main-1775847868-948cb6d [skip ci] 2026-04-10 19:05:19 +00:00
ea0c41ef50 Fix crash on /submit when LiveStash restores CSV tab without csv_result assign
Set all assigns before LiveStash recovery so reconnecting to the CSV
tab doesn't crash on missing :csv_result / :csv_preview keys.
2026-04-10 14:04:16 -05:00
FluxCD
d071b02cea chore: update prop image to git.mcintire.me/graham/prop:main-1775846284-76029d9 [skip ci] 2026-04-10 18:41:29 +00:00
0e3c06d040 Reduce hrrr queue to 1 per pod to prevent OOM
Native HRRR files are ~400MB each. At hrrr:5 per pod, 5 concurrent
downloads exceed the 4GB pod memory limit causing OOMKilled.
2026-04-10 13:37:37 -05:00
FluxCD
b5d33def74 chore: update prop image to git.mcintire.me/graham/prop:main-1775843322-3bd02fa [skip ci] 2026-04-10 17:51:50 +00:00
e7a7ae073d Phase 9.3, 9.4, and Phase 3 NEXRAD pipeline
Task 9.3 - Weight recalibration via gradient descent:
- Recalibrator module fits logistic regression weights using Nx
- Trains on QSO positives vs random baseline negatives
- Cross-validates by month, normalizes weights to sum to 1.0
- Mix task: mix recalibrate_scorer --sample 5000 --epochs 2000

Task 9.4 - Side-by-side scorer comparison:
- ScorerDiff.compare/3 re-scores grid with old vs new weights
- Reports mean diff, regressions, improvements, per-band breakdown
- Mix task: mix scorer_diff --new-weights '{...}'

Phase 3 - NEXRAD ingestion pipeline:
- NexradClient fetches IEM n0q composite PNGs, extracts per-point
  box statistics (mean/max dBZ, texture variance)
- NexradObservation schema with unique (lat, lon, observed_at)
- NexradWorker on :nexrad queue for background processing
- nexrad_texture backtest feature in Features module
- mix nexrad_backfill --limit 200

All tasks added to AdminTaskWorker and Release for production use.
1116 tests, 0 failures.
2026-04-10 12:48:36 -05:00
FluxCD
ae049dcb8f chore: update prop image to git.mcintire.me/graham/prop:main-1775841968-8f0a37c [skip ci] 2026-04-10 17:28:26 +00:00
01909dbe66 Run admin tasks as Oban jobs instead of blocking eval
Release.backtest_all, climatology, native_derive now enqueue an
AdminTaskWorker job on the new :admin queue and return immediately.
Progress visible in Oban Web at /admin/oban.
2026-04-10 12:26:01 -05:00
FluxCD
046bef6c17 chore: update prop image to git.mcintire.me/graham/prop:main-1775841362-c8237c7 [skip ci] 2026-04-10 17:17:23 +00:00
d5842cb45c Add release tasks for backtest, climatology, and native backfill
All mix tasks now have equivalents in Microwaveprop.Release for
production use via `bin/microwaveprop eval`.
2026-04-10 12:14:41 -05:00
FluxCD
6c4d036177 chore: update prop image to git.mcintire.me/graham/prop:main-1775840246-033e4cc [skip ci] 2026-04-10 16:58:19 +00:00
6d92973853 Phase 9.1: Consolidated backtest report and contest log import
- Add `mix backtest --all` for consolidated pass/fail table across all features
- Add Backtest.consolidated_report/2 and to_consolidated_markdown/1
- Add Features.all_features/0 to auto-discover backtestable features
- Add `mix import_contest_logs` for bulk ARRL contest CSV import with dedup
- Fix hrrr_climatology to batch by (month, hour) to avoid query timeout
- Fix Repo.query! result pattern (Postgrex.Result, not tuple)
- Backtest reports for all Phase 1-6 features
2026-04-10 11:57:15 -05:00
FluxCD
234519bc66 chore: update prop image to git.mcintire.me/graham/prop:main-1775830416-95d649a [skip ci] 2026-04-10 14:14:18 +00:00
65e3159a85 Pause Oban queues in all mix tasks
Mix tasks that call app.start were also booting Oban's cron scheduler,
causing PropagationGridWorker and other cron jobs to fire during
backfills. Add Oban.pause_all_queues(Oban) immediately after app.start
in every mix task that only needs Repo access.
2026-04-10 09:13:30 -05:00
FluxCD
321300895e chore: update prop image to git.mcintire.me/graham/prop:main-1775829412-c884c0c [skip ci] 2026-04-10 13:58:11 +00:00
a840cb9629 Phase 5: Frontal geometry detection via Thermal Front Parameter
FrontalAnalysis module (Weather.FrontalAnalysis):
- detect_fronts/3 computes the Thermal Front Parameter (TFP) from
  2D grids of surface temperature and pressure using Nx vectorized
  ops. TFP = -nabla|nabla(theta)| . nabla(theta)/|nabla(theta)|.
  Most negative values mark cold fronts.
- central_gradient/1 for 2D finite differences with edge handling
- nearest_front/3 finds closest front point with distance and bearing
- path_front_angle/2 computes angle between a QSO path and the
  front (0 = parallel = good, 90 = crosses = dead)

Backtest feature stubs for distance_to_front and parallel_to_front
(return nil until the pipeline caches per-cell frontal features from
the hourly HRRR grid run). The FrontalAnalysis module itself is
tested and ready for integration.

NEXRAD spike docs also included in this commit.
2026-04-10 08:56:44 -05:00
FluxCD
e69d8e6685 chore: update prop image to git.mcintire.me/graham/prop:main-1775829262-fdaf89d [skip ci] 2026-04-10 13:56:10 +00:00
3ea2548114 Phase 8: 5-minute METAR ingestion pipeline
NCEI ASOS 5-minute data client (Weather.NceiMetarClient):
- fetch/3 pulls per-station monthly .dat files from NCEI C00418
- parse/1 decodes the fixed-width METAR format including precise
  T-group temperatures (T02110094 → 21.1/9.4°C)
- metar_5min_observations table: schema-identical to
  surface_observations, separate table to avoid mixing cadences

Weather.recent_surface_obs/3 prefers 5-min data when available,
falls back to the hourly surface_observations table.

Data URL: https://www.ncei.noaa.gov/data/automated-surface-observing-system-five-minute/access/YYYY/MM/asos-5min-KXXX-YYYYMM.dat
Available back to 1996.
2026-04-10 08:53:54 -05:00
FluxCD
1e17b29c5c chore: update prop image to git.mcintire.me/graham/prop:main-1775828838-09c326e [skip ci] 2026-04-10 13:48:04 +00:00
0f0e5e8d43 Phase 3 spike (parked) + Phase 6: temperature anomaly
Phase 3 NEXRAD spike: IEM n0q composite available at 5-min cadence
back to 2022+. Compression-ratio proxy shows afternoon images have
13-81% more texture than dawn (directionally correct), but the n0q
product thresholds out the faint clear-air returns needed for BL
stability detection. Parked until MRMS or Level III products can be
investigated. See docs/research/nexrad_spike.md.

Phase 6: hrrr_climatology table aggregating surface_temp_c by
(lat, lon, month, hour) from the 42M+ hrrr_profiles grid-point
rows. mix hrrr_climatology builds it via a single SQL GROUP BY +
upsert. Backtest.Features.temperature_anomaly computes current_temp
minus climatological mean — the meteorologist's "temperature
deviation above normal" predictor for summer afternoon enhancement.
2026-04-10 08:47:11 -05:00
FluxCD
d6192535c4 chore: update prop image to git.mcintire.me/graham/prop:main-1775828348-a839b52 [skip ci] 2026-04-10 13:39:59 +00:00
604140220a Phase 7: Regionalized seasonal scoring
Add Propagation.Region module with 8 CONUS climate zones (gulf_coast,
southeast, southern_plains, corn_belt, northeast, desert_southwest,
pacific_northwest, mountain_west) and per-region monthly seasonal
adjustment multipliers.

The scorer's score_season now takes lat/lon and applies a regional
multiplier from Region.seasonal_adjustment on top of the band's
seasonal_base + seasonal_adj. Gulf coast August gets a 1.15x boost
(drier, better for ducting) while Corn Belt August gets a 0.80x
penalty (corn evapotranspiration = miserable dewpoints).

Adjustments are hand-tuned starting points from the meteorologist's
qualitative guidance. Phase 9 recalibration will refine them from
backtest data.
2026-04-10 08:39:01 -05:00
82bf248ab7 Phase 4: Ray-traced duct geometry
Duct module (Propagation.Duct):
- refractivity_profile/1: ITU-R P.453 N at each native level
- m_profile/1: modified refractivity M = N + 157*h(km)
- detect_ducts/1: find contiguous regions where dM/dh < 0, returning
  base/top height, thickness, and M-deficit per duct
- min_trapped_frequency_ghz/1: waveguide approximation (Bean & Dutton)
  for the minimum frequency a duct of given geometry can trap
- analyze/1: full pipeline from native profile to duct list + best
  trapped frequency across all ducts

Derive task updated to also compute ducts JSONB and best_duct_band_ghz
alongside the Phase 2 turbulence fields.

Backtest features: duct_thickness, best_duct_freq, duct_usable_10ghz,
duct_usable_24ghz, duct_usable_47ghz.

Real-data validation: 2022-08-20 12Z TX profile shows 0 ducts (M
increases monotonically) — correct for a well-mixed boundary layer
on a turbulent August afternoon (Ri=0.16).
2026-04-10 08:31:16 -05:00
FluxCD
e2daf5a81d chore: update prop image to git.mcintire.me/graham/prop:main-1775827372-ce1b826 [skip ci] 2026-04-10 13:23:56 +00:00
FluxCD
8afc7b52e3 chore: update prop image to git.mcintire.me/graham/prop:main-1775827290-d0f6cb4 [skip ci] 2026-04-10 13:22:56 +00:00
3d58582754 Phase 2.5: Add turbulence backtest feature wrappers
bulk_richardson, theta_e_jump, and shear_at_top feature functions
pull derived fields from the nearest hrrr_native_profile. Ready for
mix backtest once sufficient data is backfilled.
2026-04-10 08:22:43 -05:00
864a91fc5c Phase 2 tasks 2.1-2.4: BL turbulence feature computations
Inversion detection module (Propagation.Inversion):
- find_inversion_top/1 walks the native profile to locate the first
  temperature inversion (surface-based or elevated)
- bulk_richardson/3 computes the Richardson number across the
  inversion layer (Ri < 0.25 = turbulent, > 1 = laminar/good)
- shear_magnitude/3 computes the wind shear vector magnitude
- potential_temperature/2 for θ = T*(P0/P)^0.286

Theta-e module (Weather.ThetaE):
- Bolton (1980) equivalent potential temperature
- dewpoint_from_spfh/2 via Magnus-Tetens inversion
- theta_e_jump/3 for the thermodynamic decoupling metric

mix hrrr_native_derive_fields populates inversion_top_m,
bulk_richardson, theta_e_jump_k, and shear_at_top_ms on existing
hrrr_native_profiles rows.

First real data: 2022-08-20 12Z TX profile shows inversion at
186 m, Ri = 0.16 (turbulent), θ_e jump = 0.33 K — consistent with
marginal propagation conditions at that hour.
2026-04-10 08:21:23 -05:00
FluxCD
b142dfeefd chore: update prop image to git.mcintire.me/graham/prop:main-1775826822-ec20490 [skip ci] 2026-04-10 13:14:54 +00:00
e7f0f03bf0 Fix wgrib2 -lola binary parsing and add native_surface_refractivity
wgrib2 -lola ... bin writes Fortran unformatted records (4-byte
length header + data + 4-byte length trailer per message).
parse_lola_binary was treating the binary as tightly packed,
causing every message after the first to read from the wrong
offset — values came out as garbage across all grid points.

Fix: account for the 8-byte record overhead per message when
computing the data offset for each message's grid values.

This bug affects both the existing propagation grid extraction
(which may have been producing subtly wrong scores) and the new
native-level extraction (which was producing obviously wrong
values). The fix is a one-line stride change.

Also adds Backtest.Features.native_surface_refractivity for the
Phase 1 sanity check, plus a tighter wgrib2 match pattern that
selects only hybrid-level messages from the native file.
2026-04-10 08:13:33 -05:00
FluxCD
4e5efd3752 chore: update prop image to git.mcintire.me/graham/prop:main-1775826152-03005b6 [skip ci] 2026-04-10 13:03:51 +00:00
FluxCD
71398eb49e chore: update prop image to git.mcintire.me/graham/prop:main-1775825733-df2fa2b [skip ci] 2026-04-10 12:56:50 +00:00
f15b2aaa69 Fix native-level GRIB2 decoding and add wgrib2 extraction path
The Elixir GRIB2 decoder didn't map level type 105 (hybrid) or
variable IDs for SPFH and TKE, so native-level messages decoded as
"unknown:105:N" keys that build_native_profile couldn't find. Add
the three missing mappings to Section.identify_level/identify_var.

Also add HrrrNativeClient.extract_native_profiles/2 which uses
wgrib2's -lola on a tight bounding-box subgrid for speed (the pure
Elixir decoder takes ~70s per point on a 395 MB file; wgrib2 handles
40 points in seconds). The worker now routes through this path.
2026-04-10 07:54:08 -05:00
900685aa06 Phase 1 tasks 1.1-1.5: HRRR native hybrid-sigma ingestion
- Spike docs at docs/research/hrrr_native_levels.md confirming files
  are on AWS S3 for 5+ years, 50 hybrid levels, and include TKE and
  SPFH needed for Phase 2 turbulence features. Architectural finding:
  per-point on-demand fetching is impractical (~530 MB/file), so
  the ingestion worker batches per (date, hour) instead.
- hrrr_native_profiles schema: arrays per level plus cached surface
  scalars and placeholder columns for Phase 2/4 derived fields.
  Strictly additive — the existing hrrr_profiles table is untouched.
- HrrrNativeClient: pure URL/message-list helpers, build_native_profile/1
  that turns a parsed wgrib2 map into the schema shape (TDD'd).
- Exposed HrrrClient.download_grib_ranges/2 so the native client
  reuses the existing parallel byte-range download + disk cache.
- HrrrNativeGridWorker: Oban worker keyed on {year, month, day, hour},
  unique at :infinity, pulls distinct (lat, lon) points from contacts
  in the ±30 min window, downloads the native grib2, extracts per
  point, bulk-upserts.
- mix hrrr_native_backfill --limit N enqueues the top-N hours by
  contact count.

Phase 1 gate still pending Task 1.6 (sanity-check backtest after
live data lands).
2026-04-09 16:23:51 -05:00
7630934bcb Accept CamelCase feature names in mix backtest
mix backtest --feature NaiveGradient now resolves to the same function
as --feature naive_gradient. Names are normalized via Macro.underscore,
so any casing works. Also prints the available functions when an
unknown feature is requested so typos don't produce an opaque
UndefinedFunctionError deep in the Enum.map stack.
2026-04-09 16:15:01 -05:00
ab04cb9168 Phase 0: backtest harness
Add Microwaveprop.Backtest: a feature-evaluation framework that runs
a (lat, lon, valid_time) -> float function over the historical QSO
corpus and a matched random-time baseline, reporting distribution
statistics, distance-binned lift, and band-stratified lift.

Adds four baseline feature wrappers around the current scorer inputs
(naive_gradient, td_depression, time_of_day, pressure), a mix backtest
CLI, and the first set of baseline reports under priv/backtest_reports
so downstream phases have a frozen reference point to compare against.
2026-04-09 16:10:54 -05:00
34489ac9f4 Plan: propagation modeling improvements from April 2026 review
Multi-phase plan to incorporate the meteorologist's feedback on
algo.md: hybrid-sigma vertical resolution, boundary-layer turbulence
features, ray-traced duct geometry, frontal geometry, radar-based
stability, climatological anomalies, regional seasonal tuning, and
5-minute METAR. Every phase has a backtest-gated stop criterion so
features that don't show lift against the historical QSO corpus
don't ship.
2026-04-09 16:10:54 -05:00
FluxCD
9e7733f6ab chore: update prop image to git.mcintire.me/graham/prop:main-1775762358-a279784 [skip ci] 2026-04-09 19:21:15 +00:00
73d473a79b Switch Oban to the Pro Smart engine, rate-limit ERA5 CDS
Wire up the Oban Pro Smart engine in base + runtime config and add the
Pro migration (oban_producers, oban_crons, oban_queues tables + the
v1.6 partition/workflow index additions on oban_jobs).

On the Smart engine the era5_batch queue now has a global_limit of 4
in-flight jobs across the cluster and a rate_limit of 10 jobs per hour
so slow Copernicus CDS submit/poll cycles can't exhaust our daily
request budget when a backfill kicks off.
2026-04-09 14:19:04 -05:00
0db5c2ae3e Vendor oban_pro 1.6.13
Adds the commercial Oban Pro package (vendored from the towerops-web2
vendor tree) so this project can use its workers, plugins, and
Smart engine features.
2026-04-09 14:14:49 -05:00
FluxCD
de97dfe074 chore: update prop image to git.mcintire.me/graham/prop:main-1775761957-c2f4849 [skip ci] 2026-04-09 19:14:13 +00:00
485676887a Move Era5MonthBatchWorker to its own queue
The slow CDS submit/poll/download cycle of the month-batch worker was
sharing the era5 queue with the cheap Era5FetchWorker router, and its
2 concurrent slots were permanently pinned by long-running batch jobs
while hundreds of router jobs starved. Give the batch worker its own
era5_batch queue (also 2 concurrent per pod) so the router has
dedicated capacity.
2026-04-09 14:12:31 -05:00
FluxCD
b0cf2cdd80 chore: update prop image to git.mcintire.me/graham/prop:main-1775761660-98f67e3 [skip ci] 2026-04-09 19:09:07 +00:00
caad7d90a7 Mount Oban Web dashboard at /admin/oban
Vendor oban_web 2.12.1 and oban_met 1.1.0 (taken from the
towerops-web2 vendor tree), bump oban to ~> 2.21, and mount the Oban
dashboard at /admin/oban behind the existing require_admin on_mount
hook. Adds an admin-only "Oban" nav link and extends the Dockerfile
to copy vendor/ before deps.get so production builds pick up the
path dependencies.
2026-04-09 14:07:33 -05:00
FluxCD
917e82311a chore: update prop image to git.mcintire.me/graham/prop:main-1775761357-1e3e1a8 [skip ci] 2026-04-09 19:05:05 +00:00
59de7e2d73 Bump Docker image to Elixir 1.19.5 / OTP 28.4.1 2026-04-09 14:02:31 -05:00