Commit graph

31 commits

Author SHA1 Message Date
ac3a2517d9
feat(weather): /weather-ca endpoint shows HRDPS-only Canadian data
New LiveView at /weather-ca duplicates the /weather UI but defaults the
viewport to central Canada (55N, -100W, z=4) and renders the map div
with data-source="hrdps". The JS hook reads that attribute and appends
&source=hrdps to every cell fetch; the WeatherTileController routes
those reads through Weather.weather_grid_hrdps_at/2, which only reads
the <vt>.hrdps scalar dir and skips HRRR merging entirely. Timeline is
sourced from ScalarFile.list_valid_times_hrdps/0 so HRDPS-only hours
show up even when no HRRR file exists for the same valid_time.

Also drops "— Unified" from the algo.md H1.
2026-04-30 12:08:48 -05:00
3a1e79fb67
feat(scorer): retire HPBL multiplier and native-duct 1.15× boost
Both retired in light of the 2026-04-25 revision report
(`docs/algo-reports/2026-04-25-algo-revisions.md`):

  * HPBL boundary-layer multiplier in `Scorer.score_refractivity/{3,4,5}`
    is gone. On the n=47,418 10 GHz HRRR-matched corpus rho_hpbl is
    +0.004; binned distance is flat to within ±5 % across 200–2,000 m.
    The previously reported 2.3× ratio between shallow and deep HPBL
    bins was a small-corpus artefact that disappeared once the matched
    corpus passed ~5,000 contacts.

  * Native-profile 1.15× duct boost is gone. At 10 GHz on n=52,341
    matched contacts, cells where `best_duct_band_ghz` ≥ band ran
    *shorter* than no-duct cells (198 km vs 211 km, n=173 vs 44,658).
    The Bulk Richardson gate that existed only to suppress that boost
    in turbulent conditions is also retired; arity preserved on every
    `score_refractivity` overload so callers compile unchanged.

Same retirements applied in the Rust port (`rust/prop_grid_rs/src/scorer.rs`)
to keep the Elixir/Rust parity tests green.

algo.md updated end-to-end:

  * Finding 4 (HPBL) rewritten to "no usable signal" with the
    n=47,418 bin table.
  * Finding 5 (gradient) tightened: load-bearing only at 24 GHz,
    set band-specific gradient weight to 0 outside [10, 47] GHz on
    the next derive_band_weights run.
  * Finding 8 (time-of-day) augmented with the robust hour-bucketed
    amplitude index (40 / 82 / 101 % at 10 / 24 / 47 GHz) and a
    selection-bias caveat for VHF/UHF (where 129–148 % amplitude
    reflects evening contest scheduling, not propagation).
  * Finding 9 (sounding) refreshed against the n=27,058 corpus.
  * Part 2c "Native-profile duct boost" rewritten as
    "retired 2026-04-25" with the falsifying join.
  * Part 2c "Commercial-link inverse sensor" promoted from deferred
    with the 22-day, 38k-sample SNMP corpus, the +0.61 / −0.61 PWAT
    and pressure correlations against the 37-hour HRRR overlap, and
    the 04–05 / 10–12 local two-peak morning fade window.
  * Part 2b "Pressure" annotated with the U-shape at 10 GHz and the
    next-iteration target.
  * Part 2d "What stayed, what left" + "Open items" updated to
    reflect the four code/doc moves.

119 Elixir scorer tests green, 134 Rust scorer tests green, 2,888
total Elixir tests + 221 properties green via `mix precommit`.
2026-04-25 12:42:56 -05:00
b5decefa8e
docs(algo): align algo.md with Richardson-gated scoring implementation
Three small corrections now that the Richardson gate is actually
wired into Scorer.score_refractivity/5:

- Fix arity reference /4 → /5 in Part 2c.
- Reconcile the backtest section's "bulk_richardson is dead" finding
  with the new gate wiring — they're compatible claims. Richardson
  is dead as a standalone ML discriminator (stable and unstable
  inversions both produce ducts), but useful as a gate on the
  native-duct boost (a duct with high Ri is likely to mix out
  before the signal gets through).
- Note that nil Richardson is treated as "no info" so older native
  profiles without the column still receive the unconditional boost.
