Files
argus-nexus/backend/services/space_weather.py
T
2026-03-09 22:07:19 +01:00

89 lines
3.6 KiB
Python

import httpx
import asyncio
# NOAA Space Weather Prediction Center - free, no key
NOAA_KP_URL = "https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json"
NOAA_ALERTS_URL = "https://services.swpc.noaa.gov/products/alerts.json"
NOAA_SOLAR_URL = "https://services.swpc.noaa.gov/json/solar-cycle/observed-solar-cycle-indices.json"
KP_STATUS = {
(0, 2): ("QUIET", "No significant geomagnetic activity."),
(2, 4): ("UNSETTLED", "Minor geomagnetic activity."),
(4, 5): ("ACTIVE", "Active geomagnetic conditions."),
(5, 6): ("STORM-G1", "Minor geomagnetic storm (G1)."),
(6, 7): ("STORM-G2", "Moderate geomagnetic storm (G2). HF radio disruption possible."),
(7, 8): ("STORM-G3", "Strong geomagnetic storm (G3). Possible power grid fluctuations."),
(8, 9): ("STORM-G4", "Severe geomagnetic storm (G4). Widespread power disruption."),
(9, 10): ("STORM-G5", "Extreme geomagnetic storm (G5). Grid collapse risk."),
}
def classify_kp(kp: float) -> tuple[str, str]:
for (lo, hi), (status, desc) in KP_STATUS.items():
if lo <= kp < hi:
return status, desc
return "STORM-G5", "Extreme geomagnetic storm."
async def fetch_space_weather() -> dict:
"""
Fetches real space weather data from NOAA SWPC.
- Planetary K-index (geomagnetic storm indicator)
- Active space weather alerts
"""
result = {
"kp_index": 0.0,
"status": "UNKNOWN",
"description": "Awaiting space weather data...",
"alerts": [],
"timestamp": "",
}
try:
async with httpx.AsyncClient() as client:
# Fetch Kp index - returns list of [time_tag, Kp, ...]
kp_resp = await client.get(NOAA_KP_URL, timeout=10.0)
if kp_resp.status_code == 200:
kp_data = kp_resp.json()
# Skip header row [0], get latest reading from the end
if len(kp_data) > 1:
latest = kp_data[-1]
raw_kp = latest[1] if latest[1] not in ("-1", None, "") else "0"
kp = float(raw_kp)
status, description = classify_kp(kp)
result["kp_index"] = kp
result["status"] = status
result["description"] = description
result["timestamp"] = latest[0]
# Fetch active alerts
alerts_resp = await client.get(NOAA_ALERTS_URL, timeout=10.0)
if alerts_resp.status_code == 200:
alerts_raw = alerts_resp.json()
# Filter for active, non-cancelled alerts
active_alerts = []
for alert in (alerts_raw or [])[:10]:
msg = alert.get("message", "")
if "CANCEL" not in msg and "SUMMARY" not in msg:
# Extract first meaningful line as title
lines = [l.strip() for l in msg.split("\n") if l.strip()]
title = lines[0] if lines else "Space Weather Alert"
active_alerts.append({
"issue_time": alert.get("issue_datetime", ""),
"title": title[:100],
})
result["alerts"] = active_alerts[:5]
print(f"[SPACEWEATHER] Kp={result['kp_index']}{result['status']} | {len(result['alerts'])} active alerts")
except Exception as e:
print(f"[SPACEWEATHER] Fetch error: {e}")
result["status"] = "OFFLINE"
return result
if __name__ == "__main__":
import json
data = asyncio.run(fetch_space_weather())
print(json.dumps(data, indent=2))