-
Notifications
You must be signed in to change notification settings - Fork 0
Data and Testing
How AIrport checks its own work: offline airport data that seeds the simulator, two benchmark families that score the ASR and the pilot agents against a hand-written corpus, and the pytest suite that exercises the deterministic code without touching Redis, PostgreSQL, or an LLM. See index.
Barcelona (LEBL) is the one airport with data checked into the repo, under
data/airport_data/LEBL/: the raw apt.dat scenery file
(LEBL.dat), a parsed LEBL_graph.json, and an interactive LEBL_airport_map.html. Two scripts
under data/scripts/ produce them. airport_data_fetcher.py
(XPlaneAirportDownloader) pulls the scenery package straight from the X-Plane Scenery Gateway
via the xplane-airports package — downloader.download() writes airport_data/{ICAO}/{ICAO}.dat.
airport_graph_builder.py (AirportMapVisualizer) then parses the same apt.dat row codes the
plugin understands — 100 runways, 1201/1202 taxi nodes and edges, 1300 stands — and
renders them as a layered Folium map; a get_graph_data() method exposes the same parsed
elements as a plain dict for notebook use.
data/notebooks/visualization.ipynb drives this
visualizer interactively.
It's tempting to assume this is where the taxiway graph taxi_router actually routes over comes
from — it isn't. Every session re-parses the current airport's apt.dat from scratch at runtime:
the plugin's data_parser.py and graph.py build the graph fresh and write it into Redis under
airport:current:* (see xplane). The LEBL_graph.json checked into data/ gives
the game away once you compare shapes — its nodes carry a usage field that matches the plugin's
TaxiNode, not get_graph_data()'s output — so it's an offline snapshot for analysis, not a
build input consumed by anything at runtime.
Repo-root scripts/ holds the plotting utilities that turn benchmark and log
output into figures, mostly for the thesis write-up: plot_airport_routes.py renders airport
plans with sample routes; plot_lest_graph.py, plot_lest_routes.py and
overlay_lest_routes_on_image.py do the same for a second airport, LEST, whose source data isn't
checked into data/ — the last one homography-warps the routes onto a real X-Plane screenshot;
analyze_pipeline_log.py extracts per-stage latencies from logs/pipeline.log;
plot_agent_benchmark.py charts the agent benchmark below; and the newest,
plot_asr_benchmark.py, renders the ASR benchmark CSVs into WER/RTF/latency figures under
output/asr_bench/figs/.
Two independent benchmark families live in agents_evaluation/, and both
draw on the same corpus: corpus_wer/ holds a del/, gnd/
and twr/ subfolder, each with a corpus_wer_*.txt of 100 hand-written exchanges (300 total) — a
> controller transmission and a < expected ICAO pilot readback per entry, format documented in
corpus_wer/README.md.
Speech asks how well the ASR — which turns controller speech into corrected ATC text — hears.
evaluate_wer.py is the local loop: it transcribes every
corpus .wav through a live ASR endpoint and scores jiwer WER against the reference line, and
optionally replays the DEL hypotheses through the orchestrator as a smoke test.
benchmark_asr.py (new) targets the deployed Cloud Run
ASR instead: stdlib-only, with its own word-level Levenshtein aligner (substitutions, deletions,
insertions — no jiwer), it reports micro- and macro-averaged WER both raw and after
canonicalisation (numbers, callsigns and SIDs folded to coded form, reusing the service's own
core.postprocess when importable), plus server-side latency, RTF, and estimated cost from
--cost-per-h. Three --context modes (none / generic / session) exercise the service's
fuzzy entity matcher, the last feeding it per-session callsign/SID vocabularies pulled straight
from the corpus. Audio is referenced from an external pilot-readback-corpus checkout, never
copied into this repo. Results land under output/asr_bench/ (a different tree from
agents_evaluation/output/ below) and are charted by
plot_asr_benchmark.py.
Agents asks how well the pilots answer. Each DEL/GND/TWR endpoint is one of the stateless
Gemini pilots that draft the ICAO readback — nothing more — so grading them means grading the
reply itself. benchmark_agents.py posts every corpus
line to its matching Cloud Run endpoint and checks latency, HTTP status, and whether the
structured block (clearance_data / taxi_data / reply_data) carries every required field.
validate_agents.py goes deeper, regex-decoding the
expected values straight out of the phonetic ATC text (squawk, SID, runway, QNH, destination…)
and comparing them field-by-field against the agent's response, three dependency workers running
in parallel. Newest of all: judge_responses.py runs
an LLM-as-judge pass — gemini-2.5-pro on Vertex scores each reply against the transmission and
reference readback on five axes (values correct, complete, no hallucination, phraseology 1-5,
overall) — and judge_agreement.py turns a
hand-labelled sample of the judge's verdicts into a Cohen's-kappa reliability figure for it.
flowchart LR
CORPUS["corpus_wer/: del + gnd + twr, 100 each"]
WAV["external wav dataset"]
SPEECHB["speech benchmarks: evaluate_wer.py, benchmark_asr.py"]
ASRN["ASR: local service or deployed Cloud Run"]
SPEECHOUT["WER / latency / RTF / cost CSVs"]
AGENTB["agent benchmarks: benchmark_agents.py, validate_agents.py, judge_responses.py"]
AGENTS["DEL / GND / TWR agents on Cloud Run"]
SCHEMAOUT["schema + semantic reports"]
JUDGEOUT["LLM-judge CSVs + agreement"]
CORPUS --> SPEECHB
WAV --> SPEECHB
SPEECHB --> ASRN
ASRN --> SPEECHOUT
CORPUS --> AGENTB
AGENTB --> AGENTS
AGENTS --> SCHEMAOUT
AGENTS --> JUDGEOUT
Config lives in pyproject.toml's [tool.pytest.ini_options]:
asyncio_mode = "auto", testpaths = ["tests"], pythonpath extended with the orchestrator and
arrival-simulator service roots so their modules import bare, and a coverage gate of
fail_under = 70 scoped to a specific source list (the orchestrator's agent/api/db layers, the
arrival-simulator core modules, shared/services/taxi_router) rather than the whole repo. What
makes the deepest tests possible is tests/fixtures/:
fake_redis.py is a synchronous in-memory stand-in for the
Redis commands the code actually uses (KV, hash, set, list, pub/sub, pipelines),
fake_db.py builds a per-test SQLite :memory: engine from the
real SQLAlchemy Base.metadata, and adk_runner.py's
FakeADKRunner scripts what the Gemini agent runner would yield without ever calling an LLM.
Wired together in one fixture, these three fakes let even
tests/integration/test_departure_pipeline.py
and test_arrival_pipeline.py run the full
clearance-to-Redis-write pipeline hermetically — no simulator, no cloud call, no open network
socket anywhere in the run.
| Area | Path | Covers |
|---|---|---|
| Departure/arrival e2e | tests/integration/ |
full pipeline over the three fakes |
| Orchestrator units | tests/unit/orchestrator/ |
dispatch, advance tools, arrivals endpoint, events subscriber, forward tool, frequency audit, known-aircraft tool, taxi-route tool, repository, runner, session log |
| Arrival simulator units | tests/unit/arrival_simulator/ |
event bridge |
| Taxi routing | tests/taxi_router/ |
graph construction, token resolution, routing e2e, pushback leg, HMI chat |
| Arrivals | tests/arrivals/ |
phases, runway config, arrival planner, geo |
| Debrief | tests/debrief/ |
debrief builder |
Run with uv run pytest (or plain pytest). The honest gap sits outside this table entirely:
xplane_plugin/ — the in-sim motion engine — appears nowhere in the coverage source list and
has no unit tests of its own. The mover's state machine is exercised only indirectly, through the
integration fakes above and manual sessions inside X-Plane.
architecture · agents · asr · xplane · index
Getting Started
Help
Modules
- System-Overview
- Agents
- Shared
- X-Plane
- Service-Arrival-Simulator
- Service-Asr
- Service-Controller-Hmi
- Service-Flight-Plan
- Service-Orchestrator
- Service-Weather
Internals