2026-04-18 12:42:20 -05:00
0bf49f47ec
docs+ui: present-tense algo.md, NARR replaces ERA5, wider /algo page
algo.md rewrite:
- Strip retrospective language (was/previously/↑ from/Reverted/prior)
  so the doc describes the current algorithm instead of its history.
  Parts 2b/2c/2d lose their dated headings.
- Replace ERA5 references with NARR — ERA5 was never populated in
  prod; NARR is the real historical-backfill path (1979–2014 via
  NCEP / NCEI).
- Fold the sounding ducting-probability table, HPBL binned distance,
  and per-band signal summary into the current-state discussion
  instead of "changes since last refresh" tables.
- Note the bulk_richardson gating on the native-duct boost (code
  follow-up in a separate commit).

Algo page UI: widen .markdown-content from 64rem → 88rem so the
per-band weight + correlation matrices stop overflowing on typical
screens. Wide tables inside markdown content now get their own
horizontal scroll (display: block + overflow-x: auto) rather than
forcing the whole container to scroll.
2026-04-18 12:31:07 -05:00
5fd37a17fd
feat(propagation): per-band weight calibration from full-corpus correlation
Ran recalibrate_algo.py against the full local prop_dev (81,994 contacts,
18.6M HRRR rows) and derived per-band composite weights for the nine
bands with >=200 matched contacts. Moisture (dewpoint/PWAT/surface N)
is consistently beneficial through 5.76 GHz, reverses at 24 GHz; rain
scales sqrt(rain_k) not linearly so 24 GHz gets 0.215 rain weight
instead of the linear-ratio 0.95. 10 GHz stays as the reference band
(defaults); 47+ GHz inherits defaults (n<200).

- BandConfig.weights/1 returns per-band override or default fallback
- @band_configs carries :weights on 222/432/902/1296/2304/3400/5760/24G
- Recalibrator.compute_factors/3 + fit(band_mhz:) band-aware fitting
- Scorer + ContactLive.Show pass band_config to weights/1
- algo.md Part 2d documents the 2026-04-18 analysis + derivation rule
- scripts/derive_band_weights.py turns correlations into weight maps
- report preserved at docs/algo-reports/2026-04-18-recalibration.md
2026-04-18 10:19:26 -05:00
572c2851b3
Draft upper-air factors section in algo.md pending backfill
With the native hybrid-sigma profile now storing levels all the way up
to ~19 km, the scorer can finally see the mid-to-upper troposphere.
The five factors that would plausibly add signal — 500 mb dewpoint
depression, 300 mb wind speed, 850→500 mb dθ/dz, tropopause height,
and 500 mb height anomaly — get rationale, derivation notes, and
placeholder weights. Actual weights have to wait for the native
backfill to catch up enough QSO hours for a representative training
sample, at which point the existing gradient-descent recalibration
pass refits the full factor set.
2026-04-14 17:30:39 -05:00
86364f43aa
Wire NEXRAD, native ducts, and commercial links into scoring
Three signal sources we already collect but weren't using:

* NEXRAD composite reflectivity → rain rate via Marshall-Palmer, taken
  as max of HRRR-derived and NEXRAD-derived rate so fast convective
  cells between HRRR hourly analyses can still trigger the rain penalty.
  Only active on f00 — forecast hours can't see future radar. New
  Scorer.dbz_to_rain_rate_mmhr/1 with 5 dBZ noise floor and 150 mm/hr
  hail-safe ceiling.

* hrrr_native_profiles.best_duct_band_ghz → Scorer.score_refractivity/4
  applies a 1.15× boost when the cell's native-resolution duct supports
  the target band's frequency. HRRR pressure-level gradients
  systematically under-read thin trapping layers the native profile can
  resolve. Sub-band ducts do NOT boost — they're evidence that the
  gradient we have is all there is at the target frequency.

* Commercial LOS link rx_power fading → inverse tropo sensor.
  Commercial.link_degradation_at/3 computes the average 7-day-baseline
  vs current delta across enabled links within 75 km, ignoring links
  where link_state != 1. Scorer.commercial_link_boost/2 adds +2 to +25
  to the composite score for 3+ dB of fading. ~150 km radius around
  DFW is the only zone this helps today, but it's the first *measured*
  signal in the algorithm vs the model-derived proxies.

Also fix a latent test bug exposed by the earlier ERA5 poll-timeout
bump: era5_batch_client_test's "uncached path returns error" tests
hung for up to an hour when run with direnv's real CDS key. New
describe-level setup explicitly unsets the env var so the tests stay
hermetic.

