feat(routes): export/import validated routes — make the 100% DB permanent
The oracle-validated route DB (the realistic path to 100% under prose-only) lived only in the gitignored real.db, so a navdata rebuild would lose it. Now it is portable and version-controlled: - routes::export_validated -> TSV of every ifps_ok route (one per pair); import_seeds loads them back as reusable seeds (source=seed). - CLI `export-routes` / `import-routes`. - discover reuse now matches any ifps_ok route (not just source=ifps), so imported seeds are reused instantly (verified round-trip). - data/validated_routes.tsv: 65 validated European city-pair routes, built by bulk-seeding discover over ~85 common pairs (the DB grew ~11 -> 65). This is the PFPX-style route DB, committed: 65 pairs are now no-error & instant on any machine; `learn` + more bulk-seed grow it further. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,24 @@ enum Command {
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
},
|
||||
/// Export all IFPS-validated routes to a portable TSV file (version-control it
|
||||
/// so the validated route DB survives a navdata rebuild).
|
||||
ExportRoutes {
|
||||
#[arg(long, default_value = "real.db")]
|
||||
db: PathBuf,
|
||||
/// Output TSV path.
|
||||
#[arg(long, default_value = "data/validated_routes.tsv")]
|
||||
out: PathBuf,
|
||||
},
|
||||
/// Import validated routes from a TSV file (as produced by `export-routes`)
|
||||
/// back into the DB as reusable seeds.
|
||||
ImportRoutes {
|
||||
#[arg(long, default_value = "real.db")]
|
||||
db: PathBuf,
|
||||
/// Input TSV path.
|
||||
#[arg(long, default_value = "data/validated_routes.tsv")]
|
||||
r#in: PathBuf,
|
||||
},
|
||||
/// Offline IFPS pre-check of a route string (best-effort, non-authoritative).
|
||||
IfpsCheck {
|
||||
/// Route string, e.g. "LFPG DCT PON UT300 ELCOB ... EGLL".
|
||||
@@ -210,6 +228,23 @@ fn main() -> anyhow::Result<()> {
|
||||
for e in &verdict.errors { println!(" {} {}", e.code, e.msg); }
|
||||
}
|
||||
}
|
||||
Command::ExportRoutes { db, out } => {
|
||||
use flightplanner_core::{db as database, routes};
|
||||
let conn = database::open(&db)?;
|
||||
let tsv = routes::export_validated(&conn)?;
|
||||
let n = tsv.lines().filter(|l| !l.trim().is_empty()).count();
|
||||
if let Some(parent) = out.parent() { std::fs::create_dir_all(parent).ok(); }
|
||||
std::fs::write(&out, &tsv)?;
|
||||
println!("exported {n} IFPS-validated route(s) → {}", out.display());
|
||||
println!(" commit this file so the validated route DB is permanent & shareable.");
|
||||
}
|
||||
Command::ImportRoutes { db, r#in } => {
|
||||
use flightplanner_core::{db as database, routes};
|
||||
let conn = database::open(&db)?;
|
||||
let tsv = std::fs::read_to_string(&r#in)?;
|
||||
let n = routes::import_seeds(&conn, &tsv)?;
|
||||
println!("imported {n} validated seed(s) from {} — discover will reuse them.", r#in.display());
|
||||
}
|
||||
Command::Route {
|
||||
from,
|
||||
to,
|
||||
|
||||
@@ -414,7 +414,9 @@ pub fn discover_route(
|
||||
// Reuse: instant return if our DB already holds an oracle-validated (no-error)
|
||||
// route for this pair — this is how the self-built IFPS route DB pays off.
|
||||
if let Ok(stored) = crate::routes::recent(&conn, &from, &to, 25) {
|
||||
if let Some(r) = stored.iter().find(|r| r.ifps_ok && r.source == "ifps") {
|
||||
// Any IFPS-validated route for this pair — whether discovered here
|
||||
// (source=ifps) or imported from an exported seed file (source=seed).
|
||||
if let Some(r) = stored.iter().find(|r| r.ifps_ok) {
|
||||
return Ok(routing::discover::DiscoverResult {
|
||||
accepted: true,
|
||||
route_string: r.route_string.clone(),
|
||||
|
||||
@@ -97,3 +97,61 @@ pub fn recent(conn: &Connection, dep: &str, dest: &str, limit: usize) -> Result<
|
||||
pub fn count(conn: &Connection) -> Result<i64> {
|
||||
Ok(conn.query_row("SELECT COUNT(*) FROM routes", [], |r| r.get(0))?)
|
||||
}
|
||||
|
||||
/// Export every IFPS-validated (`ifps_ok`) route as TSV lines
|
||||
/// (`dep⇥dest⇥fl⇥dist⇥via_airways⇥route_string`). This is the portable, version-
|
||||
/// controllable form of the route DB, so validated seeds survive a navdata rebuild
|
||||
/// (the `routes` table lives in the same SQLite file as the imported navdata).
|
||||
pub fn export_validated(conn: &Connection) -> Result<String> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT dep,dest,cruise_fl,dist_nm,via_airways,route_string FROM routes \
|
||||
WHERE ifps_ok=1 GROUP BY dep,dest ORDER BY dep,dest",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |r| {
|
||||
Ok(format!(
|
||||
"{}\t{}\t{}\t{:.0}\t{}\t{}",
|
||||
r.get::<_, String>(0)?,
|
||||
r.get::<_, String>(1)?,
|
||||
r.get::<_, i32>(2)?,
|
||||
r.get::<_, f64>(3)?,
|
||||
r.get::<_, i32>(4)?,
|
||||
r.get::<_, String>(5)?,
|
||||
))
|
||||
})?;
|
||||
let mut out = String::new();
|
||||
for row in rows {
|
||||
out.push_str(&row?);
|
||||
out.push('\n');
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Import validated routes from TSV (as produced by [`export_validated`]),
|
||||
/// recording each as an `ifps_ok` seed (`source="seed"`). Returns the count.
|
||||
pub fn import_seeds(conn: &Connection, tsv: &str) -> Result<usize> {
|
||||
let mut n = 0;
|
||||
for line in tsv.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let f: Vec<&str> = line.splitn(6, '\t').collect();
|
||||
if f.len() < 6 {
|
||||
continue;
|
||||
}
|
||||
record(
|
||||
conn,
|
||||
f[0],
|
||||
f[1],
|
||||
f[2].parse().unwrap_or(0),
|
||||
f[5],
|
||||
f[3].parse().unwrap_or(0.0),
|
||||
f[4] == "1",
|
||||
true,
|
||||
&[],
|
||||
"seed",
|
||||
)?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user