Initial commit: offline flight planner (Rust, PFPX-class)
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>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
//! Our own database of flight plans — routes we've generated and IFPS-pre-checked,
|
||||
//! cached per city pair in the `routes` table (see `db::schema`).
|
||||
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
const SEP: &str = " ||| ";
|
||||
|
||||
/// A stored (generated + pre-checked) flight plan.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CachedRoute {
|
||||
pub dep: String,
|
||||
pub dest: String,
|
||||
pub cruise_fl: i32,
|
||||
pub route_string: String,
|
||||
pub dist_nm: f64,
|
||||
pub via_airways: bool,
|
||||
pub ifps_ok: bool,
|
||||
pub ifps_errors: Vec<String>,
|
||||
pub source: String,
|
||||
pub generated_at: String,
|
||||
}
|
||||
|
||||
/// Insert or refresh a route (unique on dep/dest/FL/route_string).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn record(
|
||||
conn: &Connection,
|
||||
dep: &str,
|
||||
dest: &str,
|
||||
cruise_fl: i32,
|
||||
route_string: &str,
|
||||
dist_nm: f64,
|
||||
via_airways: bool,
|
||||
ifps_ok: bool,
|
||||
ifps_errors: &[String],
|
||||
source: &str,
|
||||
) -> Result<()> {
|
||||
conn.execute(
|
||||
"INSERT INTO routes \
|
||||
(dep,dest,cruise_fl,route_string,dist_nm,via_airways,ifps_ok,ifps_errors,source,generated_at) \
|
||||
VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9, datetime('now')) \
|
||||
ON CONFLICT(dep,dest,cruise_fl,route_string) DO UPDATE SET \
|
||||
dist_nm=excluded.dist_nm, via_airways=excluded.via_airways, ifps_ok=excluded.ifps_ok, \
|
||||
ifps_errors=excluded.ifps_errors, source=excluded.source, generated_at=excluded.generated_at",
|
||||
params![
|
||||
dep.to_uppercase(),
|
||||
dest.to_uppercase(),
|
||||
cruise_fl,
|
||||
route_string,
|
||||
dist_nm,
|
||||
via_airways as i32,
|
||||
ifps_ok as i32,
|
||||
ifps_errors.join(SEP),
|
||||
source,
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stored routes for a city pair, most recent first.
|
||||
pub fn recent(conn: &Connection, dep: &str, dest: &str, limit: usize) -> Result<Vec<CachedRoute>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT dep,dest,cruise_fl,route_string,dist_nm,via_airways,ifps_ok,ifps_errors,source,generated_at \
|
||||
FROM routes WHERE dep=?1 AND dest=?2 ORDER BY generated_at DESC LIMIT ?3",
|
||||
)?;
|
||||
let rows = stmt.query_map(
|
||||
params![dep.to_uppercase(), dest.to_uppercase(), limit as i64],
|
||||
|r| {
|
||||
let errs: String = r.get(7)?;
|
||||
Ok(CachedRoute {
|
||||
dep: r.get(0)?,
|
||||
dest: r.get(1)?,
|
||||
cruise_fl: r.get(2)?,
|
||||
route_string: r.get(3)?,
|
||||
dist_nm: r.get(4)?,
|
||||
via_airways: r.get::<_, i32>(5)? != 0,
|
||||
ifps_ok: r.get::<_, i32>(6)? != 0,
|
||||
ifps_errors: if errs.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
errs.split(SEP).map(str::to_string).collect()
|
||||
},
|
||||
source: r.get(8)?,
|
||||
generated_at: r.get(9)?,
|
||||
})
|
||||
},
|
||||
)?;
|
||||
let mut out = Vec::new();
|
||||
for row in rows {
|
||||
out.push(row?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Total number of stored routes.
|
||||
pub fn count(conn: &Connection) -> Result<i64> {
|
||||
Ok(conn.query_row("SELECT COUNT(*) FROM routes", [], |r| r.get(0))?)
|
||||
}
|
||||
Reference in New Issue
Block a user