Release v0.3.0: CSV output, wishlist commands, improved error handling
- Replace stdout output with structured CSV file generation - Add wishlist, all commands with --min-count and --max-wishlist flags - Introduce CarCounts struct for shared analysis computation - Improve error handling: return Result instead of unwrap/panic - Add prize field to BestCarRecord - Remove TODO.md
This commit is contained in:
parent
08c96d9981
commit
9d57b18a4e
8 changed files with 697 additions and 594 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
|
@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.0] - 2026-05-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New `all` command that generates all output CSV files in a single parse pass
|
||||||
|
- New `wishlist` command with `--min-count` and `--max-wishlist` flags for wishlist management
|
||||||
|
- `--min-count` flag on `best` command to configure very_best threshold (default: 10)
|
||||||
|
- `--max-wishlist` flag on `wishlist` and `all` commands (default: 300)
|
||||||
|
- `CarCounts` struct and `compute_car_counts()` function for shared analysis computation
|
||||||
|
- CSV file output: `best.csv`, `very_best.csv`, `wishlist.csv`, `add.csv`, `remove.csv`
|
||||||
|
- All output files now include CSV headers
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `best` command now writes to `best.csv` and `very_best.csv` instead of printing to stdout
|
||||||
|
- `trash` command now writes to `trash.csv` instead of printing to stdout
|
||||||
|
- All output files are sorted by car RQ (via `compare_cars()`)
|
||||||
|
- `wishlist` command now generates ideal wishlist, add/remove recommendations
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `missing` command (functionality absorbed into `wishlist`/`add`/`remove`)
|
||||||
|
- `+` prefix from output (no longer needed with dedicated wishlist commands)
|
||||||
|
- `-` prefix from output (replaced by dedicated `remove.csv`)
|
||||||
|
- `print_best_cars()`, `print_wishlist_stale()`, `print_trash()` functions
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-05-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Comprehensive documentation suite in `docs/` directory
|
- Comprehensive documentation suite in `docs/` directory
|
||||||
- Detailed code comments for all public functions
|
- Detailed code comments for all public functions
|
||||||
|
|
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1310,7 +1310,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "top_drives"
|
name = "top_drives"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "top_drives"
|
name = "top_drives"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
|
||||||
143
README.md
143
README.md
|
|
@ -1,12 +1,12 @@
|
||||||
# Top Drives Records Analyzer
|
# Top Drives Records Analyzer
|
||||||
|
|
||||||
A comprehensive Rust-based tool for analyzing car performance data in the Top Drives racing game. This tool fetches track times from a community-maintained repository and provides detailed analysis including best cars, missing cars, and optimization recommendations.
|
A comprehensive Rust-based tool for analyzing car performance data in the Top Drives racing game. This tool fetches track times from a community-maintained repository and provides detailed analysis including best cars, trash identification, and wishlist optimization.
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- **Best Car Analysis**: Find the best performing cars across all tracks and conditions
|
- **Best Car Analysis**: Find the best performing cars across all tracks and conditions
|
||||||
- **Missing Cars Detection**: Identify cars missing from your collection that would improve performance
|
- **Wishlist Optimization**: Identify cars to add to or remove from your wishlist
|
||||||
- **Collection Optimization**: Identify cars that can be safely removed from your collection
|
- **Collection Cleanup**: Identify cars that can be safely removed from your collection
|
||||||
- **Track-Specific Analysis**: Get detailed performance data for specific tracks and conditions
|
- **Track-Specific Analysis**: Get detailed performance data for specific tracks and conditions
|
||||||
- **RQ-Based Filtering**: Filter results by car rarity (RQ) levels
|
- **RQ-Based Filtering**: Filter results by car rarity (RQ) levels
|
||||||
- **Multiple Surface Types**: Support for Dry, Wet, Dirt, Snow, and other track conditions
|
- **Multiple Surface Types**: Support for Dry, Wet, Dirt, Snow, and other track conditions
|
||||||
|
|
@ -55,14 +55,14 @@ A comprehensive Rust-based tool for analyzing car performance data in the Top Dr
|
||||||
cargo run --bin records -- fetch
|
cargo run --bin records -- fetch
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Basic Analysis**: Find the best cars in your collection
|
2. **Full Analysis**: Generate all output files in a single pass
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- best
|
cargo run --bin records -- all
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Find Missing Cars**: Discover cars that would improve your performance
|
3. **Best Cars**: Find the best cars across all tracks
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- missing
|
cargo run --bin records -- best
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Collection Cleanup**: Identify cars safe to remove
|
4. **Collection Cleanup**: Identify cars safe to remove
|
||||||
|
|
@ -70,6 +70,11 @@ A comprehensive Rust-based tool for analyzing car performance data in the Top Dr
|
||||||
cargo run --bin records -- trash
|
cargo run --bin records -- trash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
5. **Wishlist Management**: Optimize your wishlist
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist
|
||||||
|
```
|
||||||
|
|
||||||
## 📖 Usage Guide
|
## 📖 Usage Guide
|
||||||
|
|
||||||
### Basic Commands
|
### Basic Commands
|
||||||
|
|
@ -84,33 +89,79 @@ Downloads the latest car database and track information from the community repos
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- best
|
cargo run --bin records -- best
|
||||||
```
|
```
|
||||||
Shows all cars in your collection ranked by their performance across tracks.
|
Generates `best.csv` (all cars best on at least one track) and `very_best.csv` (cars best on ≥10 tracks). Both files are sorted by RQ.
|
||||||
|
|
||||||
**Sample Output:**
|
**Output Files:**
|
||||||
```
|
```
|
||||||
RQ,Brand,Model,Year,Count
|
best.csv — All cars best on at least one track
|
||||||
85,McLaren,F1,1993,15
|
very_best.csv — Cars best on ≥10 tracks (default)
|
||||||
82,Ferrari,250 GTO,1962,12
|
|
||||||
80,Lamborghini,Countach,1974,8
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Missing Cars Analysis
|
Use `--min-count N` to change the threshold for `very_best.csv`:
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- missing
|
cargo run --bin records -- best --min-count 5
|
||||||
```
|
```
|
||||||
Shows the best cars that you don't own, helping prioritize acquisitions.
|
|
||||||
|
|
||||||
#### Collection Optimization
|
#### Trash Analysis
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- trash
|
cargo run --bin records -- trash
|
||||||
```
|
```
|
||||||
Identifies cars in your collection that aren't among the best performers and could be removed.
|
Generates `trash.csv` — owned cars that are not best on any track, have RQ < 80, and are not prize cars. Sorted by RQ.
|
||||||
|
|
||||||
|
**Output File:**
|
||||||
|
```
|
||||||
|
trash.csv — Owned cars safe to remove
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Wishlist Management
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist
|
||||||
|
```
|
||||||
|
Generates `wishlist.csv` (ideal wishlist: very_best unowned cars, capped at 300), `add.csv` (cars that should be on your wishlist but aren't), and `remove.csv` (cars on your wishlist that shouldn't be). All sorted by RQ.
|
||||||
|
|
||||||
|
**Output Files:**
|
||||||
|
```
|
||||||
|
wishlist.csv — Ideal wishlist (very_best unowned, capped)
|
||||||
|
add.csv — Cars to add to wishlist
|
||||||
|
remove.csv — Cars to remove from wishlist
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--min-count N` to change the threshold for very_best cars (default 10):
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist --min-count 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--max-wishlist M` to change the wishlist cap (default 300):
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist --max-wishlist 500
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Full Analysis
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- all
|
||||||
|
```
|
||||||
|
Generates all 6 CSV files in a single parse pass. Equivalent to running `best`, `trash`, and `wishlist` together, but more efficient.
|
||||||
|
|
||||||
|
**Output Files:**
|
||||||
|
```
|
||||||
|
best.csv — All cars best on at least one track
|
||||||
|
very_best.csv — Cars best on ≥10 tracks
|
||||||
|
trash.csv — Owned cars safe to remove
|
||||||
|
wishlist.csv — Ideal wishlist (very_best unowned, capped)
|
||||||
|
add.csv — Cars to add to wishlist
|
||||||
|
remove.csv — Cars to remove from wishlist
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports both `--min-count N` (default 10) and `--max-wishlist M` (default 300):
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- all --min-count 5 --max-wishlist 500
|
||||||
|
```
|
||||||
|
|
||||||
#### Detailed Performance Data
|
#### Detailed Performance Data
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- details
|
cargo run --bin records -- details
|
||||||
```
|
```
|
||||||
Shows comprehensive timing data for all tracks and conditions.
|
Generates `details.csv` with comprehensive timing data for all tracks and conditions.
|
||||||
|
|
||||||
**Sample Output:**
|
**Sample Output:**
|
||||||
```
|
```
|
||||||
|
|
@ -123,12 +174,12 @@ RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- track "Laguna Seca" "Dry" "85"
|
cargo run --bin records -- track "Laguna Seca" "Dry" "85"
|
||||||
```
|
```
|
||||||
Finds the best car for a specific track, condition, and RQ combination.
|
Prints the best car for a specific track, condition, and RQ combination to stdout.
|
||||||
|
|
||||||
### Advanced Usage
|
### Advanced Usage
|
||||||
|
|
||||||
#### Multiple RQ Analysis
|
#### Multiple RQ Analysis
|
||||||
The tool supports analyzing performance across different RQ ranges. Use the `missing` and `best` commands to see how cars perform at various rarity levels.
|
The tool supports analyzing performance across different RQ ranges. Use the `best` and `wishlist` commands to see how cars perform at various rarity levels.
|
||||||
|
|
||||||
#### Track Conditions
|
#### Track Conditions
|
||||||
The analyzer supports multiple track conditions:
|
The analyzer supports multiple track conditions:
|
||||||
|
|
@ -143,37 +194,49 @@ The analyzer supports multiple track conditions:
|
||||||
| Command | Description | Arguments |
|
| Command | Description | Arguments |
|
||||||
|---------|-------------|-----------|
|
|---------|-------------|-----------|
|
||||||
| `fetch` | Download latest car and track data | None |
|
| `fetch` | Download latest car and track data | None |
|
||||||
| `best` | Show best performing cars in collection | None |
|
| `best` | Generate `best.csv` and `very_best.csv` | `[--min-count N]` |
|
||||||
| `missing` | Show best cars not in collection | None |
|
| `trash` | Generate `trash.csv` (owned cars safe to remove) | None |
|
||||||
| `trash` | Show cars safe to remove from collection | None |
|
| `wishlist` | Generate `wishlist.csv`, `add.csv`, `remove.csv` | `[--min-count N] [--max-wishlist M]` |
|
||||||
| `details` | Show detailed timing data for all tracks | None |
|
| `all` | Generate all 6 CSV files in a single pass | `[--min-count N] [--max-wishlist M]` |
|
||||||
| `track` | Show best car for specific track | `<track_name> <condition> <rq>` |
|
| `details` | Print detailed timing data for all tracks (stdout) | None |
|
||||||
|
| `track` | Print best car for specific track (stdout) | `<track_name> <condition> <rq>` |
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `--min-count N` | `10` | Minimum number of tracks a car must be best on to qualify as "very best" |
|
||||||
|
| `--max-wishlist M` | `300` | Maximum number of cars on the ideal wishlist |
|
||||||
|
|
||||||
## 📅 Daily Workflow
|
## 📅 Daily Workflow
|
||||||
|
|
||||||
The typical daily workflow uses `full_script` which runs `fetch` followed by all analysis commands in sequence:
|
The typical daily workflow uses the `all` command, which runs all analysis in a single efficient parse pass:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- full_script
|
cargo run --bin records -- fetch
|
||||||
|
cargo run --bin records -- all
|
||||||
```
|
```
|
||||||
|
|
||||||
This generates the following output files:
|
Or combine them:
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- fetch && cargo run --bin records -- all
|
||||||
|
```
|
||||||
|
|
||||||
| File | Description |
|
This generates the following output files (all sorted by car RQ):
|
||||||
|------|-------------|
|
|
||||||
| `best.csv` | Ranked list of best cars — used to evaluate if a car is worth keeping |
|
| File | Format | Description |
|
||||||
| `details.csv` | Detailed timing data for all tracks/conditions — used to find the best config for a car |
|
|------|--------|-------------|
|
||||||
| `add.csv` | Cars to add to wishlist — missing cars not yet wishlisted, prefixed with `+` |
|
| `best.csv` | `RQ,Brand,Model,Year,Count` | All cars best on at least one track |
|
||||||
| `remove.csv` | Cars to remove from wishlist — stale wishlist entries, prefixed with `-` |
|
| `very_best.csv` | `RQ,Brand,Model,Year,Count` | Cars best on ≥ min_count tracks |
|
||||||
| `trash.csv` | Owned cars that can be safely removed |
|
| `trash.csv` | `RQ,Brand,Model,Year` | Owned cars not best on any track (RQ<80, not prize) |
|
||||||
| `sorted.csv` | Best cars sorted by count (descending) |
|
| `wishlist.csv` | `RQ,Brand,Model,Year,Count` | Ideal wishlist (very_best unowned, capped) |
|
||||||
| `very_best.csv` | Best cars with count ≥ 10 |
|
| `add.csv` | `RQ,Brand,Model,Year,Count` | Cars to add to wishlist |
|
||||||
| `wishlist.csv` | Missing cars already on wishlist |
|
| `remove.csv` | `RQ,Brand,Model,Year,Count` | Cars to remove from wishlist |
|
||||||
|
|
||||||
### Workflow Steps
|
### Workflow Steps
|
||||||
|
|
||||||
1. **Fetch** fresh data from the community repository
|
1. **Fetch** fresh data from the community repository
|
||||||
2. **Analyze** your collection against the latest timing data
|
2. **Analyze** your collection with `all` to generate all CSV files
|
||||||
3. **Review** `best.csv` to see how your cars rank
|
3. **Review** `best.csv` to see how your cars rank
|
||||||
4. **Check** `add.csv` for cars worth adding to your wishlist
|
4. **Check** `add.csv` for cars worth adding to your wishlist
|
||||||
5. **Clean up** `remove.csv` entries from your wishlist
|
5. **Clean up** `remove.csv` entries from your wishlist
|
||||||
|
|
|
||||||
4
TODO.md
4
TODO.md
|
|
@ -1,4 +0,0 @@
|
||||||
1) **Move shell post-processing into Rust** — `full_script` does `sort`, `grep`, file splitting via shell. Add native subcommands (`very-best`, `add`, `remove`) or CLI flags (`--min-count`, `--sort-by`) so the binary produces the final files directly. Modify the script to export to files, nothing else.
|
|
||||||
2) **Correct `+` prefix semantics** — `+` means "not on wishlist". It is not needed in `best`/`missing` output but is used as "add to wishlist". Since we incorporated the wishlist actions into the application, the prefix can go.
|
|
||||||
3) **Add `all` subcommand** — `full_script` runs 5 commands sequentially, each parsing all timing files independently. A single `all` command would parse once and write all outputs, cutting runtime ~80%.
|
|
||||||
4) **Document the daily workflow** — README and USER_GUIDE don't explain `full_script` or the relationship between Rust output and the files (`sorted.csv`, `very_best.csv`, `wishlist.csv`, `add.csv`).
|
|
||||||
|
|
@ -18,9 +18,10 @@ This guide provides comprehensive instructions for using the Top Drives Records
|
||||||
|
|
||||||
**Export your car collection from Top Drives:**
|
**Export your car collection from Top Drives:**
|
||||||
|
|
||||||
1. Open Top Drives on your device
|
1. Set up HTTP interception (HTTP Toolkit)
|
||||||
2. Go to Settings → Account → Export Data
|
2. Open Top Drives on an emulator (Bluestacks)
|
||||||
3. Save the file as `my_cars.json` in the project directory
|
3. Intercept transmission of your game data
|
||||||
|
4. Save to file `my_cars.json` in the project directory
|
||||||
|
|
||||||
### 2. Initial Data Download
|
### 2. Initial Data Download
|
||||||
|
|
||||||
|
|
@ -55,9 +56,19 @@ Fetching https://api.topdrivesrecords.com/car/1002.json.
|
||||||
cargo run --bin records -- best
|
cargo run --bin records -- best
|
||||||
```
|
```
|
||||||
|
|
||||||
**Purpose:** Shows the best performing cars in your collection, ranked by how often they achieve top times.
|
**Purpose:** Generates `best.csv` (all cars best on at least one track) and `very_best.csv` (cars best on ≥10 tracks). Both files are sorted by RQ.
|
||||||
|
|
||||||
**Sample Output:**
|
**Flags:**
|
||||||
|
- `--min-count N` — Minimum number of tracks a car must be best on to qualify as "very best" (default: 10)
|
||||||
|
|
||||||
|
**Example with custom threshold:**
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- best --min-count 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Files:**
|
||||||
|
|
||||||
|
`best.csv`:
|
||||||
```csv
|
```csv
|
||||||
RQ,Brand,Model,Year,Count
|
RQ,Brand,Model,Year,Count
|
||||||
85,McLaren,F1,1993,15
|
85,McLaren,F1,1993,15
|
||||||
|
|
@ -66,6 +77,13 @@ RQ,Brand,Model,Year,Count
|
||||||
75,Porsche,911 Turbo,1975,6
|
75,Porsche,911 Turbo,1975,6
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`very_best.csv`:
|
||||||
|
```csv
|
||||||
|
RQ,Brand,Model,Year,Count
|
||||||
|
85,McLaren,F1,1993,15
|
||||||
|
82,Ferrari,250 GTO,1962,12
|
||||||
|
```
|
||||||
|
|
||||||
**Understanding the output:**
|
**Understanding the output:**
|
||||||
- **RQ**: Car's rarity level
|
- **RQ**: Car's rarity level
|
||||||
- **Brand**: Car manufacturer
|
- **Brand**: Car manufacturer
|
||||||
|
|
@ -78,38 +96,18 @@ RQ,Brand,Model,Year,Count
|
||||||
- Focus on cars with counts ≥ 5 for your core collection
|
- Focus on cars with counts ≥ 5 for your core collection
|
||||||
- Compare with your owned cars to identify strengths
|
- Compare with your owned cars to identify strengths
|
||||||
|
|
||||||
#### Missing Cars Analysis
|
#### Trash Analysis
|
||||||
|
|
||||||
**Command:**
|
|
||||||
```bash
|
|
||||||
cargo run --bin records -- missing
|
|
||||||
```
|
|
||||||
|
|
||||||
**Purpose:** Identifies high-performing cars you don't own that would improve your collection.
|
|
||||||
|
|
||||||
**Sample Output:**
|
|
||||||
```csv
|
|
||||||
RQ,Brand,Model,Year,Count
|
|
||||||
90,Pagani,Zonda R,2010,18
|
|
||||||
88,Ferrari,F40,1987,15
|
|
||||||
87,McLaren,Senna,2018,12
|
|
||||||
```
|
|
||||||
|
|
||||||
**Action items:**
|
|
||||||
- Prioritize cars with high counts (≥10)
|
|
||||||
- Consider RQ restrictions in your racing bracket
|
|
||||||
- Look for cars that perform well across multiple track types
|
|
||||||
|
|
||||||
#### Collection Optimization (Trash Analysis)
|
|
||||||
|
|
||||||
**Command:**
|
**Command:**
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- trash
|
cargo run --bin records -- trash
|
||||||
```
|
```
|
||||||
|
|
||||||
**Purpose:** Identifies cars safe to remove from your collection.
|
**Purpose:** Generates `trash.csv` — owned cars that are not best on any track, have RQ < 80, and are not prize cars. Sorted by RQ.
|
||||||
|
|
||||||
**Sample Output:**
|
**Output File:**
|
||||||
|
|
||||||
|
`trash.csv`:
|
||||||
```csv
|
```csv
|
||||||
RQ,Brand,Model,Year
|
RQ,Brand,Model,Year
|
||||||
45,Toyota,Corolla,1995
|
45,Toyota,Corolla,1995
|
||||||
|
|
@ -123,6 +121,84 @@ RQ,Brand,Model,Year
|
||||||
- Only suggests cars that never achieve best times
|
- Only suggests cars that never achieve best times
|
||||||
- **Always verify before selling in-game**
|
- **Always verify before selling in-game**
|
||||||
|
|
||||||
|
#### Wishlist Management
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose:** Generates three files for wishlist optimization:
|
||||||
|
- `wishlist.csv` — Ideal wishlist: very_best unowned cars, capped at 300
|
||||||
|
- `add.csv` — Cars that should be on your wishlist but aren't
|
||||||
|
- `remove.csv` — Cars on your wishlist that shouldn't be
|
||||||
|
|
||||||
|
All files are sorted by RQ.
|
||||||
|
|
||||||
|
**Flags:**
|
||||||
|
- `--min-count N` — Minimum number of tracks for "very best" qualification (default: 10)
|
||||||
|
- `--max-wishlist M` — Maximum number of cars on the ideal wishlist (default: 300)
|
||||||
|
|
||||||
|
**Example with custom flags:**
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- wishlist --min-count 5 --max-wishlist 500
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Files:**
|
||||||
|
|
||||||
|
`wishlist.csv`:
|
||||||
|
```csv
|
||||||
|
RQ,Brand,Model,Year,Count
|
||||||
|
90,Pagani,Zonda R,2010,18
|
||||||
|
88,Ferrari,F40,1987,15
|
||||||
|
87,McLaren,Senna,2018,12
|
||||||
|
```
|
||||||
|
|
||||||
|
`add.csv`:
|
||||||
|
```csv
|
||||||
|
RQ,Brand,Model,Year,Count
|
||||||
|
90,Pagani,Zonda R,2010,18
|
||||||
|
88,Ferrari,F40,1987,15
|
||||||
|
```
|
||||||
|
|
||||||
|
`remove.csv`:
|
||||||
|
```csv
|
||||||
|
RQ,Brand,Model,Year,Count
|
||||||
|
75,BMW,M3,2003,3
|
||||||
|
72,Audi,RS6,2002,2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Action items:**
|
||||||
|
- Add cars from `add.csv` to your in-game wishlist
|
||||||
|
- Remove cars from `remove.csv` from your in-game wishlist
|
||||||
|
- Use `wishlist.csv` as your target wishlist
|
||||||
|
|
||||||
|
#### Full Analysis
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- all
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose:** Generates all 6 CSV files in a single parse pass. Equivalent to running `best`, `trash`, and `wishlist` together, but more efficient since data is only parsed once.
|
||||||
|
|
||||||
|
**Flags:**
|
||||||
|
- `--min-count N` — Minimum number of tracks for "very best" qualification (default: 10)
|
||||||
|
- `--max-wishlist M` — Maximum number of cars on the ideal wishlist (default: 300)
|
||||||
|
|
||||||
|
**Example with custom flags:**
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- all --min-count 5 --max-wishlist 500
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Files:**
|
||||||
|
- `best.csv` — All cars best on at least one track
|
||||||
|
- `very_best.csv` — Cars best on ≥ min_count tracks
|
||||||
|
- `trash.csv` — Owned cars safe to remove
|
||||||
|
- `wishlist.csv` — Ideal wishlist (very_best unowned, capped)
|
||||||
|
- `add.csv` — Cars to add to wishlist
|
||||||
|
- `remove.csv` — Cars to remove from wishlist
|
||||||
|
|
||||||
#### Detailed Performance Data
|
#### Detailed Performance Data
|
||||||
|
|
||||||
**Command:**
|
**Command:**
|
||||||
|
|
@ -130,7 +206,7 @@ RQ,Brand,Model,Year
|
||||||
cargo run --bin records -- details
|
cargo run --bin records -- details
|
||||||
```
|
```
|
||||||
|
|
||||||
**Purpose:** Comprehensive timing data for all tracks and conditions.
|
**Purpose:** Generates `details.csv` with comprehensive timing data for all tracks and conditions.
|
||||||
|
|
||||||
**Sample Output:**
|
**Sample Output:**
|
||||||
```csv
|
```csv
|
||||||
|
|
@ -152,7 +228,7 @@ RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time
|
||||||
cargo run --bin records -- track "Laguna Seca" "Dry" "85"
|
cargo run --bin records -- track "Laguna Seca" "Dry" "85"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Purpose:** Find the best car for a specific track, condition, and RQ level.
|
**Purpose:** Prints the best car for a specific track, condition, and RQ level to stdout.
|
||||||
|
|
||||||
**Sample Output:**
|
**Sample Output:**
|
||||||
```
|
```
|
||||||
|
|
@ -169,42 +245,53 @@ Best car for Track: laguna, Condition: Dry, RQ: 85 -> McLaren F1 (1993) with tim
|
||||||
- `Dirt` - Off-road tracks
|
- `Dirt` - Off-road tracks
|
||||||
- `Snow` - Winter conditions
|
- `Snow` - Winter conditions
|
||||||
|
|
||||||
|
### Command Flags Reference
|
||||||
|
|
||||||
|
| Flag | Applicable Commands | Default | Description |
|
||||||
|
|------|-------------------|---------|-------------|
|
||||||
|
| `--min-count N` | `best`, `wishlist`, `all` | `10` | Minimum tracks a car must be best on to qualify as "very best" |
|
||||||
|
| `--max-wishlist M` | `wishlist`, `all` | `300` | Maximum number of cars on the ideal wishlist |
|
||||||
|
|
||||||
## Daily Workflow
|
## Daily Workflow
|
||||||
|
|
||||||
The recommended daily workflow uses the `full_script` command, which runs `fetch` followed by all analysis commands in a single pipeline:
|
The recommended daily workflow uses the `all` command, which runs all analysis in a single efficient parse pass:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- full_script
|
cargo run --bin records -- fetch
|
||||||
|
cargo run --bin records -- all
|
||||||
```
|
```
|
||||||
|
|
||||||
### What `full_script` Does
|
Or combine them:
|
||||||
|
```bash
|
||||||
|
cargo run --bin records -- fetch && cargo run --bin records -- all
|
||||||
|
```
|
||||||
|
|
||||||
1. **Fetches** fresh data from the community repository
|
### What `all` Does
|
||||||
2. **Runs** all analysis commands sequentially
|
|
||||||
3. **Generates** all output CSV files
|
1. **Parses** all timing data in a single pass
|
||||||
|
2. **Generates** all 6 output CSV files
|
||||||
|
3. **Sorts** all files by car RQ
|
||||||
|
|
||||||
### Output Files
|
### Output Files
|
||||||
|
|
||||||
| File | Purpose | Format |
|
| File | Format | Description |
|
||||||
|------|---------|--------|
|
|------|--------|-------------|
|
||||||
| `best.csv` | Ranked list of best cars — evaluate if a car is worth keeping | `RQ,Brand,Model,Year,Count` |
|
| `best.csv` | `RQ,Brand,Model,Year,Count` | All cars best on at least one track |
|
||||||
| `details.csv` | Detailed timing data for all tracks/conditions — find best config for a car | `RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time` |
|
| `very_best.csv` | `RQ,Brand,Model,Year,Count` | Cars best on ≥ min_count tracks |
|
||||||
| `add.csv` | Cars to add to wishlist — missing cars not yet wishlisted, prefixed with `+` | `+RQ,Brand,Model,Year` |
|
| `trash.csv` | `RQ,Brand,Model,Year` | Owned cars not best on any track (RQ<80, not prize) |
|
||||||
| `remove.csv` | Cars to remove from wishlist — stale wishlist entries, prefixed with `-` | `-RQ,Brand,Model,Year` |
|
| `wishlist.csv` | `RQ,Brand,Model,Year,Count` | Ideal wishlist (very_best unowned, capped) |
|
||||||
| `trash.csv` | Owned cars that can be safely removed | `RQ,Brand,Model,Year` |
|
| `add.csv` | `RQ,Brand,Model,Year,Count` | Cars to add to wishlist |
|
||||||
| `sorted.csv` | Best cars sorted by count (descending) | `RQ,Brand,Model,Year,Count` |
|
| `remove.csv` | `RQ,Brand,Model,Year,Count` | Cars to remove from wishlist |
|
||||||
| `very_best.csv` | Best cars with count ≥ 10 | `RQ,Brand,Model,Year,Count` |
|
|
||||||
| `wishlist.csv` | Missing cars already on wishlist | `RQ,Brand,Model,Year,Count` |
|
|
||||||
|
|
||||||
### Typical Daily Routine
|
### Typical Daily Routine
|
||||||
|
|
||||||
1. **Run `full_script`** to get fresh data and full analysis
|
1. **Run `fetch`** to get fresh data from the community repository
|
||||||
2. **Review `best.csv`** to see how your cars rank overall
|
2. **Run `all`** to generate all analysis files in one pass
|
||||||
3. **Check `add.csv`** for cars worth adding to your wishlist (prefixed with `+`)
|
3. **Review `best.csv`** to see how your cars rank overall
|
||||||
4. **Check `remove.csv`** for wishlist entries to remove (prefixed with `-`)
|
4. **Check `add.csv`** for cars worth adding to your wishlist
|
||||||
5. **Review `trash.csv`** for owned cars you can safely sell
|
5. **Check `remove.csv`** for wishlist entries to remove
|
||||||
6. **Use `details.csv`** to find the optimal tune config for a specific car
|
6. **Review `trash.csv`** for owned cars you can safely sell
|
||||||
7. **Check `very_best.csv`** for the absolute top performers (count ≥ 10)
|
7. **Check `very_best.csv`** for the absolute top performers
|
||||||
|
|
||||||
## Advanced Usage
|
## Advanced Usage
|
||||||
|
|
||||||
|
|
@ -232,9 +319,9 @@ cargo run --bin records -- best
|
||||||
- Note RQ distribution
|
- Note RQ distribution
|
||||||
- Find gaps in coverage
|
- Find gaps in coverage
|
||||||
|
|
||||||
#### Step 2: Identify Priorities
|
#### Step 2: Identify Wishlist Priorities
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin records -- missing
|
cargo run --bin records -- wishlist
|
||||||
```
|
```
|
||||||
- Focus on high-count cars in your RQ range
|
- Focus on high-count cars in your RQ range
|
||||||
- Consider track variety needs
|
- Consider track variety needs
|
||||||
|
|
@ -271,30 +358,38 @@ Different cars excel on different track types:
|
||||||
|
|
||||||
### CSV File Usage
|
### CSV File Usage
|
||||||
|
|
||||||
The tool generates CSV files for external analysis:
|
The tool generates CSV files for external analysis. All files are sorted by car RQ.
|
||||||
|
|
||||||
#### best.csv
|
#### best.csv
|
||||||
- Your best performing cars
|
- All cars that are best on at least one track (owned and unowned)
|
||||||
- Import into spreadsheet for sorting/filtering
|
- Import into spreadsheet for sorting/filtering
|
||||||
- Track performance across time
|
- Track performance across time
|
||||||
|
|
||||||
#### details.csv
|
#### very_best.csv
|
||||||
- Comprehensive timing database
|
- Cars best on ≥ min_count tracks (default ≥10)
|
||||||
- Advanced analysis and research
|
- These are the most versatile performers
|
||||||
- Historical performance tracking
|
- Use `--min-count N` to adjust the threshold
|
||||||
|
|
||||||
#### add.csv / remove.csv
|
|
||||||
- Wishlist management
|
|
||||||
- `+` prefix indicates cars to add
|
|
||||||
- `-` prefix indicates cars to remove
|
|
||||||
|
|
||||||
#### trash.csv
|
#### trash.csv
|
||||||
- Cars safe to sell from your collection
|
- Owned cars safe to sell from your collection
|
||||||
|
- Only includes cars with RQ < 80 that are not best on any track
|
||||||
|
- Excludes prize cars
|
||||||
- Cross-reference with your garage before selling
|
- Cross-reference with your garage before selling
|
||||||
|
|
||||||
#### sorted.csv / very_best.csv
|
#### wishlist.csv
|
||||||
- Sorted rankings by versatility
|
- Ideal wishlist: very_best cars you don't own, capped at max-wishlist (default 300)
|
||||||
- `very_best.csv` filters to count ≥ 10 for top-tier recommendations
|
- Sorted by RQ
|
||||||
|
- Use as your target wishlist
|
||||||
|
|
||||||
|
#### add.csv
|
||||||
|
- Cars that should be on your wishlist but aren't
|
||||||
|
- These are very_best unowned cars not currently on your wishlist
|
||||||
|
- Add these to your in-game wishlist
|
||||||
|
|
||||||
|
#### remove.csv
|
||||||
|
- Cars currently on your wishlist that shouldn't be
|
||||||
|
- These are cars on your wishlist that don't qualify as very_best
|
||||||
|
- Remove these from your in-game wishlist
|
||||||
|
|
||||||
### Spreadsheet Analysis Tips
|
### Spreadsheet Analysis Tips
|
||||||
|
|
||||||
|
|
@ -310,7 +405,7 @@ The tool generates CSV files for external analysis:
|
||||||
|
|
||||||
#### "Please provide a command"
|
#### "Please provide a command"
|
||||||
**Problem:** No arguments provided
|
**Problem:** No arguments provided
|
||||||
**Solution:** Use `best`, `missing`, `trash`, `details`, `track`, or `full_script`
|
**Solution:** Use `best`, `trash`, `wishlist`, `all`, `details`, `track`, or `fetch`
|
||||||
|
|
||||||
#### "Could not read my cars file"
|
#### "Could not read my cars file"
|
||||||
**Problem:** `my_cars.json` not found or not readable
|
**Problem:** `my_cars.json` not found or not readable
|
||||||
|
|
@ -348,8 +443,8 @@ The tool generates CSV files for external analysis:
|
||||||
## Tips and Best Practices
|
## Tips and Best Practices
|
||||||
|
|
||||||
### Regular Maintenance
|
### Regular Maintenance
|
||||||
1. **Daily**: Run `full_script` to get latest data and full analysis
|
1. **Daily**: Run `fetch` then `all` to get latest data and full analysis
|
||||||
2. **Weekly**: Review `missing` cars for new acquisitions
|
2. **Weekly**: Review `wishlist` output for new acquisitions
|
||||||
3. **Monthly**: Use `trash` to optimize collection
|
3. **Monthly**: Use `trash` to optimize collection
|
||||||
|
|
||||||
### Data-Driven Decisions
|
### Data-Driven Decisions
|
||||||
|
|
|
||||||
92
src/lib.rs
92
src/lib.rs
|
|
@ -8,12 +8,14 @@
|
||||||
//! - **`BestCarRecord` struct** — best performing car record for a specific track/RQ/condition
|
//! - **`BestCarRecord` struct** — best performing car record for a specific track/RQ/condition
|
||||||
//! - **`TripletKey` type alias** — composite key `(track_id, rq, surface)`
|
//! - **`TripletKey` type alias** — composite key `(track_id, rq, surface)`
|
||||||
//! - **`AnalysisContext` struct** — holds mutable analysis state (replaces global static)
|
//! - **`AnalysisContext` struct** — holds mutable analysis state (replaces global static)
|
||||||
|
//! - **`CarCounts` struct** — aggregated per-car win counts
|
||||||
//! - **`MyCarsData` struct** — parsed user data from `my_cars.json`
|
//! - **`MyCarsData` struct** — parsed user data from `my_cars.json`
|
||||||
//! - **`translate_track_name()`** — internal track code → human-readable name
|
//! - **`translate_track_name()`** — internal track code → human-readable name
|
||||||
//! - **`translate_track_type()`** — condition code → surface name
|
//! - **`translate_track_type()`** — condition code → surface name
|
||||||
//! - **`get_file()`** — async file download utility
|
//! - **`get_file()`** — async file download utility
|
||||||
//! - **`load_my_cars_data()`** — loads and parses `my_cars.json`
|
//! - **`load_my_cars_data()`** — loads and parses `my_cars.json`
|
||||||
//! - **`compare_cars()`** — sorting comparator for `Car`
|
//! - **`compare_cars()`** — sorting comparator for `Car`
|
||||||
|
//! - **`compute_car_counts()`** — aggregates per-car win counts from analysis context
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
@ -64,6 +66,8 @@ pub struct BestCarRecord {
|
||||||
pub time: f64,
|
pub time: f64,
|
||||||
/// Whether the current user owns this car
|
/// Whether the current user owns this car
|
||||||
pub owned: bool,
|
pub owned: bool,
|
||||||
|
/// Whether this car is only available as a prize car
|
||||||
|
pub prize: bool,
|
||||||
/// Game-specific unique identifier for the car
|
/// Game-specific unique identifier for the car
|
||||||
pub guid: String,
|
pub guid: String,
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +88,7 @@ pub struct AnalysisContext {
|
||||||
|
|
||||||
impl AnalysisContext {
|
impl AnalysisContext {
|
||||||
/// Create a new, empty analysis context.
|
/// Create a new, empty analysis context.
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
AnalysisContext {
|
AnalysisContext {
|
||||||
best_cars: Arc::new(Mutex::new(HashMap::new())),
|
best_cars: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
|
@ -91,6 +96,53 @@ impl AnalysisContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Aggregated per-car win counts across all track/RQ/condition combinations.
|
||||||
|
/// Computed once from `AnalysisContext` and shared across all output functions.
|
||||||
|
pub struct CarCounts {
|
||||||
|
/// Map from car_rid → (BestCarRecord, count of tracks where this car is best)
|
||||||
|
pub by_rid: HashMap<String, (BestCarRecord, usize)>,
|
||||||
|
/// Map from guid → car_rid (for O(1) ownership/wishlist lookups)
|
||||||
|
pub guid_to_rid: HashMap<String, String>,
|
||||||
|
/// Map from guid → Car (for O(1) lookups of car metadata by GUID).
|
||||||
|
/// Built once in `compute_car_counts()` and reused by trash/wishlist output.
|
||||||
|
pub car_map: HashMap<String, Car>,
|
||||||
|
/// Full car list from the database (kept for any callers that need iteration).
|
||||||
|
pub car_list: Vec<Car>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes per-car win counts from the analysis context.
|
||||||
|
/// Returns a `CarCounts` struct that can be shared across all output functions.
|
||||||
|
pub fn compute_car_counts(ctx: &AnalysisContext, car_list: Vec<Car>) -> CarCounts {
|
||||||
|
let best_cars = ctx.best_cars.lock()
|
||||||
|
.expect("best_cars mutex poisoned — another thread panicked");
|
||||||
|
let mut by_rid: HashMap<String, (BestCarRecord, usize)> = HashMap::new();
|
||||||
|
let mut guid_to_rid: HashMap<String, String> = HashMap::new();
|
||||||
|
|
||||||
|
for (_key, best_list) in best_cars.iter() {
|
||||||
|
for best_car in best_list {
|
||||||
|
let entry = by_rid
|
||||||
|
.entry(best_car.car_rid.clone())
|
||||||
|
.or_insert_with(|| (best_car.clone(), 0usize));
|
||||||
|
entry.1 += 1;
|
||||||
|
// Keep the record with the best (lowest) time for this car.
|
||||||
|
if best_car.time < entry.0.time {
|
||||||
|
entry.0 = best_car.clone();
|
||||||
|
}
|
||||||
|
// HashMap iteration order is non-deterministic, but car_rid and guid
|
||||||
|
// have a 1:1 relationship in the data model, so any mapping is correct.
|
||||||
|
guid_to_rid.entry(best_car.guid.clone())
|
||||||
|
.or_insert_with(|| best_car.car_rid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build guid → Car lookup once, reused by trash/wishlist output functions.
|
||||||
|
let car_map: HashMap<String, Car> = car_list.iter()
|
||||||
|
.map(|c| (c.guid.clone(), c.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
CarCounts { by_rid, guid_to_rid, car_map, car_list }
|
||||||
|
}
|
||||||
|
|
||||||
/// Parsed user data from `my_cars.json`
|
/// Parsed user data from `my_cars.json`
|
||||||
///
|
///
|
||||||
/// Holds both the owned car GUIDs and wishlist GUIDs extracted from a single
|
/// Holds both the owned car GUIDs and wishlist GUIDs extracted from a single
|
||||||
|
|
@ -185,46 +237,22 @@ pub fn compare_cars(a: &Car, b: &Car) -> std::cmp::Ordering {
|
||||||
|
|
||||||
/// Downloads a file from a URL and saves it locally.
|
/// Downloads a file from a URL and saves it locally.
|
||||||
///
|
///
|
||||||
/// Generic utility function for fetching files from remote URLs. Used by
|
/// Returns `Ok(())` on success, or an error describing what went wrong.
|
||||||
/// both `get_cars_file()` and `get_tracks_file()` to download the latest
|
/// Removes any existing file at the local path before downloading.
|
||||||
/// database files from the repository.
|
pub async fn get_file(file_url: &str, local_file_path: &str) -> Result<(), String> {
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `file_url` - The remote URL to download from
|
|
||||||
/// * `local_file_path` - Local file path where the downloaded content should be saved
|
|
||||||
///
|
|
||||||
/// # Behavior
|
|
||||||
///
|
|
||||||
/// 1. Removes any existing file at the local path
|
|
||||||
/// 2. Downloads the file from the remote URL
|
|
||||||
/// 3. Saves the content to the local file path
|
|
||||||
/// 4. Reports any errors that occur during the process
|
|
||||||
///
|
|
||||||
/// # Error Handling
|
|
||||||
///
|
|
||||||
/// Prints error messages for network failures but does not panic, allowing
|
|
||||||
/// the application to continue with cached data if available.
|
|
||||||
pub async fn get_file(file_url: &str, local_file_path: &str) {
|
|
||||||
// Remove existing file to ensure fresh download
|
|
||||||
let _ = fs::remove_file(local_file_path);
|
let _ = fs::remove_file(local_file_path);
|
||||||
|
|
||||||
match get(file_url).await {
|
match get(file_url).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Download successful, save to local file
|
|
||||||
match response.text().await {
|
match response.text().await {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
if let Err(e) = fs::write(local_file_path, content) {
|
fs::write(local_file_path, content)
|
||||||
eprintln!("Error writing {local_file_path}: {e}");
|
.map_err(|e| format!("Error writing {local_file_path}: {e}"))
|
||||||
|
}
|
||||||
|
Err(e) => Err(format!("Error reading response from {file_url}: {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Error reading response from {file_url}: {e}"),
|
Err(error) => Err(format!("Failed to fetch {file_url}: {error}")),
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
// Download failed, print error but don't panic
|
|
||||||
eprintln!("Failed to fetch the file. Status: {}", error.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
774
src/records.rs
774
src/records.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue