File size: 2,164 Bytes
6a82282 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | """NWS API — active alerts at a point.
api.weather.gov/alerts/active?point={lat},{lon}, no auth, JSON.
A User-Agent header is required (NWS rate-limits anonymous traffic).
We surface only flood-relevant categories so the doc the reconciler
sees is short and on-topic.
"""
from __future__ import annotations
from typing import Any
import httpx
DOC_ID = "nws_alerts"
CITATION = "NWS public alert API (api.weather.gov/alerts)"
USER_AGENT = "Riprap-NYC/0.1 (civic-flood-tool; +https://huggingface.co/spaces/msradam/riprap-nyc)"
_FLOOD_EVENT_KEYWORDS = (
"flood", "flash flood", "coastal flood", "high surf", "storm surge",
"hurricane", "tropical storm", "tornado warning", # high-impact context
"rip current",
)
def _is_flood_relevant(event_name: str) -> bool:
e = (event_name or "").lower()
return any(k in e for k in _FLOOD_EVENT_KEYWORDS)
def alerts_at(lat: float, lon: float) -> list[dict[str, Any]]:
r = httpx.get(
"https://api.weather.gov/alerts/active",
params={"point": f"{lat:.4f},{lon:.4f}"},
headers={"User-Agent": USER_AGENT, "Accept": "application/geo+json"},
timeout=8.0,
)
r.raise_for_status()
out = []
for f in r.json().get("features", []):
p = f.get("properties", {}) or {}
event = p.get("event") or ""
if not _is_flood_relevant(event):
continue
out.append({
"id": p.get("id"),
"event": event,
"severity": p.get("severity"),
"urgency": p.get("urgency"),
"certainty": p.get("certainty"),
"headline": p.get("headline"),
"sent": p.get("sent"),
"effective": p.get("effective"),
"expires": p.get("expires"),
"sender_name": p.get("senderName"),
"areaDesc": p.get("areaDesc"),
})
return out
def summary_for_point(lat: float, lon: float) -> dict:
try:
active = alerts_at(lat, lon)
except Exception as e:
return {"n_active": 0, "alerts": [], "error": str(e)}
return {
"n_active": len(active),
"alerts": active,
"error": None,
}
|