mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-09 21:58:41 +02:00
data: sync plane-alert VIP tracking with real names only
Import oligarchs, royals, and curated celebrities from plane-alert-db while excluding joke tag labels from tracked_names. Sync plane_alert_db.json metadata, add import scripts, and map oligarch/royal/celebrity colors in the legend. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sync plane_alert_db.json from upstream CSV and add explicit celeb/royal tails.
|
||||
|
||||
Does NOT import plane-alert joke tags into tracked_names.json.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SB_PATH = ROOT / "backend" / "data" / "tracked_names.json"
|
||||
PADB_PATH = ROOT / "backend" / "data" / "plane_alert_db.json"
|
||||
PAD = Path.home() / "Downloads" / "plane-alert-db-main" / "plane-alert-db-main"
|
||||
|
||||
MANUAL_TRACKED: list[tuple[str, str, list[str]]] = [
|
||||
("Michael Dell", "Celebrity", ["N28ZD"]),
|
||||
("Lady Moura", "Celebrity", ["VP-CNR"]),
|
||||
("Lewis Hamilton", "Celebrity", ["G-OFOM"]),
|
||||
("Mario Andretti", "Celebrity", ["N500MA"]),
|
||||
("Frank Lowry", "Celebrity", ["N613LF"]),
|
||||
("Mukesh Ambani", "Celebrity", ["VT-AKV"]),
|
||||
("Judge Judy", "Celebrity", ["N555QB"]),
|
||||
("Monaco Royal Family", "Royal", ["3A-MGA"]),
|
||||
("PGA Tour", "Sports", ["N795HG"]),
|
||||
]
|
||||
|
||||
|
||||
def norm_reg(s: str) -> str:
|
||||
return (s or "").strip().upper()
|
||||
|
||||
|
||||
def norm_icao(s: str) -> str:
|
||||
return (s or "").strip().upper()
|
||||
|
||||
|
||||
def row_get(row: dict[str, str], *keys: str) -> str:
|
||||
for key in keys:
|
||||
if row.get(key):
|
||||
return str(row[key]).strip()
|
||||
return ""
|
||||
|
||||
|
||||
def load_git_baseline() -> set[str]:
|
||||
raw = subprocess.check_output(
|
||||
["git", "-C", str(ROOT), "show", "HEAD:backend/data/tracked_names.json"],
|
||||
)
|
||||
data = json.loads(raw)
|
||||
return set(data.get("details", {}).keys())
|
||||
|
||||
|
||||
def wiki_from_link(link: str) -> str:
|
||||
if not link:
|
||||
return ""
|
||||
if "wikipedia.org/wiki/" in link:
|
||||
return link.rsplit("/wiki/", 1)[-1].split("#")[0]
|
||||
return ""
|
||||
|
||||
|
||||
def steal_reg(details: dict, reg: str, protect_names: set[str]) -> None:
|
||||
reg = norm_reg(reg)
|
||||
for name, info in list(details.items()):
|
||||
if name in protect_names:
|
||||
continue
|
||||
regs = info.get("registrations", [])
|
||||
kept = [r for r in regs if norm_reg(r) != reg]
|
||||
if len(kept) != len(regs):
|
||||
if kept:
|
||||
info["registrations"] = kept
|
||||
else:
|
||||
del details[name]
|
||||
|
||||
|
||||
def ensure_entry(
|
||||
details: dict,
|
||||
names_list: list,
|
||||
name: str,
|
||||
category: str,
|
||||
reg: str,
|
||||
*,
|
||||
protect_names: set[str] | None = None,
|
||||
) -> bool:
|
||||
reg = norm_reg(reg)
|
||||
if not reg:
|
||||
return False
|
||||
steal_reg(details, reg, protect_names or set())
|
||||
entry = details.setdefault(name, {"category": category, "registrations": []})
|
||||
entry["category"] = category
|
||||
if reg not in {norm_reg(r) for r in entry["registrations"]}:
|
||||
entry["registrations"].append(reg)
|
||||
if name not in {n["name"] for n in names_list}:
|
||||
names_list.append({"name": name, "category": category})
|
||||
return True
|
||||
|
||||
|
||||
def sync_plane_alert_db() -> tuple[int, int]:
|
||||
if not PADB_PATH.exists():
|
||||
return 0, 0
|
||||
with PADB_PATH.open(encoding="utf-8") as f:
|
||||
db: dict = json.load(f)
|
||||
|
||||
updated = 0
|
||||
added = 0
|
||||
for fname in (
|
||||
"plane-alert-db.csv",
|
||||
"plane-alert-civ.csv",
|
||||
"plane-alert-gov.csv",
|
||||
"plane-alert-mil.csv",
|
||||
"plane-alert-pol.csv",
|
||||
):
|
||||
path = PAD / fname
|
||||
if not path.exists():
|
||||
continue
|
||||
with path.open(encoding="utf-8", errors="replace") as f:
|
||||
for row in csv.DictReader(f):
|
||||
icao = norm_icao(row_get(row, "$ICAO", "ICAO"))
|
||||
if not icao:
|
||||
continue
|
||||
reg = row_get(row, "$Registration", "Registration")
|
||||
operator = row_get(row, "$Operator", "Operator")
|
||||
ac_type = row_get(row, "$Type", "Type")
|
||||
category = row_get(row, "Category")
|
||||
tag1 = row_get(row, "$Tag 1", "Tag 1")
|
||||
tag2 = row_get(row, "#Tag 2", "$#Tag 2")
|
||||
tag3 = row_get(row, "#Tag 3", "$#Tag 3")
|
||||
link = row_get(row, "$#Link", "#Link", "$#Link ")
|
||||
tags = ", ".join(t for t in (tag1, tag2, tag3) if t)
|
||||
|
||||
record = {
|
||||
"registration": reg,
|
||||
"operator": operator,
|
||||
"ac_type": ac_type,
|
||||
"category": category,
|
||||
"tags": tags,
|
||||
"link": link,
|
||||
}
|
||||
wiki = wiki_from_link(link)
|
||||
if wiki:
|
||||
record["wiki"] = wiki
|
||||
|
||||
if icao in db:
|
||||
if db[icao] != record:
|
||||
db[icao] = record
|
||||
updated += 1
|
||||
else:
|
||||
db[icao] = record
|
||||
added += 1
|
||||
|
||||
with PADB_PATH.open("w", encoding="utf-8") as f:
|
||||
json.dump(db, f, indent=4, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
return added, updated
|
||||
|
||||
|
||||
def main() -> None:
|
||||
baseline_keys = load_git_baseline()
|
||||
|
||||
with SB_PATH.open(encoding="utf-8") as f:
|
||||
sb = json.load(f)
|
||||
|
||||
details: dict = sb.setdefault("details", {})
|
||||
names_list: list[dict] = sb.setdefault("names", [])
|
||||
|
||||
manual_added = 0
|
||||
for name, category, regs in MANUAL_TRACKED:
|
||||
for reg in regs:
|
||||
if ensure_entry(details, names_list, name, category, reg, protect_names=baseline_keys):
|
||||
manual_added += 1
|
||||
|
||||
for key in baseline_keys:
|
||||
if key not in details:
|
||||
raise RuntimeError(f"Baseline tracked name lost: {key}")
|
||||
|
||||
names_list.sort(key=lambda x: x["name"].lower())
|
||||
with SB_PATH.open("w", encoding="utf-8") as f:
|
||||
json.dump(sb, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
pad_added, pad_updated = sync_plane_alert_db()
|
||||
|
||||
print(f"Manual celeb regs added: {manual_added}")
|
||||
print(f"plane_alert_db.json: +{pad_added} updated {pad_updated}")
|
||||
print(f"tracked_names details: {len(details)}")
|
||||
print(f"tracked_names registrations: {sum(len(v.get('registrations',[])) for v in details.values())}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user