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`.
16 KiB
Proposed algo.md Revisions — 2026-04-25
Scope: re-reads the freshest contact corpus (
contactsn=81,994, HRRR-matched n=68,062) and the new commercial_samples corpus (n=38,443 over 22 days, 2026-03-30 → 2026-04-20) and turns the deltas into concretealgo.mdedits. Companion to the auto-generated2026-04-25-recalibration.md.Local dev DB (
prop_devmirrored from prod 10.0.15.24). Queries below can be re-run verbatim against either.
Summary of recommended algo.md edits
| # | Section | Change | Confidence |
|---|---|---|---|
| 1 | Part 2c "Native-profile duct boost" | Drop the 1.15× boost. Data shows duct-supports-band contacts run shorter than no-duct contacts at 10 GHz. | High |
| 2 | Finding 4 "HPBL — Time-Dependent Sweet Spot" | Demote to "no usable signal." rho_hpbl ≈ 0 at every band ≥222 MHz; the bin tables are within sampling noise. | High |
| 3 | Part 2b "Pressure" + Part 2d weights row 10 GHz | Note the U-shape. Distance is longest at both tails (<990 mb and ≥1020 mb) at 10 GHz. Linear scoring underweights the high-pressure ridge. | Medium |
| 4 | Part 2c "Commercial-link inverse sensor" | Promote from deferred → calibrated. 22-day SNMP corpus exists; 37 HRRR-overlap hours show rho(fade, PWAT) = +0.61, rho(fade, P) = −0.61. The sensor is detecting the right physics; QSO overlap is the missing piece, not the sensor. | High |
| 5 | Finding 8 "Time-of-Day Scales with Frequency" | Revise magnitudes. Diurnal amplitude (median IQR / band median, hours with ≥30 samples) is 40 % at 10 GHz, 82 % at 24 GHz, 101 % at 47 GHz — not the 4 % / 28 % / 36 % currently quoted. | Medium |
| 6 | Finding 8 / Part 2b | Add contest-schedule caveat. 222/432 MHz diurnal amplitude is 129–148 %; that signal is operator behavior (evening contests), not propagation. Document explicitly so future weight-fitters don't propagate it. | High |
| 7 | Finding 5 "Use Continuous Gradient" | Affirm and tighten. Gradient correlation only clears noise at 24 GHz (rho ≈ 0.017 — still tiny) and 75 GHz (0.474, n=83). At every other band it's <0.1. Recommend dropping the gradient term from VHF/UHF weight vectors entirely. | Medium |
| 8 | Part 2c "Sounding ducting probability" | Refresh table. Numbers in the current algo.md Part 2c table differ from the latest sounding pull (e.g. April 25.5 % vs 31.3 %). Use the values from 2026-04-25-recalibration.md. |
Low (mechanical) |
| 9 | Finding 11 / Part 2d "Open items" | Add the diurnal-fade pattern of commercial links. 11 GHz fade ≥3 dB peaks 04 :00–12 :00 local (1.4–2.2 % of samples) vs 0.2–0.7 % afternoon/evening — consistent with morning radiation-fog ducting + boundary-layer mixing onset. | Medium |
Evidence per recommendation
1. Native-duct boost is wrong-signed at 10 GHz
Scorer.score_refractivity/4 multiplies the refractivity component by 1.15 when
hrrr_native_profiles.best_duct_band_ghz ≥ contact band. The validation
matrix:
WITH joined AS (
SELECT DISTINCT ON (c.id)
c.id, c.band::int AS band, c.distance_km::float AS dist,
n.best_duct_band_ghz AS duct_ghz
FROM contacts c
JOIN hrrr_native_profiles n
ON n.lat BETWEEN (c.pos1->>'lat')::float - 0.07 AND (c.pos1->>'lat')::float + 0.07
AND n.lon BETWEEN (c.pos1->>'lon')::float - 0.07 AND (c.pos1->>'lon')::float + 0.07
AND n.valid_time BETWEEN c.qso_timestamp - INTERVAL '1 hour' AND c.qso_timestamp + INTERVAL '1 hour'
WHERE c.pos1 IS NOT NULL AND c.distance_km < 3000 AND c.flagged_invalid = false
ORDER BY c.id, ABS(EXTRACT(EPOCH FROM n.valid_time - c.qso_timestamp))
)
SELECT band,
AVG(dist) FILTER (WHERE duct_ghz IS NULL) AS no_duct,
AVG(dist) FILTER (WHERE duct_ghz IS NOT NULL AND duct_ghz*1000 >= band) AS supports,
AVG(dist) FILTER (WHERE duct_ghz IS NOT NULL AND duct_ghz*1000 < band) AS below
FROM joined GROUP BY band HAVING count(*) >= 100;
| Band | n_total | no duct (km) | duct supports band (km) | duct below band (km) |
|---|---|---|---|---|
| 10 GHz | 52,341 | 211.3 (n=44 658) | 197.9 (n=173) | 199.8 (n=7,510) |
| 24 GHz | 3,700 | 94.8 (n=3,182) | 131.5 (n=6) | 98.3 (n=512) |
| 47 GHz | 689 | 64.3 (n=552) | – (n=0) | 58.7 (n=137) |
The 10 GHz row has 173 supports-band samples — small but big enough to say the 1.15× boost is not just absent, it's opposite the truth in our matched corpus. At 24 GHz the supports-band cell is too small (n=6) to draw any conclusion. At 47 GHz it's empty.
Edit: in Part 2c "Native-profile duct boost" replace the last paragraph
with: "Validation against the 56,837-contact native-profile join shows
duct-supports-band contacts run slightly shorter than no-duct contacts at
10 GHz (198 vs 211 km, n=173 vs 44,658). The 1.15× multiplier in
Scorer.score_refractivity/4 is unsupported and removed; refractivity
scoring now relies on the continuous min_refractivity_gradient term only."
2. HPBL has no signal at any matched band
From 2026-04-25-recalibration.md correlations (rho_hpbl column):
| Band | n | rho_hpbl |
|---|---|---|
| 222 MHz | 5,578 | −0.031 |
| 432 MHz | 6,782 | −0.063 |
| 902 MHz | 1,168 | −0.045 |
| 1.296 GHz | 2,111 | −0.064 |
| 2.304 GHz | 405 | −0.085 |
| 10 GHz | 47,418 | +0.004 |
| 24 GHz | 3,429 | −0.031 |
| 47 GHz | 679 | −0.092 |
Magnitude never exceeds 0.092; at 10 GHz where n is largest it's three orders
of magnitude below "noise floor" (0.05 in the existing s_band,factor
formula). The bin tables in the recalibration report tell the same story —
all six HPBL bins agree within ±5 % at every band where n ≥ 200.
Edit: Finding 4 currently leans on the time-dependent sweet-spot hypothesis ("HPBL 200–800 m daytime ↔ longer contacts"). Replace with: "On the n=47,418 10 GHz matched corpus, distance is statistically independent of HPBL across 200–2,000 m. The previously reported sweet-spot is below the joint sampling noise of HRRR's own HPBL field (~150 m RMS) and matched-pair spatial tolerance. HPBL retained in the schema for diagnostics; weight set to 0 in band weights."
3. 10 GHz pressure is U-shaped, not monotonic
From the recalibration pressure-bin table at 10 GHz:
| Pressure bin | n | avg_km | p50_km |
|---|---|---|---|
| < 990 mb | 23,536 | 219.4 | 206.0 |
| 990–1000 | 12,221 | 182.7 | 159.6 |
| 1000–1010 | 6,246 | 204.5 | 170.6 |
| 1010–1020 | 3,996 | 214.7 | 173.6 |
| ≥ 1020 | 1,419 | 235.7 | 208.9 |
The minimum is 990–1000 mb, both tails are longer. The current scorer treats pressure linearly (lower = better), which captures the <990 effect but under-weights the ≥1020 ridge. Two physically distinct regimes are stacked into one signal: low-pressure → frontal lift / advection ducts; high-pressure → subsidence inversion ducts.
Edit: Part 2b "Finding 3" — note the bimodality. Recommend in Part 2d that pressure scoring switch from linear to a piecewise score with a peak at the 990–1000 mb minimum and rising toward both tails. (Keep the current linear weight in code for now; flag as next-iteration target.)
4. Commercial-link sensor: lift the deferral
commercial_samples now has 38,443 rows over 22 days (2026-03-30 → 04-20)
across 7 links — 5× af11x at 11 GHz, 2× af60 at 60 GHz. Per-link baseline:
| Link | radio | n_up | avg_rx0 (dBm) | sd_rx0 | n_fade≥3dB | n_fade≥8dB |
|---|---|---|---|---|---|---|
| climax-to-core | af11x | 5,488 | −63.86 | 1.06 | 7 | 5 |
| core-new-hope | af11x | 5,491 | −50.83 | 1.65 | 62 | 2 |
| core-to-climax | af11x | 5,491 | −61.10 | 0.91 | 7 | 5 |
| new-hope-to-core | af11x | 5,493 | −51.50 | 1.36 | 33 | 3 |
| verona-to-climax | af11x | 5,501 | −41.97 | 1.04 | 80 | 12 |
| 380_982_60LR | af60 | 150 | −52.49 | 2.93 | 8 | 4 |
| 982_380_60LR | af60 | 206 | −52.14 | 2.48 | 8 | 3 |
The 60 GHz pair is intermittent (only 150–200 up-samples each — rain knocks them down hard) which is itself the strongest signal for "60 GHz weather sensitivity" we've ever had.
QSO overlap: zero contacts in the DFW box during the 22-day window. But
the sensor is correlating with HRRR cleanly. Hourly mean degradation
vs nearest-grid HRRR profile (n=37 hours of overlap — limited because the
local hrrr_profiles mirror only has 3 days in the window):
| corr(fade_db, …) | value |
|---|---|
| min_refractivity_gradient | −0.161 |
| surface_dewpoint_c | +0.487 |
| pwat_mm | +0.609 |
| t-td depression | +0.147 |
| surface_pressure_mb | −0.605 |
| HPBL | +0.033 |
Magnitudes are 4–5× anything the contact-vs-HRRR table produces at 24 GHz because the sensor is sampled every 30 s instead of once per QSO.
Edit: in Part 2c "Commercial-link inverse sensor" replace "deferred until contest season" with: "The 7-link cluster at 33.2N/−96.5W now has 22 days of 30-second polling. Per-link rx_power_0 SD is 1.0–1.7 dB on af11x and 2.5–2.9 dB on af60. Hourly mean rx-degradation correlates with HRRR PWAT at +0.61 and surface pressure at −0.61 — direction matches the QSO-derived 24 GHz weights. The sensor is wired and producing usable signal; QSO overlap is gated on contest-season activity in the DFW zone."
5. Diurnal amplitude scales with frequency, but the magnitudes in algo.md are off
Restricting to local hours with ≥30 contacts per band (so 0300-local doesn't get a single 79 km outlier) produces a defensible amplitude index:
WITH local_q AS (
SELECT band::int AS band, distance_km::float AS dist,
EXTRACT(HOUR FROM qso_timestamp AT TIME ZONE 'America/Chicago')::int AS local_hr
FROM contacts
WHERE pos1 IS NOT NULL AND distance_km < 3000 AND flagged_invalid = false
AND band >= 222 AND qso_timestamp >= '2014-10-02'
),
hourly AS (
SELECT band, local_hr, count(*) AS n,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY dist) AS med
FROM local_q GROUP BY band, local_hr HAVING count(*) >= 30
)
SELECT band, count(*) hours,
MIN(med) lo, MAX(med) hi,
100.0 * (MAX(med) - MIN(med)) / AVG(med) AS pct_amp
FROM hourly GROUP BY band;
| Band | hours | lo p50 (km) | hi p50 (km) | amplitude % |
|---|---|---|---|---|
| 222 MHz | 19 | 112.0 | 379.5 | 129.2 |
| 432 MHz | 19 | 95.0 | 355.0 | 148.4 |
| 902 MHz | 15 | 94.5 | 178.0 | 57.7 |
| 1.296 GHz | 17 | 80.0 | 176.0 | 71.2 |
| 2.304 GHz | 13 | 108.0 | 171.5 | 48.2 |
| 10 GHz | 23 | 141.2 | 217.1 | 40.4 |
| 24 GHz | 17 | 57.3 | 129.8 | 82.0 |
| 47 GHz | 14 | 31.8 | 91.3 | 101.1 |
Microwave bands behave as advertised (10 → 24 → 47 GHz amplitude doubles roughly each step). VHF/UHF bands do not — and this is the big gotcha:
6. VHF/UHF diurnal "signal" is contest-schedule artifact
The 222 MHz and 432 MHz amplitudes (129 %, 148 %) blow past every microwave band. Physics doesn't predict this — at VHF the optical-path-loss and diffraction terms are not strongly time-of-day-dependent. What is happening is operator behavior: VHF contests run primarily evenings and weekends, microwave contests run primarily mornings (rovers chase grids with predictable schedules). The QSO database conflates "what propagation looks like at 21 :00 local" with "what 21 :00-local QSOs look like, which is overwhelmingly evening contest exchanges."
Edit: Add a new caveat box to Finding 8:
Selection bias warning. Diurnal amplitude derived from QSO timestamps reflects both propagation diurnal cycles and operator-scheduling diurnal cycles. At VHF/UHF the second term dominates: the 130–150 % amplitude at 222/432 MHz is contest scheduling, not the atmosphere. Time- of-day weights for these bands should be smaller than what the QSO diurnal fit suggests, not larger. Until a clean atmospheric-only signal exists (e.g. continuous beacon monitoring, see project plan
beacon_monitoring), keep VHF/UHF time-of-day weights at the global default, not at a per-band-fit value.
7. Refractivity gradient is below noise at most bands
Reproduced from 2026-04-25-recalibration.md:
| Band | rho_grad |
|---|---|
| 222 MHz | +0.031 |
| 432 MHz | +0.003 |
| 902 MHz | −0.062 |
| 1.296 GHz | −0.079 |
| 2.304 GHz | −0.178 |
| 3.4 GHz | 0.000 |
| 5.76 GHz | −0.110 |
| 10 GHz | +0.027 |
| 24 GHz | +0.017 |
| 47 GHz | −0.008 |
| 75 GHz | +0.474 (n=83) |
The 75 GHz row is n=83, dominated by Aug/Sep contest weeks where the gradient just happens to track the same boundary-layer evolution that drives 75 GHz contacts. It is not strong evidence for a per-band gradient term.
Edit: Finding 5 already says "use continuous gradient." Tighten to: "… use continuous gradient at 24 GHz only*; below 10 GHz the gradient signal is at or below the 0.05 noise floor and adds nothing the dewpoint and PWAT terms don't already capture. Set band-specific gradient weight to 0 outside [10 GHz, 47 GHz]."*
8. Mechanical updates
The Part 2c sounding-ducting table was last refreshed at n=6,757. The 2026-04-25 recalibration ran on n=27,058. April ducting % is 25.5 %, not 31.3 % as currently shown. April PWAT is 12.7 mm, not 13.5 mm. Replace verbatim from the recalibration report.
9. Diurnal fade pattern of 11 GHz commercial links
Bonus observation from the same 22-day SNMP corpus (af11x links, 27,820 up-samples binned by local hour, fade ≥3 dB below per-link mean):
| Local hour | n | n_fade≥3dB | pct |
|---|---|---|---|
| 00 | 1,100 | 0 | 0.00 |
| 01 | 1,135 | 2 | 0.18 |
| 02 | 1,140 | 0 | 0.00 |
| 03 | 1,139 | 7 | 0.61 |
| 04 | 1,135 | 16 | 1.41 |
| 05 | 1,135 | 18 | 1.59 |
| 06 | 1,135 | 7 | 0.62 |
| 07 | 1,140 | 4 | 0.35 |
| 08 | 1,140 | 10 | 0.88 |
| 09 | 1,130 | 13 | 1.15 |
| 10 | 1,134 | 25 | 2.20 |
| 11 | 1,140 | 16 | 1.40 |
| 12 | 1,117 | 19 | 1.70 |
| 13–22 | flat | 3–8 | 0.18–0.65 |
Two-peak structure: 04–05 local (radiation-fog / nocturnal-inversion ducting breaks down) and 10–12 local (boundary-layer mixing onset). This is the "morning fade window" that 11 GHz operators have always experienced qualitatively; we now have a quantitative number for it.
Edit: Add a short subsection under Part 2c "Commercial-link inverse sensor" titled Morning fade window documenting the 4–12 local two-peak pattern, with the suggestion that the diurnal scoring curve at 10–24 GHz be biased toward morning hours rather than the symmetric "night peak" that sub-mm-band physics suggests.
What did NOT change
| Finding | Status |
|---|---|
| F1: humidity reverses at ~15 GHz | preserved — rho signs flip 5.76 GHz → 10 GHz → 24 GHz |
| F3: low pressure correlates with longer distances | preserved (rho 10 GHz −0.075, 24 GHz −0.20, 47 GHz −0.20) |
| F9: ducting peaks Jun–Jul | preserved (sounding ducting % Jun 69.5, Jul 67.5, Aug 55.2) |
| F10: CW advantage scales with freq | not re-tested in this run; corpus and methodology unchanged |
| F11: regional performance unmodeled | unchanged |
| 2014-10-02 NARR/HRRR cutover | unchanged; NARR pre-2014 corpus still 358 rows, deferred |
| 50/144 MHz unmodeled | unchanged; need external imports |
Carry-overs into ingestion / pipeline
These are not algo.md edits but worth flagging from the same query session:
hrrr_climatologyis empty in dev mirror —mix hrrr_climatologyhas not been run (or its output is not synced). Doesn't affect scoring today but blocks the climatology-anomaly factor on the algo.md roadmap.rtma_observationsis empty —RtmaFetchWorkernot enabled in dev. Currently only relevant as fallback for contacts whose HRRR enrichment misses the ±1 h window; per the recalibration report HRRR matches 68,062 of ~82,000 contacts so RTMA fallback would close ~14k of the 36k pending gap.metar_5min_observationsis empty — schema exists but no ingestor. Either wire an IEM 5-minute ASOS fetcher or drop the schema.- HRRR backlog: 36,276 contacts pending enrichment, 45,718 complete
(55.8 %). Year breakdown via the new section in
scripts/recalibrate_algo.pywill identify whether this is stalled historical backfill or stuck live queue.
Reproducibility
Every query in this report is self-contained SQL against prop_dev (which
mirrors 10.0.15.24:5432/prop). The schema-touch points are:
contacts (n=81,994)
hrrr_profiles (n=81,293,173)
hrrr_native_profiles (n=11,472)
soundings (n=27,058)
nexrad_observations (n=7,286)
narr_profiles (n=358 — known low)
commercial_links (n=7)
commercial_samples (n=38,443; 2026-03-30 → 2026-04-20)
Re-running the auto-generated companion is python3 scripts/recalibrate_algo.py.