"""Weather context for the map (server-26#237): NWS alerts + nearest-METAR wind. Public-domain US Government data, cached server-side in app/internal/weather.py; no licence gate (unlike 511NY / outage providers). """ from typing import Optional from fastapi import APIRouter, HTTPException, Query from app.internal import weather router = APIRouter(prefix="/weather", tags=["weather"]) @router.get("/alerts") async def get_alerts( south: Optional[float] = Query(None, ge=-90, le=90), west: Optional[float] = Query(None, ge=-180, le=180), north: Optional[float] = Query(None, ge=-90, le=90), east: Optional[float] = Query(None, ge=-180, le=180), ): """Active NWS alerts with polygons, optionally filtered to those intersecting a bbox. ``zone_only`` holds alerts whose zones could not be resolved to a polygon; it is never bbox-filtered (there is no shape to test) and never dropped. """ box = (south, west, north, east) if any(v is not None for v in box) and any(v is None for v in box): raise HTTPException(400, "give all of south, west, north, east or none") if south is not None and (south > north or west > east): raise HTTPException(400, "bbox must satisfy south<=north and west<=east") feed = await weather.alerts.get() items = feed.alerts if south is not None: items = [a for a in items if weather.intersects(a.get("bbox"), south, west, north, east)] return { "alerts": items, "zone_only": feed.zone_only, "fetched_at": feed.fetched_at, "error": feed.error, "credit": weather.CREDIT, } @router.get("/wind") async def get_wind( lat: float = Query(..., ge=-90, le=90), lon: float = Query(..., ge=-180, le=180), ): """Wind from the nearest METAR station, or ``available: false`` with a reason.""" return await weather.nearest_wind(lat, lon)