1,359 tests, 0 failures.
2026-04-13 12:08:15 -05:00
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
880988b591 Update algo.md weights and data flow for native duct integration
- Replace stale April 2026 manual weights with recalibrated values
- Document native hybrid-sigma data in data flow section
- Note refractivity factor now uses native 10-50m resolution
- Add hourly grid integration section to Part 12
- Expose duct_info (count, freq, thickness) in scoring factors for UI
2026-04-11 13:44:13 -05:00
7d68d13dcc Recalibrate scoring weights from gradient descent on 5000 QSOs
Loss improved 72% (0.42 → 0.12). Key changes:
- rain: 0.08 → 0.136 (+70%) — strongest discriminator
- season: 0.08 → 0.111 (+39%)
- wind: 0.05 → 0.08 (+60%)
- refractivity: 0.08 → 0.105 (+31%)
- time_of_day: 0.10 → 0.050 (-50%) — was overweighted by contest bias
- pressure: 0.15 → 0.103 (-31%)
- humidity: 0.18 → 0.124 (-31%)

Validated by native profile backtest (11,431 profiles):
theta_e_jump strongest native discriminator, duct_usable_* and
bulk_richardson dropped as dead features.
2026-04-11 13:20:00 -05:00
d49ec5d32e Drop dead backtest features, document consolidated results
Backtest on 11,431 native profiles (2026-04-11):
- Drop duct_usable_10/24/47ghz (always 1.0, no discrimination)
- Drop bulk_richardson (near-identical QSO vs baseline means)
- Document all feature results with signal strength assessment
- theta_e_jump is strongest native discriminator (44% lift)
- best_duct_freq and duct_thickness show clear physical signal
2026-04-11 13:11:39 -05:00
c8127fbb02 Update algo.md to match current codebase
- Fix pressure scoring thresholds (finer granularity at <980-990 mb)
- Fix 10 GHz parameters (o2_db_km, h2o_coeff, seasonal Feb/Oct)
- Fix 24 GHz parameters (o2_db_km 0.02, h2o_coeff 0.002)
- Fix 47 GHz o2_db_km (0.04)
- Add 5 new UHF/microwave bands (902-5760 MHz)
- Add Part 12: HRRR native hybrid-sigma levels (50-level profiles)
- Add Part 13: Duct analysis (M-profile, trapped frequency)
- Add Part 14: Inversion analysis (Ri, theta-e, shear)
- Add Part 15: NEXRAD radar data integration
- Add Part 16: Backtest framework (21 features)
2026-04-11 10:19:08 -05:00
0977d041b8 Document ERA5, RTMA, and path-integrated HRRR in algo.md
Add sections for all three new data sources in Meteorological
Foundations: path-integrated scoring strategy, ERA5 reanalysis
for pre-2014 contacts, RTMA 15-minute surface analysis. Update
calibration dataset summary with ERA5/RAOB/RTMA coverage.
2026-04-07 12:37:51 -05:00
2eefe64c9b Update seasonal scores from sounding data, document commercial link patterns
Soundings (10,935): October has strongest gradients (-350 N/km) and
65.7% ducting, raised seasonal score 82→88. February ducting (20.2%)
exceeds January (14.8%), raised 32→40.

Commercial links (12,068 samples): 68 GHz shows 3.9 dB diurnal swing,
11 GHz shows 1.7 dB with inverted multipath pattern, 24 GHz remarkably
stable at 0.9 dB. Diurnal sensitivity is non-monotonic with frequency.

RAOB gradients avg -265 N/km vs HRRR -107 (2.5x stronger) — confirms
HRRR misses thin surface ducts that soundings resolve.
2026-04-07 11:45:44 -05:00
3cdd132f24 Data-driven algorithm refinements from full dataset analysis
Analyzed 37,925 HRRR-matched contacts from 58,367 total.

- Remove shallow BL bonus (score 82 for HPBL < 300m): data shows
  medium BL (1000-2000m) produces longest contacts (222 km avg),
  not shallow (210 km). Refractivity fallback now uses default score.

- Refine pressure scoring bins: add <980 mb tier (score 88), steeper
  gradient from low to high. Contacts at <970 mb avg 242.7 km vs
  184.1 km at 990-1000 mb.

