feat(cli): learn — validate a known route and store it as a permanent seed
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<String> = 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,
|
||||
|
||||
Reference in New Issue
Block a user