top_drives/docs/API.md
Jan-Peter von Hunnius d2c1418e5e docs: update documentation to match refactored codebase
- Delete obsolete plans/code-review.md (all suggestions implemented)
- Rewrite docs/API.md to reflect modular architecture, DashMap, corrected
  AppError variants, accurate function signatures and module locations
- Rewrite docs/ARCHITECTURE.md to reflect DashMap concurrency, remove
  completed future enhancements, fix AnalysisContext description
- Update docs/USER_GUIDE.md with global CLI path options (--my-cars,
  --cars-db, --times-dir, --tracks-db) and updated troubleshooting
2026-05-08 16:36:14 +02:00

12 KiB

API Documentation

Overview

This document provides comprehensive API documentation for the Top Drives Records Analyzer, including all public functions, data structures, and their usage patterns.

Module Structure

src/
├── lib.rs                 # Shared library: types, utilities, error handling
├── records.rs             # Binary entry point: CLI definition and dispatch
├── commands/
│   ├── mod.rs             # Command dispatch, run_analysis(), load_my_cars_or_warn()
│   ├── fetch.rs           # Fetch command: download car/track/timing data
│   ├── best.rs            # Best command: best.csv + very_best.csv
│   ├── trash.rs           # Trash command: trash.csv
│   ├── wishlist.rs        # Wishlist command: wishlist.csv, add.csv, remove.csv
│   ├── details.rs         # Details command: details.csv
│   ├── track.rs           # Track command: stdout query
│   └── all.rs             # All command: generates all output files
├── parsing/
│   ├── mod.rs             # Core parsing: parse_car_times, update_best_car, parse_all_times
│   └── car_list.rs        # Car database loading: get_car_list
└── output/
    └── mod.rs             # CSV output: write_sorted_csv, write_best_csv, wishlist logic, etc.

Shared Library (src/lib.rs)

Common types and utilities shared across the project.

Public Types

Car

Core car information structure

#[derive(Debug, Clone, Deserialize)]
pub struct Car {
    pub rid: String,        // Repository identifier
    pub brand: String,      // Manufacturer/brand name
    #[serde(rename = "onlyName")]
    pub only_name: String,  // Model name (without brand)
    pub year: u16,          // Model year
    pub rq: u8,             // Rarity level (10-113)
    #[serde(default)]
    pub prize: bool,        // Whether car is prize-only
    pub guid: String,       // Game-specific card identifier
}

Purpose: Represents a car entity from the records database.

Deserialization: Uses serde::Deserialize with #[serde(rename = "onlyName")] for the only_name field and #[serde(default)] for prize.

BestCarRecord

Best performing car record for a specific track/RQ/condition

#[derive(Debug, Clone)]
pub struct BestCarRecord {
    pub car_rid: String,    // Car identifier
    pub brand: String,      // Manufacturer
    pub only_name: String,  // Model name
    pub year: u16,          // Year
    pub rq: u8,             // RQ level
    pub config: String,     // Tune config (111, 233, etc.)
    pub time: f64,          // Track time in seconds
    pub owned: bool,        // User ownership status
    pub prize: bool,        // Prize car status
    pub guid: String,       // Game identifier
}

TripletKey

Composite key for unique track conditions

pub type TripletKey = (String, u8, String);
//   Track ID   RQ Level   Surface Condition

AnalysisContext

Mutable analysis state using concurrent DashMap

pub struct AnalysisContext {
    pub best_cars: Arc<DashMap<TripletKey, Vec<BestCarRecord>>>,
}

Methods:

  • new() — Create a new, empty analysis context

Thread Safety: Uses DashMap for sharded internal locking, allowing concurrent access from multiple parsing tasks without a global lock.

CarCounts

Aggregated per-car win counts

pub struct CarCounts {
    pub by_rid: HashMap<String, (BestCarRecord, usize)>,
    pub guid_to_rid: HashMap<String, String>,
    pub car_map: HashMap<String, Car>,
    pub car_list: Vec<Car>,
}

