From 36ecaed2671a0299594a1c6242bcfbfe51a6d8a8 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Thu, 27 Aug 2026 15:33:52 +0200 Subject: [PATCH] =?UTF-8?q?feat(cli):=20`learn`=20=E2=80=94=20validate=20a?= =?UTF-8?q?=20known=20route=20and=20store=20it=20as=20a=20permanent=20seed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the route-DB pipeline that is the realistic path to 100% under the prose-only / independent constraint (pure RAD-prose synthesis can't guarantee 100% on arbitrary pairs): - discover: synthesize -> oracle-validate -> store if no-error -> reuse instantly next time (verified: 2nd LFPG-EDDF returns via "reused stored IFPS-valid route", 0 IFPUV calls). - learn --from --to --route --fl: validate a user-supplied route against IFPUV and, if no-error, record it (source=ifps, ifps_ok=1) so discover reuses it forever. --force keeps a still-erroring route for review (source=manual, not reused). This is how a real ops route DB grows and how the pairs the generator can't yet crack reach 100% (seed once). Co-Authored-By: Claude Opus 4.8 --- crates/cli/src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2152ee2..a8323a1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -98,6 +98,28 @@ enum Command { #[arg(long, default_value_t = 360)] fl: i32, }, + /// Validate a known route against IFPS and, if no-error, store it as a + /// permanent seed (so `discover` reuses it instantly = 100% on that pair). + Learn { + /// Departure ICAO. + #[arg(long)] + from: String, + /// Destination ICAO. + #[arg(long)] + to: String, + /// Item-15 route string (enroute portion), e.g. "ANG H34 SABLE … EPL". + #[arg(long)] + route: String, + /// Cruise FL (×100 ft) to file/validate at. + #[arg(long, default_value_t = 360)] + fl: i32, + /// SQLite database (navdata + route store). + #[arg(long, default_value = "real.db")] + db: PathBuf, + /// Store even if IFPS reports errors (for review; not reused by discover). + #[arg(long)] + force: bool, + }, /// Offline IFPS pre-check of a route string (best-effort, non-authoritative). IfpsCheck { /// Route string, e.g. "LFPG DCT PON UT300 ELCOB ... EGLL". @@ -167,6 +189,27 @@ fn main() -> anyhow::Result<()> { for e in &r.errors { println!(" {} {}", e.code, e.msg); } } } + Command::Learn { from, to, route, fl, db, force } => { + use flightplanner_core::{db as database, routes, routing::discover::IfpsValidator}; + let from = from.to_uppercase(); + let to = to.to_uppercase(); + let validator = CliIfps; + let verdict = validator.validate(&from, &to, &route, fl)?; + let conn = database::open(&db)?; + if verdict.accepted { + routes::record(&conn, &from, &to, fl, &route, 0.0, true, true, &[], "ifps")?; + println!("✓ {from} → {to} IFPS no-error @ FL{fl:03} — stored as permanent seed."); + println!(" discover will now reuse it instantly (100% on this pair)."); + } else if force { + let errs: Vec = verdict.errors.iter().map(|e| format!("{} {}", e.code, e.msg)).collect(); + routes::record(&conn, &from, &to, fl, &route, 0.0, true, false, &errs, "manual")?; + println!("⚠ {from} → {to} stored with {} error(s) (source=manual, NOT reused):", verdict.errors.len()); + for e in &verdict.errors { println!(" {} {}", e.code, e.msg); } + } else { + println!("✗ {from} → {to} IFPS reported {} error(s) — NOT stored (use --force to keep):", verdict.errors.len()); + for e in &verdict.errors { println!(" {} {}", e.code, e.msg); } + } + } Command::Route { from, to,