prop/docs/algo-reports/2026-08-01-full-recalibration.md
Graham McIntire 0a3976bc79
Some checks failed
Build and Push / Build and Push Docker Image (push) Failing after 4m44s
algo: recalibrate per-band weights from prod corpus (2026-08-01)
First full recalibration since 2026-05-24. Corpus is 1,726,757 PSKR
samples (50/144/432 MHz) plus 74,072 contacts joined to hrrr_profiles
(microwave), yielding overrides for 12 bands.

The 6m fit moved most, and coherently: humidity 0.125 -> 0.204 and pwat
0.120 -> 0.185, paid for by season 0.217 -> 0.147. 6m contributes 1.63M
of the 1.73M PSKR samples, so ten weeks of new spot density pulled the
band off a seasonal prior and onto direct moisture signal. Next largest
is 144 MHz refractivity 0.111 -> 0.053.

75 GHz (n=93) and 122 GHz (n=63) appear in the evidence table but get
no overrides -- they fall under MIN_N_FOR_FIT, which is correct; 75 GHz
reports rho_pr of -0.774 on 93 samples, which is noise, not physics.

Caveat: the refractivity multipliers are numerically unstable. They are
computed as |band_rho_grad| / |ref_rho_grad| against the 10 GHz
reference, whose rho_grad is +0.030 -- an order of magnitude smaller
than the other reference correlations. Two runs minutes apart moved
47 GHz refractivity 0.0688 -> 0.0522 (-24% relative) purely on new rows
landing between queries. The stable factors barely moved. These weights
are live once deployed, so this commit is isolated from the script fixes
to keep it independently revertable.
2026-08-01 16:35:11 -05:00

