Fix map detail panel: update weights, add PWAT, show duct info

- Update all 9 hardcoded weights to match recalibrated values
- Add missing PWAT factor (11.3% weight) to FACTOR_META and FACTOR_ORDER
- Reorder factors by weight (highest first)
- Fix pressure explanation (low pressure is better, not rising)
- Add PWAT factor explanation (beneficial vs harmful by band)
- Show duct info panel when native data detects ducting layers
  (count, thickness, minimum trapped frequency)
This commit is contained in:
Graham McIntire 2026-04-11 13:49:22 -05:00
parent a1d787b26f
commit 58a99de3cd

View file

@ -1,21 +1,24 @@
import topbar from "../vendor/topbar"
import { updateGridOverlay } from "./maidenhead_grid"
// Weights must match BandConfig.weights() in band_config.ex
// Recalibrated 2026-04-11 via gradient descent (loss 0.42 → 0.12)
const FACTOR_META = {
humidity: { label: "Humidity", weight: 0.20, unit: "g/m\u00b3" },
time_of_day: { label: "Time of Day", weight: 0.20, unit: "" },
td_depression: { label: "Td Depression", weight: 0.12, unit: "\u00b0F" },
refractivity: { label: "Refractivity", weight: 0.10, unit: "N/km" },
sky: { label: "Sky Cover", weight: 0.10, unit: "%" },
season: { label: "Season", weight: 0.10, unit: "" },
rain: { label: "Rain", weight: 0.08, unit: "" },
wind: { label: "Wind", weight: 0.06, unit: "kts" },
pressure: { label: "Pressure", weight: 0.04, unit: "mb" }
rain: { label: "Rain", weight: 0.1362, unit: "" },
humidity: { label: "Humidity", weight: 0.1243, unit: "g/m\u00b3" },
pwat: { label: "PWAT", weight: 0.1128, unit: "mm" },
season: { label: "Season", weight: 0.1112, unit: "" },
refractivity: { label: "Refractivity", weight: 0.1049, unit: "N/km" },
pressure: { label: "Pressure", weight: 0.1032, unit: "mb" },
td_depression: { label: "Td Depression", weight: 0.0978, unit: "\u00b0F" },
sky: { label: "Sky Cover", weight: 0.08, unit: "%" },
wind: { label: "Wind", weight: 0.08, unit: "kts" },
time_of_day: { label: "Time of Day", weight: 0.0496, unit: "" }
}
const FACTOR_ORDER = [
"humidity", "time_of_day", "td_depression", "refractivity",
"sky", "season", "rain", "wind", "pressure"
"rain", "humidity", "pwat", "season", "refractivity",
"pressure", "td_depression", "sky", "wind", "time_of_day"
]
function scoreTier(score) {
@ -90,10 +93,20 @@ function factorExplanation(key, score, detail) {
if (score >= 90) return "No rain — no path attenuation"
if (score >= 50) return "Light rain — minor attenuation"
return "Heavy rain — significant path loss"
case "pwat":
if (beneficial) {
if (score >= 80) return "High column moisture — strong refractivity"
if (score >= 50) return "Moderate PWAT — some refractivity enhancement"
return "Low PWAT — limited moisture for ducting"
} else {
if (score >= 80) return "Low column moisture — minimal absorption"
if (score >= 50) return "Moderate PWAT"
return "High PWAT — significant absorption at this frequency"
}
case "pressure":
if (score >= 70) return "Rising pressure — increasing stability favors inversions"
if (score >= 55) return "Steady pressure — stable conditions"
return "Falling pressure — instability weakens inversions"
if (score >= 70) return "Low pressure — frontal boundaries favor ducting"
if (score >= 45) return "Normal pressure — average conditions"
return "High pressure — stable ridge, inversions cap at wrong altitude"
default:
return ""
}
@ -344,11 +357,26 @@ function buildPopupHTML(detail, viewshedLoading) {
<div style="font-size:10px;font-weight:700;opacity:0.5;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.5px;">Analysis</div>
${explanations}
</div>
${detail.factors.duct_info ? buildDuctInfoHTML(detail.factors.duct_info) : ""}
${detail.forecast ? buildForecastSvg(detail.forecast) : ""}
${viewshedStatus}
</div>`
}
function buildDuctInfoHTML(duct) {
const freq = duct.best_duct_freq_ghz != null ? `${duct.best_duct_freq_ghz.toFixed(1)} GHz` : "—"
const thick = duct.max_duct_thickness_m != null ? `${Math.round(duct.max_duct_thickness_m)} m` : "—"
return `
<div style="padding:4px 10px 6px;border-top:1px solid rgba(255,255,255,0.15);">
<div style="font-size:10px;font-weight:700;opacity:0.5;margin-bottom:3px;text-transform:uppercase;letter-spacing:0.5px;">Ducting</div>
<div style="font-size:12px;display:flex;gap:12px;">
<span><strong>${duct.duct_count}</strong> layer${duct.duct_count !== 1 ? "s" : ""}</span>
<span>Thickness: <strong>${thick}</strong></span>
<span>Traps \u2265 <strong>${freq}</strong></span>
</div>
</div>`
}
// --- Hook ---
export const PropagationMap = {