- Update algo.md calibration stats (58,367 contacts, 41M HRRR
  profiles, 58,361 terrain profiles) and document dataset bias
  (99.5% Aug-Sep).

- Create updates.md with full binned analysis tables for all factors.
2026-04-07 11:15:49 -05:00
4af6c5db73
Fix stale pressure trend scores and pressure levels in algo.md 2026-04-01 18:28:10 -05:00
7fa8ca1ebe
Remove reference to 'original model' in algo.md 2026-04-01 16:52:46 -05:00
dd8ac4a311
Rewrite meteorological foundations for technical audience 2026-04-01 16:21:21 -05:00
bc529af392
Update algo.md Finding 10: corrected mode advantage, no SSB on rainscatter 2026-04-01 11:10:11 -05:00
5c9b43d619
Update analysis with solar time, refine algo.md time-of-day section
Solar time (longitude/15) replaces fixed CDT/CST offset for time-of-day
scoring. Correlation analysis shows dramatic improvement at higher
frequencies: 24 GHz rho jumps from 0.056 (UTC) to 0.188 (solar), and
75 GHz corrects from spurious -0.39 to physically correct +0.24.
2026-04-01 09:02:57 -05:00
83a1511c84
Update algo.md with data-driven findings from 57K QSO analysis 2026-04-01 08:41:33 -05:00
88655e40a6
Add meteorological foundations section to algo.md
Comprehensive description of atmospheric science behind the predictions
for meteorologist review: refractivity-propagation connection, four
ducting mechanisms, HRRR model usage, frequency-dependent effects,
diurnal/seasonal cycles, and known limitations.
2026-03-31 17:00:30 -05:00
c9112b9280
Recalibrate refractivity thresholds for HRRR gradient distribution
Previous thresholds (-500 to -60) were calibrated for radiosonde data.
HRRR profiles have coarser vertical resolution, with gradients clustering
between -40 and -130 N/km (median -70). Nearly all grid points were
falling through to the default score of 42, wasting the refractivity
factor. New thresholds (-200 to -40) spread across HRRR percentiles.
2026-03-31 16:08:15 -05:00
49cbe6789c
Implement ITU-R P.526-16 terrain diffraction model
- Replace piecewise knife-edge loss with P.526-16 Eq. 31 single formula
- Fix diffraction parameter ν to use standard formula instead of ad-hoc approximation
- Implement Deygout 3-edge method for multiple obstacle diffraction
- Add dynamic k-factor from HRRR refractivity gradient (falls back to 4/3)
- Terrain worker now looks up nearest HRRR profile for atmospheric correction
- Update algo.md with P.526-16 methods and k-factor table
- Fix pre-existing map_live_test antenna height default (33 ft, not 8)
2026-03-31 16:04:23 -05:00
d458c23782
Expand algo.md sources: full data provenance, APIs, and ITU-R references
- Expand ITU-R section with recommendation numbers, titles, and usage
- Document all 8 external data sources with API URLs, volumes, fields
- Add derived parameter documentation (sounding params, HRRR profiles)
- Document live scoring pipeline (grid resolution, update cadence, retention)
- Add contest bias and rain model caveats to known issues
2026-03-31 10:52:45 -05:00
a18cf48493
Add regional analysis to algo.md, update site title to NTMS
- Document Finding 11: regional QSO performance by Maidenhead field
- Explain why regional weight adjustments are not justified
- Fix algo.md table formatting (blank lines before tables for Earmark)
- Change page title from Phoenix Framework to North Texas Microwave Society
2026-03-31 10:47:39 -05:00
a6b4ac1965
Increase weather queue to 20 and iemre to 10 for faster backfill 2026-03-31 10:41:49 -05:00
d7fc120c28
Update algo link data location and fix markdown dark mode styling 2026-03-31 09:39:12 -05:00
ea439e56bd
bump algo and fix backfill worker 2026-03-30 15:54:04 -05:00
10e8feb486
Add commercial link monitoring via SNMP polling
Poll UBNT AirFiber radios (AF11X + AF60-LR) every 5 minutes via
net-snmp CLI, storing signal metrics in commercial_samples. Fetches
ASOS weather alongside each cycle for propagation correlation.

Includes 7 seeded link definitions, Oban cron worker, and net-snmp
in the Docker image.
2026-03-30 13:02:59 -05:00
42b2ac920b
add algo docs 2026-03-29 15:55:34 -05:00