65 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Full Recalibration Report — 2026-08-01
> Auto-generated by `scripts/recalibrate.py`. The companion JSON file at `priv/algo/band_weights.json` is the machine-readable output that Elixir loads at runtime. This report is the human-readable audit trail.
Connection: `postgres://prop:***@10.0.19.30:6432/prop`
Generated: `2026-08-01T21:30:41+00:00`
## Data sources
- **PSKR** (VHF/UHF, label = `spot_count`): bands [50, 144, 432, 1296, 2304, 3400, 10000], 1,726,757 samples
- **Contacts ↔ HRRR** (microwave, label = `distance_km`): 12 bands, 74,072 matched contacts
## Per-band evidence
| Band | Source | n | label avg | ρ_tc | ρ_dpc | ρ_pr | ρ_pwat | ρ_grad |
|------|--------|---:|----------:|-----:|------:|-----:|-------:|-------:|
| 50 MHz | pskr | 1,633,976 | 2.24 | +0.113 | +0.102 | +0.044 | +0.114 | -0.033 |
| 144 MHz | pskr | 87,724 | 3.07 | +0.057 | +0.101 | +0.140 | +0.117 | +0.004 |
| 222 MHz | contacts | 7,517 | 229.17 | -0.082 | +0.078 | -0.012 | +0.069 | +0.041 |
| 432 MHz | pskr | 4,152 | 1.62 | +0.094 | -0.205 | +0.123 | -0.188 | +0.207 |
| 902 MHz | contacts | 1,753 | 172.23 | -0.070 | +0.094 | +0.077 | +0.047 | -0.041 |
| 1296 MHz | contacts | 2,951 | 160.98 | -0.035 | +0.100 | +0.023 | +0.052 | -0.072 |
| 2304 MHz | contacts | 718 | 154.72 | -0.053 | +0.169 | +0.082 | +0.128 | -0.094 |
| 3400 MHz | contacts | 334 | 154.14 | -0.052 | +0.149 | -0.025 | +0.145 | +0.076 |
| 5760 MHz | contacts | 320 | 127.92 | -0.058 | +0.071 | +0.151 | +0.097 | -0.020 |
| 10000 MHz | contacts | 47,260 | 208.25 | -0.001 | -0.024 | -0.089 | -0.029 | +0.030 |
| 24000 MHz | contacts | 3,330 | 95.36 | -0.215 | -0.333 | -0.204 | -0.366 | +0.005 |
| 47000 MHz | contacts | 661 | 62.80 | -0.121 | -0.187 | -0.246 | -0.220 | -0.027 |
| 75000 MHz | contacts | 93 | 49.90 | -0.688 | -0.696 | -0.774 | -0.636 | +0.495 |
| 122000 MHz | contacts | 63 | 16.26 | +0.038 | -0.130 | -0.042 | -0.128 | +0.356 |
## Derived per-band weights
| Band | n | source | humidity | time_of_day | td_depression | refractivity | sky | season | wind | rain | pwat | pressure |
|------|---:|--------|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|
| 50 MHz | 1,633,976 | pskr | 0.2039 | 0.0184 | 0.1632 | 0.0823 | 0.0679 | 0.1466 | 0.0679 | 0.0116 | 0.1853 | 0.0528 |
| 144 MHz | 87,724 | pskr | 0.2020 | 0.0213 | 0.1617 | 0.0527 | 0.0673 | 0.1271 | 0.0673 | 0.0115 | 0.1836 | 0.1055 |
| 222 MHz | 7,517 | contacts | 0.1947 | 0.0273 | 0.1815 | 0.1000 | 0.0756 | 0.1325 | 0.0756 | 0.0129 | 0.1565 | 0.0434 |
| 432 MHz | 4,152 | pskr | 0.1869 | 0.0225 | 0.1496 | 0.1460 | 0.0623 | 0.1008 | 0.0623 | 0.0106 | 0.1699 | 0.0892 |
| 902 MHz | 1,753 | contacts | 0.2167 | 0.0302 | 0.1787 | 0.0988 | 0.0744 | 0.1103 | 0.0744 | 0.0127 | 0.1256 | 0.0782 |
| 1296 MHz | 2,951 | contacts | 0.2289 | 0.0345 | 0.1510 | 0.1361 | 0.0763 | 0.1029 | 0.0763 | 0.0130 | 0.1350 | 0.0462 |
| 2304 MHz | 718 | contacts | 0.1916 | 0.0289 | 0.1530 | 0.1344 | 0.0639 | 0.0861 | 0.0639 | 0.0344 | 0.1742 | 0.0698 |
| 3400 MHz | 334 | contacts | 0.1983 | 0.0298 | 0.1574 | 0.1217 | 0.0661 | 0.0891 | 0.0661 | 0.0503 | 0.1802 | 0.0411 |
| 5760 MHz | 320 | contacts | 0.1621 | 0.0299 | 0.1591 | 0.0676 | 0.0662 | 0.0893 | 0.0662 | 0.0797 | 0.1696 | 0.1101 |
| 10000 MHz | 47,260 | contacts | 0.1262 | 0.0380 | 0.1010 | 0.0986 | 0.0841 | 0.1134 | 0.0841 | 0.1431 | 0.1147 | 0.0967 |
| 24000 MHz | 3,330 | contacts | 0.1514 | 0.0410 | 0.1211 | 0.0402 | 0.0504 | 0.0748 | 0.0504 | 0.2270 | 0.1376 | 0.1061 |
| 47000 MHz | 661 | contacts | 0.1402 | 0.0528 | 0.1122 | 0.0522 | 0.0467 | 0.0756 | 0.0467 | 0.2385 | 0.1275 | 0.1075 |
## Changes vs prior `band_weights.json`
Largest weight moves (≥0.5 pp), sorted by magnitude:
| Band | Factor | Prior | New | Δ |
|------|--------|------:|----:|--:|
| 47000 MHz | refractivity | 0.0688 | 0.0522 | -0.0166 |
| 3400 MHz | refractivity | 0.1090 | 0.1217 | +0.0127 |
| 2304 MHz | refractivity | 0.1439 | 0.1344 | -0.0095 |
| 5760 MHz | refractivity | 0.0764 | 0.0676 | -0.0088 |
| 24000 MHz | refractivity | 0.0453 | 0.0402 | -0.0051 |
## Applying these weights
These weights are **live scoring inputs, not documentation**. `BandConfig.weights/1` consults `BandWeights.lookup/1` (which reads this JSON) *before* falling back to the in-source `:weights` override or the global `@weights` default. This script still never edits `band_config.ex` — it only rewrites the JSON.
`BandWeights` caches the parsed file in `:persistent_term` on first read, so a running node keeps serving the *previous* weights until it restarts. To put this run into effect, deploy (the file ships under `priv/`) or call `BandWeights.reset/0` on every node.