Spaces:
Running on Zero
Running on Zero
| """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("&", "&").replace("<", "<").replace(">", ">") | |
| 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>""" | |