修复跑道数据:用 LIKE 查询所有 RKSI_RWY_%,不再硬编码索引

This commit is contained in:
2569718930@qq.com
2026-05-13 22:00:00 +08:00
parent 7038b14904
commit 26fff84c3a
2 changed files with 46 additions and 19 deletions
+31
View File
@@ -25,6 +25,37 @@ pub fn get_daily_max(db_path: &str, city_key: &str) -> Option<(f64, Option<Strin
.ok() .ok()
} }
/// Get runway-level observations for Seoul/Busan (stored as ICAO_RWY_N).
pub fn get_runway_temps(db_path: &str, icao: &str) -> Vec<(String, f64)> {
let conn = match Connection::open(db_path) {
Ok(c) => c,
Err(_) => return vec![],
};
let pattern = format!("{}_RWY_%", icao.to_uppercase());
let mut stmt = match conn.prepare(
"SELECT icao, temp_c FROM airport_obs_log \
WHERE icao LIKE ?1 AND created_at > datetime('now', '-120 minutes') \
ORDER BY icao, created_at DESC"
) {
Ok(s) => s,
Err(_) => return vec![],
};
let mut results: Vec<(String, f64)> = vec![];
let rows = stmt.query_map(rusqlite::params![pattern], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, f64>(1)?))
});
if let Ok(iter) = rows {
let mut seen = std::collections::HashSet::new();
for r in iter.filter_map(|r| r.ok()) {
// Take only the first (most recent) per unique icao
if seen.insert(r.0.clone()) {
results.push(r);
}
}
}
results
}
/// Get recent temperature observations for an ICAO station. /// Get recent temperature observations for an ICAO station.
pub fn get_recent_obs(db_path: &str, icao: &str, minutes: i32, limit: usize) -> Vec<ObsRow> { pub fn get_recent_obs(db_path: &str, icao: &str, minutes: i32, limit: usize) -> Vec<ObsRow> {
let conn = match Connection::open(db_path) { let conn = match Connection::open(db_path) {
+15 -19
View File
@@ -32,12 +32,6 @@ const CITIES: &[(&str, &str, &str, &str, &str, i32, f64, &str, usize)] = &[
("taipei", "台北", "Taipei", "466920", "Songshan", 8, 1.5, "TST", 0), ("taipei", "台北", "Taipei", "466920", "Songshan", 8, 1.5, "TST", 0),
]; ];
// 跑道标签(与 AMOS scrape 返回的一致)
const RUNWAY_LABELS: &[&[&str]] = &[
&["18L/36R", "18R/36L"], // seoul
&["18L/36R", "18R/36L"], // busan
];
// ── app state ── // ── app state ──
struct AppState { struct AppState {
db_path: String, db_path: String,
@@ -83,7 +77,7 @@ fn parse_obs_time(raw: &str) -> Option<chrono::DateTime<Utc>> {
// ── data loading ── // ── data loading ──
fn load_city_snapshot(db_path: &str, idx: usize, (key, _zh, en, icao, airport, tz, thresh, tz_abbr, rw_count): &(&str, &str, &str, &str, &str, i32, f64, &str, usize)) -> CitySnapshot { fn load_city_snapshot(db_path: &str, _idx: usize, (key, _zh, en, icao, airport, tz, thresh, tz_abbr, rw_count): &(&str, &str, &str, &str, &str, i32, f64, &str, usize)) -> CitySnapshot {
let now_utc = Utc::now(); let now_utc = Utc::now();
let offset = chrono::FixedOffset::east_opt(*tz * 3600).unwrap(); let offset = chrono::FixedOffset::east_opt(*tz * 3600).unwrap();
@@ -139,18 +133,20 @@ fn load_city_snapshot(db_path: &str, idx: usize, (key, _zh, en, icao, airport, t
_ => false, _ => false,
}; };
// Runway data: query each runway ICAO // Runway data: query all ICAO_RWY_* for this airport
let mut runway_pairs: Vec<(String, f64)> = vec![]; let runway_pairs: Vec<(String, f64)> = if *rw_count > 0 {
if *rw_count > 0 && idx < RUNWAY_LABELS.len() { db::get_runway_temps(db_path, icao)
for i in 0..*rw_count { .into_iter()
let rw_icao = format!("{}_RWY_{}", icao, i); .map(|(rw_icao, temp)| {
let rw_obs = db::get_recent_obs(db_path, &rw_icao, 120, 1); let label = rw_icao.strip_prefix(&format!("{}_RWY_", icao.to_uppercase()))
if let Some(rw_temp) = rw_obs.first().and_then(|o| o.temp_c) { .map(|n| format!("RWY {}", n))
let label = RUNWAY_LABELS[idx].get(i).unwrap_or(&"?").to_string(); .unwrap_or_else(|| rw_icao);
runway_pairs.push((label, rw_temp)); (label, temp)
} })
} .collect()
} } else {
vec![]
};
CitySnapshot { CitySnapshot {
name: key.to_string(), name: key.to_string(),