MyCarsData

Parsed user data from my_cars.json

#[derive(Debug, Default)]
pub struct MyCarsData {
    pub owned: HashSet<String>,     // Owned car GUIDs
    pub wishlist: HashSet<String>,  // Wishlist car GUIDs
}

AppError

Unified error type

#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Network error: {0}")]
    Network(String),
    #[error("Parse error in {file}: {reason}")]
    Parse { file: String, reason: String },
    #[error("Missing data file: {0}")]
    MissingFile(String),
    #[error("Internal error: {0}")]
    Internal(String),
}

Public Functions

translate_track_name(track_code: &str) -> String

Translates internal track identifiers to human-readable names

  • Input: Track code (e.g., "laguna")
  • Output: Track name (e.g., "Laguna Seca")
  • Fallback: Returns original code if no mapping found
  • Data source: track_names.json (compiled into binary via include_str!)

translate_track_type(track_code: &str) -> &'static str

Translates condition codes to surface names

  • Input: Condition code (e.g., "00")
  • Output: Surface name (e.g., "Dry")
  • Fallback: Returns the raw code string for unrecognized codes

get_file(file_url: &str, local_file_path: &str) -> Result<(), AppError>

Downloads a file from a URL and saves it locally

  • Writes to a .tmp file first, then renames on success (atomic write)
  • Retries up to 3 times on server errors (5xx) and empty responses
  • Returns AppError::Network on HTTP failures
  • Returns AppError::Io on file write failures

load_my_cars_data(path: &str) -> Result<MyCarsData, AppError>

Loads and parses user car collection data

  • Input: Path to my_cars.json
  • Returns: MyCarsData with owned and wishlist GUIDs
  • Errors: AppError::MissingFile if file not found, AppError::Parse if JSON invalid

compare_cars(a: &Car, b: &Car) -> std::cmp::Ordering

Sorting comparator for cars

  • Sorts by: RQ → Brand → Model name

compute_car_counts(ctx: &AnalysisContext, car_list: Vec<Car>) -> Result<CarCounts, AppError>

Aggregates per-car win counts from analysis context

  • Iterates all entries in the DashMap
  • Returns CarCounts with by_rid, guid_to_rid, car_map, and car_list

escape_csv_field(field: &str) -> String

Escapes a field for safe CSV inclusion

  • Wraps in quotes if field contains comma, quote, newline, or carriage return
  • Doubles internal quotes per CSV spec

Records Binary (src/records.rs)

CLI entry point. Defines command-line arguments and dispatches to command handlers.

CLI Global Options

Option Default Description
--my-cars my_cars.json Path to user's car collection export
--cars-db cars_repo.json Path to car database file
--times-dir times Directory containing per-car timing JSON files
--tracks-db tracks_repo.json Path to tracks database file

CLI Commands

Command Description Flags
fetch Download latest car, track, and timing data None
best Generate best.csv and very_best.csv --min-count N (default: 10)
trash Generate trash.csv None
wishlist Generate wishlist.csv, add.csv, remove.csv --min-count N, --max-wishlist M
all Generate all output files in a single pass --min-count N, --max-wishlist M
details Generate details.csv with all timing data None
track Show best car for specific track/condition/RQ <track> <condition> <rq>

Parsing Module (src/parsing/)

Constants (src/parsing/mod.rs)

Constant Value Description
MAX_RQ 113 Maximum RQ level for range analysis
MIN_VALID_TIME 1.2 Minimum valid track time (seconds)
TIME_TOLERANCE 0.005 Tolerance for tie detection (seconds)
TEST_BOWL_FACTOR 2000.0 Multiplier for Test Bowl time inversion
CONCURRENCY_LIMIT 20 Maximum concurrent tasks
CAR_TIMING_API_BASE API URL Base URL for per-car timing data

Key Functions

