- Break monolithic records.rs into commands/, output/, parsing/ modules - Add lib.rs with shared types (AppError, Car, BestCarRecord, etc.) - Replace Arc<Mutex<HashMap>> with DashMap for concurrent access - Add unified AppError enum with thiserror - Add configurable CLI paths (--my-cars, --cars-db, --times-dir, --tracks-db) - Add serde deserialization for Car struct - Add CSV escaping for fields with commas/quotes/newlines - Fix track name splitting: split_once -> rsplit_once for _a handling - Add integration tests, proptest property-based tests, and unit tests - Add rustfmt.toml configuration - Update documentation (API, ARCHITECTURE, DATA_STRUCTURES, README)
8.7 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.
Core Modules
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
pub only_name: String, // Model name (without brand)
pub year: u16, // Model year
pub rq: u8, // Rarity level (10-113)
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
pub struct AnalysisContext {
pub best_cars: Arc<Mutex<HashMap<TripletKey, Vec<BestCarRecord>>>>,
}
Methods:
new()— Create a new, empty analysis contextlock_best_cars()— Acquire a lock on the best_cars map (returnsResult, converts poison errors toAppError)
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)]
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: mutex poisoned — {0}")]
Poisoned(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
"Unknown"for unrecognized codes
get_file(file_url: &str, local_file_path: &str) -> Result<(), AppError>
Downloads a file from a URL and saves it locally
- Removes existing file before downloading
- 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>) -> Result<CarCounts, AppError>
Aggregates per-car win counts from analysis context
- Returns
CarCountswith 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)
Primary analysis engine for processing JSON-based timing data.
Constants
| 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 |
DEFAULT_MY_CARS_PATH |
"my_cars.json" |
Default user data file path |
CARS_DB_PATH |
"cars_repo.json" |
Car database file path |
TIMES_DIR |
"times" |
Timing data directory |
CARS_DB_URL |
GitHub URL | Remote car database URL |
TRACKS_DB_URL |
GitHub URL | Remote tracks database URL |
CAR_TIMING_API_BASE |
API URL | Base URL for per-car timing data |
CLI Commands
| Command | Description | Flags |
|---|---|---|
fetch |
Download latest car and track 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> |
Key Internal Functions
run_analysis(owned: &HashSet<String>) -> Result<(AnalysisContext, CarCounts), AppError>
Common analysis setup
Loads user data, parses all timing files, and computes car counts.
parse_car_times(car: Car, my_cars: &HashSet<String>, ctx: &AnalysisContext)
Processes timing data for a single car
- Runs inside
tokio::task::spawn_blockingto avoid blocking async runtime - Reads car JSON file from
times/directory - Processes each configuration and track
- Applies quality filters (downvotes, minimum time)
- Handles Test Bowl time inversion
update_best_car(best_cars, track_id, car_rq, surface, new_record)
Updates best car records with a new performance entry
- 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
parse_all_times(my_cars, ctx) -> Vec<Car>
Orchestrates concurrent parsing of all car timing files
- Uses
Semaphore(CONCURRENCY_LIMIT)for bounded concurrency - Spawns blocking tasks via
tokio::task::spawn_blocking - Returns the full car list for downstream use
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
Output Functions
write_sorted_csv(path, header, entries, cmp, format_row)
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)
Print best car for a specific track/condition/RQ to stdout