- Add improve command to all command tables and usage sections - Add normalized_config field to BestCarRecord struct documentation - Update CarCounts.by_rid type from String to (String, String) - Update CarCounts.guid_to_rid type to HashMap<String, HashSet<(String, String)>> - Add owned_configs field to MyCarsData and CarCounts docs - Add normalize_to_final_build() to API docs - Update CSV format examples to include FinalBuild column - Update my_cars.json structure with engineMajor/weightMajor/chassisMajor - Update timing file example with v2c_ version prefix - Add version prefix stripping to data transformation docs - Update CHANGELOG.md with Unreleased section - Fix compare_records description to include normalized config sorting
14 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 # Thin re-export module: declares sub-modules, re-exports public types
├── types.rs # Core types: AppError, Car, BestCarRecord, TripletKey
├── context.rs # Analysis state: AnalysisContext, CarCounts, compute_car_counts
├── user_data.rs # User data: MyCarsData, load_my_cars_data
├── translate.rs # Translation: translate_track_name, translate_track_type
├── http.rs # HTTP: get_file (async download with retry)
├── csv_util.rs # CSV utilities: compare_cars, escape_csv_field
├── test_utils.rs # Test helpers: make_record, make_car
├── 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
│ ├── improve.rs # Improve command: improve.csv (owned cars not at final build)
│ └── 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)
Thin re-export module that declares 6 focused sub-modules and re-exports all public types for backward compatibility. External code can use top_drives::Car without knowing the internal module layout.
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, // Original tune config from JSON (111, 221, 233, etc.)
pub normalized_config: String, // Normalized final build (323, 233, or 332)
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, String), (BestCarRecord, usize)>,
pub guid_to_rid: HashMap<String, HashSet<(String, String)>>,
pub car_map: HashMap<String, Car>,
pub car_list: Vec<Car>,
pub owned_configs: HashMap<String, String>,
}
Key grouping: by_rid is keyed by (car_rid, normalized_config) so different tune configs of the same car are tracked separately. guid_to_rid maps each GUID to all (rid, config) pairs it appears under.
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
pub owned_configs: HashMap<String, String>, // GUID → actual tune config (e.g., "233")
}
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 viainclude_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
.tmpfile first, then renames on success (atomic write) - Retries up to 3 times on server errors (5xx) and empty responses
- Returns
AppError::Networkon HTTP failures - Returns
AppError::Ioon 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:
MyCarsDatawith owned and wishlist GUIDs - Errors:
AppError::MissingFileif file not found,AppError::Parseif 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>, owned_configs: HashMap<String, String>) -> CarCounts
Aggregates per-car win counts from analysis context
- Iterates all entries in the
DashMap - Groups by
(car_rid, normalized_config)for per-config tracking - Returns
CarCountswith by_rid, guid_to_rid, car_map, car_list, and owned_configs
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
normalize_to_final_build(config: &str) -> String
Normalizes a tune configuration to the closest "final build"
- The three final builds are
"323","233", and"332"(permutations of digits summing to 8) - For non-standard configs, repeatedly increments the smallest digit until sum reaches 8
- Returns the original string unchanged if config cannot be parsed or sum already ≥ 8
- Version prefixes (
v2c_,v1c_) are stripped at parse time before reaching this function
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> |
improve |
Generate improve.csv (owned cars not at final build) |
None |
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_ridentries in tie lists - Optimized: avoids cloning on the last iteration via
is_lastguard
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::MissingFileorAppError::Parseon 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,FinalBuild,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,FinalBuild,Count
write_improve_csv(counts) -> Result<usize, AppError>
Generate improve CSV file
- Lists owned cars whose actual player config is not yet a final build (323/233/332)
- Uses actual player config from
my_cars.json, not timing data configs - Format:
RQ,Brand,Model,Year,Target,Count - Sorted by track count descending (most impactful upgrades first)
- Returns the number of cars written
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 → Normalized Config → Brand → Model name