Flight-Transit-Agent / sidebar.py
Quazim0t0's picture
Upload 34 files
41e0c9e verified
Raw
History Blame Contribute Delete
4.44 kB
"""Builds the neon 'hacker' sidebar: MOST TRACKED + AIRPORT DISRUPTIONS panels.
Mirrors the FlightRadar24 sidebar layout. Since the FR24 API exposes neither
viewer/tracker counts nor an airport-disruption feed, both panels are derived
from the live in-view flights (and real weather from Open-Meteo). Labels make
the derivation explicit so nothing is presented as official FR24 data.
"""
from __future__ import annotations
from collections import Counter
import fr24
import weather
_AIRPORT_NAMES = {
"EGLL": "London", "KJFK": "New York", "OMDB": "Dubai", "EGBB": "Birmingham",
"ZGSZ": "Shenzhen", "KDFW": "Dallas", "LTBJ": "Izmir", "EHAM": "Amsterdam",
"LFPG": "Paris", "EDDF": "Frankfurt", "RJTT": "Tokyo", "VHHH": "Hong Kong",
}
def _fmt_int(n) -> str:
try:
return f"{int(n):,}"
except Exception:
return "—"
def _esc(s) -> str:
return (str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
if s is not None else "")
def _most_tracked(flights, limit=8):
ranked = sorted(
[f for f in flights if f.get("gspeed")],
key=lambda f: f.get("gspeed") or 0, reverse=True,
)[:limit]
rows = []
for i, f in enumerate(ranked, 1):
cs = _esc(f.get("callsign") or f.get("flight") or "N/A")
typ = _esc(f.get("type") or "—")
orig = _esc(f.get("orig") or "N/A")
dest = _esc(f.get("dest") or "N/A")
rows.append(f"""
<div class="fd-item">
<div class="fd-row">
<span class="fd-rank">{i}.</span>
<span class="fd-cs">{cs}</span>
<span class="fd-badge">{typ}</span>
<span class="fd-metric">{_fmt_int(f.get('gspeed'))} <small>kt</small></span>
</div>
<div class="fd-route">{orig} <span class="fd-arrow">–</span> {dest}</div>
</div>""")
if not rows:
rows.append('<div class="fd-empty">no contacts</div>')
return "".join(rows)
def _disruptions(flights, limit=6):
# Busiest destinations = inbound congestion proxy.
counts = Counter(
f.get("dest") for f in flights if f.get("dest"))
top = counts.most_common(limit)
if not top:
return '<div class="fd-empty">no inbound traffic</div>'
max_n = max(n for _, n in top) or 1
rows = []
for code, n in top:
ll = fr24.airport_coords(code)
wx = weather.current(*ll) if ll else None
name = _AIRPORT_NAMES.get(code, (code or "—"))
if wx:
temp = (f"{round(wx['temp_c'])}°C" if wx.get("temp_c") is not None else "—°C")
wind = (f"{weather.wind_arrow(wx.get('wind_dir'))} "
f"{round(wx.get('wind_dir') or 0)}° "
f"{round(wx.get('wind_kts') or 0)} kts")
sky = weather.code_glyph(wx.get("code"))
else:
temp, wind, sky = "—", "wx n/a", "·"
# Derived 0.0–9.9 congestion index (NOT FR24's official rating).
idx = round(min(9.9, 9.9 * (n / max_n)), 1)
dot = "#FF2BD6" if idx >= 6 else ("#FFD23F" if idx >= 3 else "#39FF14")
rows.append(f"""
<div class="fd-item">
<div class="fd-row">
<span class="fd-cs">{_esc(name)}</span>
<span class="fd-badge">{_esc(code)}</span>
</div>
<div class="fd-wx">
<span>{sky} {temp}</span>
<span class="fd-wind">{wind}</span>
</div>
<div class="fd-scores">
<span title="inbound contacts in view">🛬 {n}</span>
<span class="fd-dot" style="color:{dot}">● {idx}</span>
</div>
</div>""")
return "".join(rows)
def build_sidebar_html(flights) -> str:
tracked = _most_tracked(flights)
disrupt = _disruptions(flights)
return f"""
<div id="fd-sidebar">
<details open class="fd-panel">
<summary>MOST TRACKED <span class="fd-live">LIVE</span><span class="fd-chev">▾</span></summary>
<div class="fd-note">ranked by ground speed · API has no viewer counts</div>
<div class="fd-list">{tracked}</div>
</details>
<details open class="fd-panel">
<summary>AIRPORT DISRUPTIONS <span class="fd-live">LIVE</span><span class="fd-chev">▾</span></summary>
<div class="fd-note">busiest inbound · weather: open-meteo · index derived</div>
<div class="fd-list">{disrupt}</div>
</details>
</div>"""