parse_car_times(car, my_cars, best_cars, times_dir) -> Result<(), AppError>

Processes timing data for a single car (src/parsing/mod.rs)

  • Synchronous — designed to be called from tokio::task::spawn_blocking
  • Reads car JSON file from the times directory
  • Processes each configuration and track
  • Applies quality filters (downvotes, minimum time)
  • Handles Test Bowl time inversion
  • Uses rsplit_once("_a") for correct track name splitting

update_best_car(best_cars, track_id, car_rq, surface, new_record)

Updates best car records with a new performance entry (src/parsing/mod.rs)

  • Iterates RQ range from car's RQ to MAX_RQ
  • Replaces existing record if new time is faster
  • Adds to tie list if within TIME_TOLERANCE
  • Prevents duplicate car_rid entries in tie lists
  • Optimized: avoids cloning on the last iteration via is_last guard

parse_all_times(my_cars, ctx, cars_db_path, times_dir) -> Result<Vec<Car>, AppError>

Orchestrates concurrent parsing of all car timing files (src/parsing/mod.rs)

  • Loads car list from database
  • Uses Semaphore(CONCURRENCY_LIMIT) for bounded concurrency
  • Acquires semaphore in async context, then runs sync parsing via spawn_blocking
  • Returns the full car list for downstream use

fetch_times(cars_db_path, times_dir) -> Result<(), AppError>

Fetches timing data files for all cars (src/parsing/mod.rs)

  • Uses bounded concurrency via semaphore
  • Returns first error encountered, or Ok(()) if all succeed

get_car_list(cars_db_path: &str) -> Result<Vec<Car>, AppError>

Loads the car database (src/parsing/car_list.rs)

  • Uses serde deserialization for type-safe parsing
  • Returns AppError::MissingFile or AppError::Parse on failure

Commands Module (src/commands/)

run_analysis(owned, cars_db_path, times_dir) -> Result<(AnalysisContext, CarCounts), AppError>

Common analysis setup (src/commands/mod.rs)

Loads car database, parses all timing files concurrently, and computes car counts.

load_my_cars_or_warn(path: &str) -> Result<MyCarsData, AppError>

Loads user data with graceful degradation (src/commands/mod.rs)

  • Returns empty MyCarsData::default() with a warning if file is missing
  • Propagates parse errors and other failures

Output Module (src/output/)

CSV Output Functions

write_sorted_csv(path, header, entries, cmp, format_row) -> Result<(), AppError>

Generic sorted CSV writer

  • Sorts by index (avoids cloning entries)
  • Writes header row
  • Formats each row via closure

write_best_csv(counts), write_very_best_csv(counts, min_count)

Generate best car CSV files

  • Format: RQ,Brand,Model,Year,Count
  • Fields are CSV-escaped

write_trash_csv(counts, owned)

Generate trash car CSV file

  • Filters: not best on any track, RQ < 80, not prize car
  • Format: RQ,Brand,Model,Year

write_wishlist_files(counts, owned, wishlist, min_count, max_wishlist)

Generate wishlist, add, and remove CSV files

  • Computes ideal wishlist once
  • Derives all three files from it
  • Format: RQ,Brand,Model,Year,Count

write_details_csv(ctx)

Generate comprehensive timing data CSV

  • Format: RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time
  • Sorted by RQ then track ID

print_best_car(ctx, track, condition, rq) -> Result<(), AppError>

Print best car for a specific track/condition/RQ to stdout

compute_ideal_wishlist(counts, owned, min_count, max_wishlist) -> Vec<(BestCarRecord, usize)>

Computes the ideal wishlist

  • Filters to cars with count >= min_count
  • Excludes owned cars
  • Sorts by RQ
  • Caps at max_wishlist

Sorting Helpers

compare_records(a: &BestCarRecord, b: &BestCarRecord) -> std::cmp::Ordering

Comparison for BestCarRecord sorting

  • Sorts by: RQ → Brand → Model name