feat: domestic flights, road traffic, CCTV webcams, AI situation brief

Four new intelligence domains for the dashboard:

1. Domestic flights (OpenSky) — global airborne aircraft count by
   region with commercial/general breakdown. No API key needed.

2. Road traffic (TomTom) — real-time congestion % for 20 major world
   cities + traffic incidents in 5 strategic regions. Needs
   TOMTOM_API_KEY (free 2500 req/day at developer.tomtom.com).

3. CCTV webcams (Windy) — public traffic camera locations worldwide.
   Needs WINDY_API_KEY (free 100 req/day at api.windy.com).

4. AI situation brief (Ollama) — LLM-generated 3-paragraph
   intelligence brief synthesizing all dashboard data. Uses local
   Ollama (llama3.2). Falls back to structured metrics summary.

New files: sources/traffic.py, sources/webcams.py, analysis/situation.py
Modified: sources/aviation.py (+fetch_domestic_flights), dashboard/app.py,
dashboard/index.html (drawer sections + HUD pills for all 4 domains).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marc Shade
2026-02-24 11:46:19 -05:00
co-authored by Claude Opus 4.6
parent fc105872e2
commit fcc702b093
6 changed files with 688 additions and 2 deletions
+121 -2
View File
@@ -1,11 +1,14 @@
"""FAA airport delay data source for world-intel-mcp.
"""Aviation data sources for world-intel-mcp.
Provides real-time US airport delay information from the FAA Airport
Status Web Service (ASWS) API. No API key required.
Status Web Service (ASWS) API, and global domestic air traffic counts
from OpenSky Network. No API key required for either.
"""
import asyncio
import base64
import logging
import os
from datetime import datetime, timezone
from ..fetcher import Fetcher
@@ -131,3 +134,119 @@ async def fetch_airport_delays(fetcher: Fetcher) -> dict:
"source": "faa",
"timestamp": now_iso,
}
# ---------------------------------------------------------------------------
# Domestic / commercial air traffic (OpenSky Network)
# ---------------------------------------------------------------------------
_OPENSKY_STATES_URL = "https://opensky-network.org/api/states/all"
_AIR_REGIONS = {
"north_america": (15, -170, 72, -50),
"europe": (35, -25, 72, 45),
"east_asia": (15, 95, 55, 155),
"middle_east": (12, 25, 42, 65),
"south_asia": (5, 60, 40, 100),
"africa": (-35, -20, 37, 55),
"south_america": (-56, -82, 15, -34),
"oceania": (-50, 110, 0, 180),
}
_COMMERCIAL_PREFIXES = [
"UAL", "AAL", "DAL", "SWA", "JBU", "ASA", "NKS", "FFT", "SKW",
"BAW", "EZY", "RYR", "DLH", "AFR", "KLM", "SAS", "AUA", "TAP",
"QFA", "ANZ", "JST", "VOZ", "CPA", "SIA", "THA", "ANA", "JAL",
"CES", "CSN", "CCA", "HDA", "AIC", "UAE", "ETH", "SAA", "RAM",
"TAM", "GLO", "AZU", "AVA", "LAN", "THY", "TRK", "SHT",
]
def _opensky_auth_headers() -> dict[str, str] | None:
username = os.environ.get("OPENSKY_USERNAME")
password = os.environ.get("OPENSKY_PASSWORD")
if username and password:
cred = base64.b64encode(f"{username}:{password}".encode()).decode()
return {"Authorization": f"Basic {cred}"}
return None
def _classify_region(lat: float | None, lon: float | None) -> str:
if lat is None or lon is None:
return "unknown"
for name, (lat_min, lon_min, lat_max, lon_max) in _AIR_REGIONS.items():
if lat_min <= lat <= lat_max and lon_min <= lon <= lon_max:
return name
return "other"
def _is_commercial(callsign: str | None) -> bool:
if not callsign:
return False
cs = callsign.strip().upper()
return any(cs.startswith(p) for p in _COMMERCIAL_PREFIXES)
async def fetch_domestic_flights(fetcher: Fetcher) -> dict:
"""Fetch global air traffic counts from OpenSky Network.
Queries all airborne aircraft once, then buckets by region and type.
"""
data = await fetcher.get_json(
_OPENSKY_STATES_URL,
source="opensky-domestic",
cache_key="aviation:opensky:all",
cache_ttl=120,
headers=_opensky_auth_headers(),
)
if data is None or not isinstance(data, dict):
return {
"total_aircraft": 0,
"by_region": {},
"busiest_origins": [],
"error": "OpenSky API unavailable",
"source": "opensky-domestic",
"timestamp": _utc_now_iso(),
}
states = data.get("states") or []
by_region: dict[str, dict] = {r: {"count": 0, "commercial": 0, "general": 0} for r in _AIR_REGIONS}
by_region["other"] = {"count": 0, "commercial": 0, "general": 0}
by_region["unknown"] = {"count": 0, "commercial": 0, "general": 0}
country_counts: dict[str, int] = {}
total = 0
for s in states:
if not isinstance(s, list) or len(s) < 15:
continue
if s[8]: # on_ground
continue
total += 1
lat, lon = s[6], s[5]
callsign = s[1]
origin = s[2] or "Unknown"
region = _classify_region(lat, lon)
by_region[region]["count"] += 1
if _is_commercial(callsign):
by_region[region]["commercial"] += 1
else:
by_region[region]["general"] += 1
country_counts[origin] = country_counts.get(origin, 0) + 1
# Remove empty regions
by_region = {k: v for k, v in by_region.items() if v["count"] > 0}
busiest = sorted(country_counts.items(), key=lambda x: -x[1])[:15]
return {
"total_aircraft": total,
"by_region": by_region,
"busiest_origins": [{"country": c, "count": n} for c, n in busiest],
"source": "opensky-domestic",
"timestamp": _utc_now_iso(),
}