Address full-codebase review: logging boundary, clone elimination, robustness

Library no longer prints to stderr:
- Replace every eprintln! with log::warn! (lib: user_data, http; bin:
  parsing, output, commands); initialize env_logger in main with a
  warn-level default so output is unchanged unless RUST_LOG is set
- Warn on missing/non-numeric "t" time fields instead of silently
  coercing them to 0.0; suppress the per-car NotFound warning (most
  cars legitimately have no timing file), keep warnings for real I/O
  errors

Ownership and clone cleanup:
- CarCounts no longer duplicates the car database: drop the car_list
  field and build car_map by consuming car_list (each Car stored once);
  iterate car_map.values() where the list was previously used
- run_analysis takes MyCarsData by value: owned moves into the parse
  Arc, owned_configs/copy_count move into CarCounts; only the three
  commands that need owned/wishlist afterwards retain their own copy
- is_final_build checks the digits directly (no per-call String
  allocation) and treats malformed configs as non-final so they
  surface in improve.csv; owned_final_configs becomes a borrowed
  rid-to-configs map so the target-exclusion check allocates nothing
- Normalize sum arithmetic to u8 in normalize_to_final_build

Docs and structure:
- Document the first-seen-wins tie-breaking in compute_ideal_wishlist
  and the non-final/non-best coverage gap in write_trash_csv
- Extract compare_by_rq_config_brand_name shared by compare_records
  and the remove.csv sort; drop the unused _output_dir parameter from
  run_track; add a retry_delay helper de-duplicating the three HTTP
  backoff sites; rename the shadowed `entry` binding to config_data
This commit is contained in:
Jan-Peter von Hunnius 2026-08-15 19:29:51 +02:00
parent 39d1e1d71b
commit 58b8d23e68
17 changed files with 255 additions and 120 deletions

View file

@ -33,8 +33,8 @@ 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.");
log::warn!("{msg}");
log::warn!("Analysis will run without ownership data.");
Ok(MyCarsData::default())
}
Err(e) => Err(e),
@ -45,23 +45,24 @@ pub fn load_my_cars_or_warn(path: &str) -> Result<MyCarsData, AppError> {
// Analysis orchestration
// ---------------------------------------------------------------------------
/// Common analysis setup: loads user data, parses all timing files, and computes car counts.
/// Common analysis setup: parses all timing files and computes car counts.
///
/// Takes `my_data` by value: `owned` is shared with the parse tasks via `Arc`,
/// and `owned_configs`/`copy_count` are moved into [`CarCounts`]. Callers that
/// need `owned` or `wishlist` after analysis must retain their own copy before
/// calling this function.
///
/// Returns the analysis context (for stdout commands) and the aggregated car counts
/// (for CSV output commands). The car list is stored inside `CarCounts`.
/// (for CSV output commands).
pub async fn run_analysis(
my_data: &MyCarsData,
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 MyCarsData { owned, wishlist: _, owned_configs, copy_count } = my_data;
let owned = Arc::new(owned);
let car_list = parse_all_times(owned, &ctx, cars_db_path, times_dir).await?;
let counts = compute_car_counts(
&ctx,
car_list,
my_data.owned_configs.clone(),
my_data.copy_count.clone(),
);
let counts = compute_car_counts(&ctx, car_list, owned_configs, copy_count);
Ok((ctx, counts))
}