init commit

This commit is contained in:
2026-04-12 07:19:48 -03:00
commit 9dbf89da02
111 changed files with 14925 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
"""OpenMeteo API client — live weather data, no API key required."""
import httpx
BASE_URL = "https://api.open-meteo.com/v1/forecast"
# United hub coordinates
HUB_COORDS: dict[str, tuple[float, float]] = {
"ORD": (41.9742, -87.9073), # Chicago O'Hare
"EWR": (40.6895, -74.1745), # Newark
"IAH": (29.9902, -95.3368), # Houston Intercontinental
"SFO": (37.6213, -122.3790), # San Francisco
"DEN": (39.8561, -104.6737), # Denver
"LAX": (33.9425, -118.4081), # Los Angeles
"IAD": (38.9531, -77.4565), # Washington Dulles
}
# WMO weather interpretation codes
WMO_CODES: dict[int, str] = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Fog",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
71: "Slight snow",
73: "Moderate snow",
75: "Heavy snow",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail",
}
SIGNIFICANT_CODES = {45, 48, 65, 75, 82, 86, 95, 96, 99}
def _interpolate_waypoints(
origin: tuple[float, float], dest: tuple[float, float], n: int = 2
) -> list[tuple[float, float]]:
"""Generate n intermediate waypoints along a great-circle approximation."""
points = []
for i in range(1, n + 1):
frac = i / (n + 1)
lat = origin[0] + frac * (dest[0] - origin[0])
lon = origin[1] + frac * (dest[1] - origin[1])
points.append((round(lat, 4), round(lon, 4)))
return points
def _interpret_weather(code: int) -> dict:
return {
"code": code,
"condition": WMO_CODES.get(code, f"Unknown ({code})"),
"is_significant": code in SIGNIFICANT_CODES,
}
async def fetch_weather_at_point(
lat: float, lon: float, client: httpx.AsyncClient
) -> dict:
"""Fetch current weather at a single point."""
params = {
"latitude": lat,
"longitude": lon,
"current": "temperature_2m,wind_speed_10m,wind_direction_10m,weather_code,visibility,precipitation",
"timezone": "UTC",
}
resp = await client.get(BASE_URL, params=params)
resp.raise_for_status()
data = resp.json()
current = data["current"]
weather = _interpret_weather(current["weather_code"])
return {
"latitude": lat,
"longitude": lon,
"temperature_c": current["temperature_2m"],
"wind_speed_kmh": current["wind_speed_10m"],
"wind_direction_deg": current["wind_direction_10m"],
"visibility_m": current.get("visibility"),
"precipitation_mm": current.get("precipitation", 0),
"weather": weather,
}
async def get_weather_along_route(origin: str, destination: str) -> dict:
"""Fetch weather at origin, destination, and en-route waypoints."""
origin_coords = HUB_COORDS.get(origin.upper())
dest_coords = HUB_COORDS.get(destination.upper())
if not origin_coords or not dest_coords:
return {
"error": f"Unknown airport code. Known: {', '.join(HUB_COORDS.keys())}",
"origin": origin,
"destination": destination,
}
waypoints = _interpolate_waypoints(origin_coords, dest_coords)
all_points = [
("origin", origin.upper(), origin_coords),
*[(f"waypoint_{i+1}", f"WP{i+1}", wp) for i, wp in enumerate(waypoints)],
("destination", destination.upper(), dest_coords),
]
results = {}
significant_events = []
async with httpx.AsyncClient(timeout=10.0) as client:
for label, name, (lat, lon) in all_points:
try:
weather = await fetch_weather_at_point(lat, lon, client)
weather["label"] = label
weather["name"] = name
results[label] = weather
if weather["weather"]["is_significant"]:
significant_events.append(
{
"location": name,
"condition": weather["weather"]["condition"],
"code": weather["weather"]["code"],
}
)
except Exception as e:
results[label] = {"label": label, "name": name, "error": str(e)}
return {
"origin": origin.upper(),
"destination": destination.upper(),
"waypoints": results,
"significant_events": significant_events,
"source": "openmeteo_live",
}
async def get_weather_forecast_hubs() -> dict:
"""Fetch 4-hour forecast for all United hubs."""
hubs = ["ORD", "EWR", "IAH", "SFO", "DEN"]
results = {}
async with httpx.AsyncClient(timeout=10.0) as client:
for hub in hubs:
lat, lon = HUB_COORDS[hub]
params = {
"latitude": lat,
"longitude": lon,
"hourly": "temperature_2m,wind_speed_10m,weather_code,visibility,precipitation_probability",
"forecast_hours": 4,
"timezone": "UTC",
}
try:
resp = await client.get(BASE_URL, params=params)
resp.raise_for_status()
data = resp.json()
hourly = data["hourly"]
hours = []
risk_flag = False
for i in range(len(hourly["time"])):
code = hourly["weather_code"][i]
weather = _interpret_weather(code)
if weather["is_significant"]:
risk_flag = True
hours.append(
{
"time": hourly["time"][i],
"temperature_c": hourly["temperature_2m"][i],
"wind_speed_kmh": hourly["wind_speed_10m"][i],
"weather": weather,
"visibility_m": hourly.get("visibility", [None])[i]
if "visibility" in hourly
else None,
"precipitation_probability": hourly[
"precipitation_probability"
][i],
}
)
results[hub] = {
"hub": hub,
"forecast": hours,
"risk_flag": risk_flag,
}
except Exception as e:
results[hub] = {"hub": hub, "error": str(e)}
return {"hubs": results, "source": "openmeteo_live"}