top_drives/src/commands/mod.rs
Jan-Peter von Hunnius bf21d3d1f6 feat: add normalized config tracking and improve command
- Add normalize_to_final_build() to map tune configs to final builds (323/233/332)
- Add normalized_config field to BestCarRecord
- Change CarCounts.by_rid key from String to (String, String) for per-config tracking
- Change CarCounts.guid_to_rid to HashMap<String, HashSet<(String, String)>>
- Extract owned tune configs from my_cars.json (engineMajor/weightMajor/chassisMajor)
- Add improve command: owned cars not yet at final build, sorted by track count
- Strip version prefixes (v2c_/v1c_) from config keys at parse time
- Update dedup check in update_best_car to consider normalized_config
- Add atomic_write helper for safe CSV output
- Delete completed plans/code-review-v2.md
- Apply rustfmt formatting across codebase
2026-05-11 16:23:04 +02:00

63 lines
2.3 KiB
Rust

//! Command dispatch and shared helpers.
//!
//! Provides the `run_analysis` orchestration function and `load_my_cars_or_warn`
//! helper used by all command handlers.
pub mod all;
pub mod best;
pub mod details;
pub mod fetch;
pub mod improve;
pub mod track;
pub mod trash;
pub mod wishlist;
use std::sync::Arc;
use top_drives::{
compute_car_counts, load_my_cars_data, AnalysisContext, AppError, CarCounts, MyCarsData,
};
use crate::parsing::parse_all_times;
// ---------------------------------------------------------------------------
// Helper: load my_cars.json with graceful degradation
// ---------------------------------------------------------------------------
/// Loads user car data, printing a warning and returning empty data if the file is missing.
///
/// Only `MissingFile` errors are gracefully degraded — parse errors and other
/// failures are propagated to the caller, because running analysis with
/// silently-corrupted ownership data would produce misleading results.
pub fn load_my_cars_or_warn(path: &str) -> Result<MyCarsData, AppError> {
match load_my_cars_data(path) {
Ok(data) => Ok(data),
Err(AppError::MissingFile(msg)) => {
eprintln!("Warning: {msg}");
eprintln!(" Analysis will run without ownership data.");
Ok(MyCarsData::default())
}
Err(e) => Err(e),
}
}
// ---------------------------------------------------------------------------
// Analysis orchestration
// ---------------------------------------------------------------------------
/// Common analysis setup: loads user data, parses all timing files, and computes car counts.
///
/// Returns the analysis context (for stdout commands) and the aggregated car counts
/// (for CSV output commands). The car list is stored inside `CarCounts`.
pub async fn run_analysis(
my_data: &MyCarsData,
cars_db_path: &str,
times_dir: &str,
) -> Result<(AnalysisContext, CarCounts), AppError> {
let ctx = AnalysisContext::new();
let owned = Arc::new(my_data.owned.clone());
let car_list = parse_all_times(owned, &ctx, cars_db_path, times_dir).await?;
// Move only owned_configs; construct a new MyCarsData to transfer ownership.
let counts = compute_car_counts(&ctx, car_list, my_data.owned_configs.clone());
Ok((ctx, counts))
}