Initial commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user