Make environmental feeds band-agnostic; add Environment page

- Remove band_assessment and band_detail from SWPC adapter
- Remove all frequency-specific conclusions (906 MHz, 10m-20m, etc.)
- Store only raw indices: SFI, Kp, R/S/G scales, dM/dz gradients
- Let LLM interpret propagation data based on user's band of interest
- Add full Environment page with feed status, solar indices, and ducting data
- Update Dashboard RF Propagation card to show raw values only
- Update alert messages to be frequency-agnostic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zvx-echo6 2026-05-12 14:59:54 -06:00
commit 1158e30c0b
12 changed files with 863 additions and 273 deletions

20
meshai/env/store.py vendored
View file

@ -134,32 +134,32 @@ class EnvironmentalStore:
else:
lines.append("NWS: No active alerts for mesh area.")
# HF
# Space weather indices (raw - LLM interprets)
s = self._swpc_status
if s:
kp = s.get("kp_current", "?")
sfi = s.get("sfi", "?")
assessment = s.get("band_assessment", "Unknown")
lines.append(f"HF: {assessment} -- SFI {sfi}, Kp {kp}")
r = s.get("r_scale", 0)
g = s.get("g_scale", 0)
lines.append(f"Space Weather: SFI {sfi}, Kp {kp}, R{r}/G{g}")
warnings = s.get("active_warnings", [])
if warnings:
for w in warnings[:2]:
lines.append(f" Warning: {w}")
else:
lines.append("HF: Space weather data not available.")
lines.append("Space Weather: Data not available.")
# UHF ducting
# Tropospheric ducting (raw - LLM interprets)
d = self._ducting_status
if d:
condition = d.get("condition", "unknown")
gradient = d.get("min_gradient", "?")
if condition == "normal":
lines.append("UHF Ducting: Normal propagation, no ducting detected.")
elif condition in ("super_refraction", "ducting", "surface_duct", "elevated_duct"):
gradient = d.get("min_gradient", "?")
lines.append(f"Tropospheric: Normal (dM/dz {gradient} M-units/km)")
else:
thickness = d.get("duct_thickness_m", "?")
lines.append(f"UHF Ducting: {condition.replace('_', ' ').title()} detected")
lines.append(f"Tropospheric: {condition.replace('_', ' ').title()}")
lines.append(f" dM/dz: {gradient} M-units/km, duct ~{thickness}m thick")
lines.append(" Extended range likely on 906 MHz -- expect distant nodes")
return "\n".join(lines)

30
meshai/env/swpc.py vendored
View file

@ -52,7 +52,7 @@ class SWPCAdapter:
changed = True
if changed:
self._update_assessment()
self._update_events()
return changed
@ -197,29 +197,9 @@ class SWPCAdapter:
except (ValueError, TypeError):
pass
def _update_assessment(self):
"""Compute band assessment from SFI and Kp."""
sfi = self._status.get("sfi", 0)
kp = self._status.get("kp_current", 0)
# Band assessment formula
if sfi > 150 and kp <= 1:
assessment = "Excellent"
detail = "Upper HF bands (10m-20m) open, solid DX conditions"
elif sfi >= 100 and kp <= 3:
assessment = "Good"
detail = "Upper HF bands (10m-20m) open, solid DX conditions"
elif sfi >= 80 and kp <= 4:
assessment = "Fair"
detail = "Mid HF bands (20m-40m) usable, upper bands marginal"
else:
assessment = "Poor"
detail = "HF conditions degraded, stick to lower bands (40m-80m)"
self._status["band_assessment"] = assessment
self._status["band_detail"] = detail
# Generate events for R-scale >= 3
def _update_events(self):
"""Generate events for significant space weather conditions."""
# Generate events for R-scale >= 3 (radio blackout)
self._events = []
r_scale = self._status.get("r_scale", 0)
if r_scale >= 3:
@ -228,7 +208,7 @@ class SWPCAdapter:
"event_id": f"swpc_r{r_scale}_{int(time.time())}",
"event_type": f"R{r_scale} Radio Blackout",
"severity": "warning" if r_scale >= 3 else "advisory",
"headline": f"R{r_scale} HF Radio Blackout -- HF comms degraded",
"headline": f"R{r_scale} Radio Blackout in progress",
"expires": time.time() + 3600, # 1hr TTL
"areas": [],
"fetched_at": time.time(),