From 0a3976bc79174a8ac68edf3b10750c03045dd54b Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 1 Aug 2026 16:35:11 -0500 Subject: [PATCH] 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-full-recalibration.md | 65 +++++ priv/algo/band_weights.json | 241 +++++++++--------- 2 files changed, 186 insertions(+), 120 deletions(-) create mode 100644 docs/algo-reports/2026-08-01-full-recalibration.md diff --git a/docs/algo-reports/2026-08-01-full-recalibration.md b/docs/algo-reports/2026-08-01-full-recalibration.md new file mode 100644 index 00000000..885a0f54 --- /dev/null +++ b/docs/algo-reports/2026-08-01-full-recalibration.md @@ -0,0 +1,65 @@ +# 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. diff --git a/priv/algo/band_weights.json b/priv/algo/band_weights.json index d00d85ad..b50904d1 100644 --- a/priv/algo/band_weights.json +++ b/priv/algo/band_weights.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-05-25T19:44:49+00:00", + "generated_at": "2026-08-01T21:30:41+00:00", "generated_by": "scripts/recalibrate.py", "schema_version": 1, "global_weights": { @@ -22,9 +22,10 @@ 432, 1296, 2304, + 3400, 10000 ], - "total_samples": 411619 + "total_samples": 1726757 }, "contacts": { "bands": [ @@ -41,152 +42,152 @@ 75000, 122000 ], - "total_samples": 81571 + "total_samples": 74072 } }, "band_overrides": { "50": { "weights": { - "humidity": 0.1252, - "time_of_day": 0.0273, - "td_depression": 0.1511, - "refractivity": 0.0764, - "sky": 0.1006, - "season": 0.2171, - "wind": 0.1006, - "rain": 0.0171, - "pwat": 0.1197, - "pressure": 0.0649 + "humidity": 0.2039, + "time_of_day": 0.0184, + "td_depression": 0.1632, + "refractivity": 0.0823, + "sky": 0.0679, + "season": 0.1466, + "wind": 0.0679, + "rain": 0.0116, + "pwat": 0.1853, + "pressure": 0.0528 }, - "n_samples": 396474, + "n_samples": 1633976, "source": "pskr" }, "144": { "weights": { - "humidity": 0.1865, - "time_of_day": 0.0197, - "td_depression": 0.1492, - "refractivity": 0.1106, - "sky": 0.0621, - "season": 0.1173, - "wind": 0.0621, - "rain": 0.0106, - "pwat": 0.1695, - "pressure": 0.1125 + "humidity": 0.202, + "time_of_day": 0.0213, + "td_depression": 0.1617, + "refractivity": 0.0527, + "sky": 0.0673, + "season": 0.1271, + "wind": 0.0673, + "rain": 0.0115, + "pwat": 0.1836, + "pressure": 0.1055 }, - "n_samples": 13710, + "n_samples": 87724, "source": "pskr" }, "222": { "weights": { - "humidity": 0.2002, - "time_of_day": 0.0271, - "td_depression": 0.1804, - "refractivity": 0.101, - "sky": 0.0751, - "season": 0.1316, - "wind": 0.0751, - "rain": 0.0128, - "pwat": 0.1535, - "pressure": 0.0432 + "humidity": 0.1947, + "time_of_day": 0.0273, + "td_depression": 0.1815, + "refractivity": 0.1, + "sky": 0.0756, + "season": 0.1325, + "wind": 0.0756, + "rain": 0.0129, + "pwat": 0.1565, + "pressure": 0.0434 }, - "n_samples": 7518, + "n_samples": 7517, "source": "contacts" }, "432": { "weights": { - "humidity": 0.153, - "time_of_day": 0.0256, - "td_depression": 0.17, - "refractivity": 0.1659, - "sky": 0.0708, - "season": 0.1145, - "wind": 0.0708, - "rain": 0.012, - "pwat": 0.1376, - "pressure": 0.0799 + "humidity": 0.1869, + "time_of_day": 0.0225, + "td_depression": 0.1496, + "refractivity": 0.146, + "sky": 0.0623, + "season": 0.1008, + "wind": 0.0623, + "rain": 0.0106, + "pwat": 0.1699, + "pressure": 0.0892 }, - "n_samples": 1013, + "n_samples": 4152, "source": "pskr" }, "902": { "weights": { - "humidity": 0.2204, - "time_of_day": 0.0299, - "td_depression": 0.1764, - "refractivity": 0.0967, - "sky": 0.0734, - "season": 0.1089, - "wind": 0.0734, - "rain": 0.0125, - "pwat": 0.1273, - "pressure": 0.081 + "humidity": 0.2167, + "time_of_day": 0.0302, + "td_depression": 0.1787, + "refractivity": 0.0988, + "sky": 0.0744, + "season": 0.1103, + "wind": 0.0744, + "rain": 0.0127, + "pwat": 0.1256, + "pressure": 0.0782 }, - "n_samples": 1754, + "n_samples": 1753, "source": "contacts" }, "1296": { "weights": { - "humidity": 0.2259, - "time_of_day": 0.034, - "td_depression": 0.1528, - "refractivity": 0.1429, - "sky": 0.0753, - "season": 0.1015, - "wind": 0.0753, - "rain": 0.0128, - "pwat": 0.1327, - "pressure": 0.0468 + "humidity": 0.2289, + "time_of_day": 0.0345, + "td_depression": 0.151, + "refractivity": 0.1361, + "sky": 0.0763, + "season": 0.1029, + "wind": 0.0763, + "rain": 0.013, + "pwat": 0.135, + "pressure": 0.0462 }, "n_samples": 2951, "source": "contacts" }, "2304": { "weights": { - "humidity": 0.1886, - "time_of_day": 0.0284, - "td_depression": 0.1503, - "refractivity": 0.1474, - "sky": 0.0629, - "season": 0.0848, - "wind": 0.0629, - "rain": 0.0338, - "pwat": 0.1714, - "pressure": 0.0695 + "humidity": 0.1916, + "time_of_day": 0.0289, + "td_depression": 0.153, + "refractivity": 0.1344, + "sky": 0.0639, + "season": 0.0861, + "wind": 0.0639, + "rain": 0.0344, + "pwat": 0.1742, + "pressure": 0.0698 }, - "n_samples": 719, + "n_samples": 718, "source": "contacts" }, "3400": { "weights": { - "humidity": 0.2001, - "time_of_day": 0.0301, - "td_depression": 0.1601, - "refractivity": 0.1102, - "sky": 0.0667, - "season": 0.0899, - "wind": 0.0667, - "rain": 0.0507, - "pwat": 0.1818, - "pressure": 0.0437 + "humidity": 0.1983, + "time_of_day": 0.0298, + "td_depression": 0.1574, + "refractivity": 0.1217, + "sky": 0.0661, + "season": 0.0891, + "wind": 0.0661, + "rain": 0.0503, + "pwat": 0.1802, + "pressure": 0.0411 }, - "n_samples": 335, + "n_samples": 334, "source": "contacts" }, "5760": { "weights": { - "humidity": 0.1753, + "humidity": 0.1621, "time_of_day": 0.0299, - "td_depression": 0.1589, - "refractivity": 0.0491, - "sky": 0.0661, - "season": 0.0892, - "wind": 0.0661, - "rain": 0.0796, - "pwat": 0.1708, - "pressure": 0.1151 + "td_depression": 0.1591, + "refractivity": 0.0676, + "sky": 0.0662, + "season": 0.0893, + "wind": 0.0662, + "rain": 0.0797, + "pwat": 0.1696, + "pressure": 0.1101 }, - "n_samples": 321, + "n_samples": 320, "source": "contacts" }, "10000": { @@ -202,39 +203,39 @@ "pwat": 0.1147, "pressure": 0.0967 }, - "n_samples": 54161, + "n_samples": 47260, "source": "contacts" }, "24000": { "weights": { - "humidity": 0.1483, - "time_of_day": 0.0402, - "td_depression": 0.1187, - "refractivity": 0.0517, - "sky": 0.0494, - "season": 0.0733, - "wind": 0.0494, - "rain": 0.2224, - "pwat": 0.1348, - "pressure": 0.112 + "humidity": 0.1514, + "time_of_day": 0.041, + "td_depression": 0.1211, + "refractivity": 0.0402, + "sky": 0.0504, + "season": 0.0748, + "wind": 0.0504, + "rain": 0.227, + "pwat": 0.1376, + "pressure": 0.1061 }, - "n_samples": 3808, + "n_samples": 3330, "source": "contacts" }, "47000": { "weights": { - "humidity": 0.1403, + "humidity": 0.1402, "time_of_day": 0.0528, - "td_depression": 0.1123, - "refractivity": 0.0517, - "sky": 0.0468, - "season": 0.0757, - "wind": 0.0468, - "rain": 0.2387, + "td_depression": 0.1122, + "refractivity": 0.0522, + "sky": 0.0467, + "season": 0.0756, + "wind": 0.0467, + "rain": 0.2385, "pwat": 0.1275, "pressure": 0.1075 }, - "n_samples": 759, + "n_samples": 661, "source": "contacts" } }