The transition parser dropped airways, so mandate splices filed all-DCT
(MOVUM DCT HAREM) — wrong routing, and the arrival splice self-looped
(ROUTE49) when the enroute already crossed the arrival area.
- rad: expand_via keeps airway tokens (is_airway_tok); a transition is now
a point/airway token sequence (DCT where no airway), e.g.
[MOVUM, T109, HAREM, T104, WLD].
- discover: transition_legs() builds legs with the interspersed airways.
route_via_arrival_transition anchors on the transition ENTRY (first
on-airway point) and flies the transition through once — the enroute leg
stops before the arrival area, so no self-loop. route_via_transition and
the est/near sorts use the last/first *point* (skip airways).
Files the mandate correctly now (… MOVUM T109 HAREM T104 WLD DCT EDDM).
Coverage 6/10 held, no regression (LFPG-EDDF still passes); LSZH-EGLL
6→4 PROF204; EGLL-EDDM's fake 1-err self-loop is replaced by its real
residuals (CPT dep forbidden, forbidden points on the mandated T104, WLD
STAR limit). 49 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse Annex 2B mandatory arrival routings and repair the best few
candidates (not just the lowest-error one):
- core::rad: MandatoryArr + RadData.mandatory_arr +
mandatory_arr_transitions(ades) -> allowed point-sequences.
- rad crate: parse_mandatory_arr() (VIA-clauses in NOT-AVBL-EXC rows,
scoped to ARR airports). via_sequences() now scans all parenthesised
transition groups (contain DCT/airway), not just contiguous "VIA (",
because sub-option markers separate them ("VIA i) (KARDU DCT KALMO)").
rad-tool `manarr [ADES]`. LIML now yields KARDU KALMO etc.
- discover: "manarr" candidates route to the transition's on-airway
anchor then splice the (often off-airway) transition points in via DCT.
- attempt_once repairs the top-2 candidates and keeps the best result:
the lowest-raw-error candidate isn't always the most repairable (a
mandate-satisfying route may carry more mechanical errors than a route
stuck on an unfixable mandatory).
Collapses the dense-FRA near-misses: EGLL-EDDM 7->1 err, EDDF-LIRF 3->1
(mandatory PROF205 resolved; residual is a single ROUTE49). Coverage 6/10,
no regression (passing pairs short-circuit on accept). 49 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse Annex 3A DEP "COMPULSORY FOR TFC" rows into structured mandatory
departure routings and satisfy PROF205 "… ANNEX3A DEP … MANDATORY ROUTE"
by offering the allowed transitions as discovery candidates:
- core::rad: MandatoryDep + RadData.mandatory_dep +
mandatory_dep_transitions(adep) -> allowed point-sequences.
- rad crate: parse_mandatory_dep() extracts the VIA(...) clauses, expands
nested "(A, B)" alternatives into separate branches, and chains
sub-fragments into full sequences from an entry point (46 rules; LFPG =
20 transitions incl. NURMO CMB VEKIN ADUTO, OPALE KESAX DIMAL ALESO).
rad-tool `mandep [ADEP]` to inspect.
- discover: for the departure airport, inject "mandep" candidates that fly
the allowed transitions nearest the destination (additive — the oracle
still picks best, so no regression risk).
Cracks LFPG-EDDF (Paris-Frankfurt): coverage 5/10 -> 6/10 on the batch,
no regression. 49 tests green. Phase 2b (mandatory arrivals) is next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse the 1135 forbidden airway segments in RAD Annex 2B (rows with
Airway/From/To + "NOT AVBL FOR TFC") into structured ForbiddenSeg records
(FL band + arrival/departure applicability), and route around the ones
that apply to a flight from the start:
- core::rad: ForbiddenSeg + RadData.forbidden_segs + forbidden_segments(
adep, ades, fl) -> applicable (airway,from,to) triples.
- rad crate: parse_forbidden_segments() wired into parse(); rad-tool
`forbidden [ADEP ADES FL]` to inspect.
- graph: seg_key() encodes a segment ban into the existing avoid channel;
build_avoiding skips those segments. discover seeds the avoid set with
the flight's Annex-2B bans before generating the airway candidate.
Measured: no regression (coverage 4/10 unchanged on the batch) — this is
the *forbidden* half. The coverage mover is mandatory routing (PROF205:
"must route via X"), which pure avoidance can't provide; that's phase 2.
49 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clean-room reproduction of PFPX: route generation + IFPS validation + OFP.
Independent design — official ICAO/EUROCONTROL data only (RAD, FRA points,
IFPUV oracle); no community FPL sources.
Workspace crates: core (routing/discover/rad/navdata/perf/export),
cli, server, rad (Annex parser), gui (Tauri v2 + React + MapLibre).
Route discovery: oracle-in-the-loop repair against Eurocontrol IFPUV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>