refactor: extract lib.rs, remove club module, add bounded concurrency

- Extract shared types (Car, BestCarRecord, AnalysisContext) into src/lib.rs
- Replace global static BEST_CARS with injected AnalysisContext for testability
- Add semaphore-bounded concurrency (limit 20) in parse_all_times()
- Replace hardcoded track name match with track_names.json loaded via include_str!
- Replace magic number 115 with const MAX_RQ: u8 = 113
- Improve floating-point tie comparison with TIME_TOLERANCE (5ms) constant
- Remove src/club.rs binary (topdrives.club no longer maintained)
- Remove scraper dependency from Cargo.toml
- Remove tracks.txt (replaced by track_names.json)
- Remove empty/placeholder test bodies
- Improve error handling in get_file() for response body read
- Add load_my_cars_data() combining owned + wishlist extraction in one I/O
- Update CHANGELOG, README, and all docs to reflect changes
This commit is contained in:
Jan-Peter von Hunnius 2026-05-06 20:20:15 +02:00
parent 6a68469239
commit 89fa69e3c8
14 changed files with 874 additions and 2037 deletions

View file

@ -23,7 +23,7 @@ async fn main()
**Purpose**: Command-line interface and operation dispatcher
**Arguments**: Command line arguments
- `args[1]`: Command (`fetch`, `best`, `missing`, `trash`, `details`, `track`)
- `args[1]`: Command (`fetch`, `best`, `missing`, `trash`, `details`, `track`, `full_script`)
**Commands**:
- `fetch`: Download all data files
@ -32,6 +32,7 @@ async fn main()
- `trash`: Show removable cars
- `details`: Export all timing data
- `track`: Track-specific analysis
- `full_script`: Run fetch + all analysis commands
**Example**:
```bash
@ -148,7 +149,7 @@ async fn parse_all_times()
**Purpose**: Coordinates parallel processing of car timing files
**Concurrency**: One async task per car file
**Concurrency**: One async task per car file, bounded by a semaphore (limit 20)
**Synchronization**: Waits for all tasks to complete
##### `update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCarRecord)`
@ -161,12 +162,12 @@ fn update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCa
**Purpose**: Implements core best-car selection algorithm
**RQ Range Analysis**:
- Tests car from base RQ up to maximum (115)
- Tests car from base RQ up to `MAX_RQ` (113)
- Compares against existing best times
- Updates records for improved performance
**Logic**:
1. For each RQ level from car_rq to 115:
1. For each RQ level from car_rq to `MAX_RQ` (113):
2. If new time is better → replace record
3. If new time ties → add to list (if not duplicate)
4. If new time is worse → ignore
@ -205,7 +206,7 @@ async fn fetch_times()
**Purpose**: Batch download of car timing data
**Rate Limiting**: 20 concurrent downloads maximum
**Rate Limiting**: 20 concurrent downloads maximum (semaphore)
**Progress**: Shows download progress for each file
##### `fetch_car(rid: String)`
@ -258,6 +259,10 @@ async fn get_file(file_url: &str, local_file_path: &str)
- Error handling with graceful failure
- Progress indication through errors
### Shared Library (`src/lib.rs`)
Common utilities shared across the project.
##### `translate_track_name(track_code: &str) -> &str`
**Converts track codes to human-readable names**
@ -267,7 +272,9 @@ fn translate_track_name(track_code: &str) -> &str
**Purpose**: User-friendly track name display
**Coverage**: 200+ track mappings
**Implementation**: Loads mappings from `track_names.json` at compile time via `include_str!`
**Coverage**: All tracks defined in `track_names.json`
**Fallback**: Returns original code if not found
##### `translate_track_type(track_code: &str) -> &str`
@ -299,89 +306,6 @@ fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a s
split_prefix_postfix("laguna_a00", "_a") // Some(("laguna", "00"))
```
### Club Module (`src/club.rs`)
Alternative data source using web scraping from topdrives.club.
#### Public Functions
##### `main()`
**Entry point for club scraper**
```rust
#[tokio::main]
async fn main()
```
**Purpose**: Web scraping interface for live data
**Arguments**:
- Command line arguments for RQ and track filtering
- `-d` flag for detailed output
##### `get_page(url: &str) -> String`
**Fetches HTML content from URLs**
```rust
async fn get_page(url: &str) -> String
```
**Purpose**: Downloads web pages for scraping
**Security**: Accepts invalid SSL certificates for compatibility
##### `parse_best_car(text: String) -> Car`
**Extracts car data from HTML content**
```rust
fn parse_best_car(text: String) -> Car
```
**Purpose**: Parses HTML tables to extract car information
**CSS Selectors**:
- `.carList__item-manufacturer`: Car brand
- `.carList__item-model`: Car model
- `.carList__item-year`: Model year
- `.carList__item-level`: Class and RQ (complex parsing)
- `.carList__item-upgrade`: Upgrade configuration
##### `parse_tracks(file_name: &str) -> HashMap<u16, String>`
**Loads track definitions from file**
```rust
fn parse_tracks(file_name: &str) -> HashMap<u16, String>
```
**Purpose**: Creates track ID to name mappings
**Format**: Text file with `ID NAME` format per line
##### `get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String`
**Scrapes best car data for specific track**
```rust
async fn get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String
```
**Purpose**: Fetches and parses best car from topdrives.club
**API Integration**:
- Constructs topdrives.club API URLs
- Parses HTML response tables
- Formats output based on detail level
##### `parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16`
**Validates command line arguments**
```rust
fn parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16
```
**Purpose**: Input validation with range checking
**Panics**: On invalid values with descriptive messages
## Data Structures
### Records Module Data Types
@ -436,22 +360,15 @@ type TripletKey = (String, u8, String);
// Track ID RQ Level Surface Condition
```
### Club Module Data Types
#### `Car`
**Scraped car data from website**
### Constants
#### `MAX_RQ`
```rust
struct Car {
class: String, // Class letter (S, A, B, etc.)
rq: u8, // Rarity level
manufacturer: String, // Brand name
model: String, // Model name
year: u16, // Model year
upgrade: String, // Upgrade config
}
const MAX_RQ: u8 = 113;
```
**Purpose**: Maximum RQ level for range analysis. Replaces former magic number 115 with a named constant.
## Global State
### `BEST_CARS` Cache
@ -533,7 +450,7 @@ fetch_times().await;
- **Per Car**: ~1-2KB metadata + timing data
### Concurrency
- **Downloads**: 20 concurrent connections max
- **Downloads**: 20 concurrent connections max (semaphore bounded)
- **Processing**: One task per car file
- **Thread Safety**: Mutex-protected shared state
@ -544,41 +461,16 @@ fetch_times().await;
2. Implement analysis function
3. Update help documentation
### Adding New Data Sources
1. Implement new module (similar to `club.rs`)
2. Add data source selection logic
3. Update configuration system
### Adding New Tracks
1. Update `tracks_repo.json` with new track definitions
2. Add track name mapping to `track_names.json`
3. Track names are compiled in at build time
### Custom Analysis Types
1. Create new output formatting functions
2. Add aggregation algorithms
3. Extend data structures as needed
## Testing
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_parse_car_times() {
// Test implementation
}
}
```
### Integration Tests
- Full workflow testing
- File I/O validation
- Network request mocking
### Benchmarking
- Parse performance measurement
- Memory usage profiling
- Concurrent operation testing
## Security Considerations
### Network Security
@ -643,12 +535,14 @@ cargo flamegraph --bin records -- best
- **Future**: Semantic versioning maintained
### Breaking Changes
- None currently planned
- Advance notice provided for API changes
- Deprecation warnings for removed features
- Club module (`src/club.rs`) has been removed
- `scraper` dependency removed
- `tracks.txt` removed (replaced by `track_names.json`)
- Magic number 115 replaced with `const MAX_RQ: u8 = 113`
- `translate_track_name()` now loads from `track_names.json` instead of hardcoded match
---
**API Version**: 0.2.0
**Last Updated**: 2024-12-XX
**Compatibility**: Rust 1.70.0+
**Last Updated**: 2026-05-06
**Compatibility**: Rust 1.70.0+

View file

@ -2,7 +2,7 @@
## System Overview
The Top Drives Records Analyzer consists of two main components working together to provide comprehensive car performance analysis for the Top Drives racing game.
The Top Drives Records Analyzer consists of a main analysis binary and a shared library working together to provide comprehensive car performance analysis for the Top Drives racing game.
## Component Architecture
@ -16,6 +16,9 @@ The Top Drives Records Analyzer consists of two main components working together
- **Performance Analysis**: Compares timing data across RQ ranges and track conditions
- **Collection Management**: Identifies owned cars and optimization opportunities
#### Key Constants
- `MAX_RQ: u8 = 113` — Maximum RQ level used for range analysis (replaces former magic number 115)
#### Data Flow
```
@ -32,28 +35,17 @@ User Collection (my_cars.json)
Output: CSV/Terminal Results
```
### 2. Club Module (`src/club.rs`)
### 2. Shared Library (`src/lib.rs`)
**Alternative data source** that scrapes live data from topdrives.club website.
**Common utilities** shared across the project.
#### Key Responsibilities
- **Web Scraping**: Fetches HTML content from topdrives.club
- **HTML Parsing**: Extracts car performance data using CSS selectors
- **Real-time Data**: Provides fresh data without pre-downloaded files
- **Track Name Translation**: Converts track codes to human-readable names via `translate_track_name()`
- **Track Type Translation**: Converts condition codes to surface descriptions via `translate_track_type()`
#### Data Flow
```
topdrives.club Website
↓
HTML Content
↓
CSS Selector Parsing
↓
Car Data Extraction
↓
Output: Terminal Results
```
#### Configuration
- Track name mappings are loaded from `track_names.json` at compile time using `include_str!`
- This external config file maps track codes (e.g., `"laguna"`) to human-readable names (e.g., `"Laguna Seca"`)
## Data Structures
@ -69,7 +61,7 @@ type TripletKey = (String, u8, String);
**Purpose**: Composite key for uniquely identifying track conditions
**Usage**: HashMap key for storing best car records
#### Car (Records Module)
#### Car
```rust
struct Car {
rid: String, // Repository ID
@ -97,18 +89,6 @@ struct BestCarRecord {
}
```
#### Car (Club Module)
```rust
struct Car {
class: String, // Class letter (S, A, B, etc.)
rq: u8, // Rarity level
manufacturer: String, // Brand
model: String, // Model
year: u16, // Year
upgrade: String, // Upgrade config
}
```
## Global State Management
### BEST_CARS Cache
@ -127,7 +107,7 @@ static BEST_CARS: Lazy<Mutex<HashMap<TripletKey, Vec<BestCarRecord>>>>
The system analyzes car performance across multiple RQ brackets:
1. **Base RQ**: Car's natural rarity level
2. **RQ Range**: Tests from car's RQ up to maximum (115)
2. **RQ Range**: Tests from car's RQ up to `MAX_RQ` (113)
3. **Cross-RQ Competition**: Allows cars to compete above their base level
**Example**: A RQ 85 car might perform well at RQ 90-95 levels against higher-rated cars
@ -142,9 +122,9 @@ Multiple validation layers ensure data quality:
### Concurrent Processing
#### Records Module
#### Bounded Concurrency Pattern
- **Task Spawning**: One async task per car file
- **Rate Limiting**: Semaphore limits concurrent downloads (20 max)
- **Semaphore**: Limits concurrent operations to 20 (bounded concurrency)
- **Error Isolation**: Individual car failures don't stop batch processing
#### Synchronization
@ -157,6 +137,7 @@ Multiple validation layers ensure data quality:
```
├── cars_repo.json # Car database from repository
├── tracks_repo.json # Track definitions
├── track_names.json # Track code → name mappings (compiled in)
├── my_cars.json # User's car collection (game export)
└── times/ # Individual car timing files
├── Ferrari_F40_1987.json
@ -167,8 +148,13 @@ Multiple validation layers ensure data quality:
### Output Files
```
├── best.csv # Best cars analysis results
├── missing.csv # Missing recommended cars
└── details.csv # Detailed timing data
├── details.csv # Detailed timing data
├── add.csv # Cars to add to wishlist
├── remove.csv # Cars to remove from wishlist
├── trash.csv # Owned cars safe to remove
├── sorted.csv # Best cars sorted by count
├── very_best.csv # Best cars with count ≥ 10
└── wishlist.csv # Missing cars already on wishlist
```
## Performance Characteristics
@ -178,12 +164,7 @@ Multiple validation layers ensure data quality:
- **Memory Usage**: Moderate (loads all timing data into memory)
- **Accuracy**: High (comprehensive dataset)
- **Update Frequency**: Manual refresh required
### Club Module
- **Startup Time**: Slow (web scraping required)
- **Memory Usage**: Low (processes one track at a time)
- **Accuracy**: Variable (depends on website structure)
- **Update Frequency**: Real-time on each run
- **Concurrency**: Bounded at 20 simultaneous operations via semaphore
## Error Handling Strategy
@ -195,13 +176,12 @@ Multiple validation layers ensure data quality:
### User Feedback
- **Progress Indication**: Shows fetching/download progress
- **Error Messages**: Clear descriptions of what went wrong
- **Fallback Options**: Multiple data sources available
## Extension Points
### Adding New Tracks
1. Update `tracks_repo.json` with new track definitions
2. Add track name mappings in `translate_track_name()`
2. Add track name mapping in `track_names.json`
3. Add condition mappings in `translate_track_type()`
### Adding New Analysis Types
@ -209,17 +189,12 @@ Multiple validation layers ensure data quality:
2. Add command parsing in main function
3. Update help text and documentation
### Custom Data Sources
1. Implement new scraping module (similar to club.rs)
2. Add data source selection logic
3. Update configuration system
## Security Considerations
### Network Access
- **HTTPS Only**: All external requests use HTTPS
- **Certificate Validation**: Accepts invalid certificates for compatibility
- **Rate Limiting**: Respects server limits with controlled concurrency
- **Rate Limiting**: Respects server limits with controlled concurrency (semaphore limit of 20)
### Local File Access
- **Read Only**: No modification of game files
@ -229,7 +204,7 @@ Multiple validation layers ensure data quality:
## Future Enhancements
### Potential Improvements
1. **Caching Layer**: Cache web scraping results
1. **Caching Layer**: Cache parsed results between runs
2. **Database Backend**: Replace in-memory HashMap with persistent storage
3. **Configuration Files**: External configuration for analysis parameters
4. **GUI Interface**: Web-based interface for non-technical users
@ -239,4 +214,4 @@ Multiple validation layers ensure data quality:
1. **Pagination**: Handle large datasets with pagination
2. **Incremental Updates**: Only fetch changed data
3. **Compression**: Compress large timing files
4. **Streaming**: Process data without loading everything into memory
4. **Streaming**: Process data without loading everything into memory

View file

@ -175,6 +175,48 @@ This document describes the JSON data structures used by the Top Drives Records
- Used for track-specific analysis queries
- Supports condition filtering
### 5. Track Names Configuration (`track_names.json`)
**Purpose**: Maps internal track codes to human-readable track names.
**Source**: Included in the repository, loaded at compile time via `include_str!`
**Structure**:
```json
{
"laguna": "Laguna Seca",
"monaco": "Monaco Streets",
"testBowl": "Test Bowl",
"dealsGap": "Deals Gap"
}
```
**Field Descriptions**:
| Field | Type | Description |
|-------|------|-------------|
| *key* | String | Internal track code (e.g., `"laguna"`) |
| *value* | String | Human-readable track name (e.g., `"Laguna Seca"`) |
**Usage in Application**:
- Loaded by `translate_track_name()` in `src/lib.rs`
- Compiled into the binary at build time
- Fallback: Returns original track code if no mapping found
## Application Constants
### `MAX_RQ`
```rust
const MAX_RQ: u8 = 113;
```
**Purpose**: Defines the maximum RQ level used in analysis range calculations.
**Usage**: When evaluating car performance, each car is tested from its base RQ up to `MAX_RQ`. This replaces the former magic number 115, providing a named constant for clarity and easy adjustment.
**Location**: Defined in `src/records.rs`
## Data Processing Pipeline
### 1. Data Acquisition Phase
@ -226,6 +268,11 @@ if let Some(configurations) = json_data["data"].as_object() {
- Process: Split on `"_a"` delimiter
- Output: `("laguna", "00")` (track name, condition code)
**Track Name Translation:**
- Track codes are looked up in `track_names.json` (compiled into binary)
- Example: `"laguna"` → `"Laguna Seca"`
- Fallback: Unknown codes returned as-is
**Condition Code Translation:**
- `"00"` → `"Dry"`
- `"01"` → `"Wet"`
@ -255,7 +302,7 @@ BestCarRecord {
```
**RQ Range Analysis:**
- Test each car from its base RQ up to 115
- Test each car from its base RQ up to `MAX_RQ` (113)
- Compare against existing best times
- Update records if better performance found
@ -303,7 +350,7 @@ BestCarRecord {
- User collection: Small, loaded once
**Optimization Strategies**:
- Concurrent processing of car files
- Concurrent processing of car files with bounded concurrency (semaphore limit of 20)
- Rate-limited downloads (20 concurrent max)
- Streaming JSON parsing where possible
@ -315,7 +362,7 @@ BestCarRecord {
3. **HashMap Operations**: Frequent insertions with RQ range checking
**Optimizations Applied**:
1. **Concurrency**: One task per car file
1. **Bounded Concurrency**: Semaphore limits concurrent operations to 20
2. **Connection Pooling**: Reuse HTTP connections
3. **Early Filtering**: Invalid times rejected quickly
@ -361,8 +408,8 @@ BestCarRecord {
**Track Name Issues**:
- **Symptom**: Unknown track codes in output
- **Cause**: Outdated track definitions
- **Solution**: Update `tracks_repo.json`
- **Cause**: Missing mapping in `track_names.json`
- **Solution**: Add new mapping to `track_names.json`
### Data Validation Tools
@ -404,4 +451,4 @@ grep -r "No RID" times/
- Historical performance trends
- Meta change impact analysis
- Community submission quality metrics
- Regional performance differences
- Regional performance differences

View file

@ -168,6 +168,7 @@ serde_json = "1.0.140"
- Parse `my_cars.json` (user collection)
- Parse individual car timing files
- Parse `tracks_repo.json` (track definitions)
- Parse `track_names.json` (track name mappings)
**Performance**: Zero-copy parsing where possible
@ -205,26 +206,9 @@ once_cell = "1.20.2"
- Thread-safe access during concurrent parsing
- Avoid expensive computation on every access
#### 6. `scraper` - HTML Parsing (Club Module Only)
```toml
scraper = "0.23.1"
```
**Purpose**: HTML parsing and CSS selector engine for web scraping
**Features Used**:
- CSS selector parsing with `Selector::parse()`
- HTML document parsing with `Html::parse_document()`
- Element selection and text extraction
**Why Needed**:
- Alternative data source for club.rs module
- Parse car data from topdrives.club HTML tables
- Extract performance information from web pages
### Development Dependencies
#### 7. Build Tools and Quality
#### 6. Build Tools and Quality
**Recommended additions for development:**
```toml
@ -289,9 +273,9 @@ name = "top_drives"
version = "0.2.0"
edition = "2021"
[[bin]]
name = "club"
path = "src/club.rs"
[lib]
name = "top_drives"
path = "src/lib.rs"
[[bin]]
name = "records"
@ -300,7 +284,6 @@ path = "src/records.rs"
**Binary Configuration:**
- **records**: Main analysis tool (JSON-based)
- **club**: Alternative scraper (web-based)
**Dependency Versions:**
- All dependencies pinned to specific versions for reproducibility
@ -342,7 +325,7 @@ RUST_BACKTRACE=1 cargo run
```
**Concurrency Tuning:**
- Default: 20 concurrent downloads (configurable via Semaphore)
- Default: 20 concurrent operations (bounded via Semaphore)
- Thread count: Automatic based on CPU cores
- Memory usage: ~100-200MB for full dataset
@ -471,7 +454,6 @@ cargo build --release
**Binary Location:**
```
target/release/records # Main analyzer
target/release/club # Club scraper
```
**Cross-Platform Building:**
@ -529,4 +511,4 @@ cargo profdata # Profile-guided optimization
- **Build Tool**: Cargo (included with Rust)
- **Editor**: VS Code with rust-analyzer (recommended)
- **Testing**: Built-in Rust testing framework
- **Debugging**: GDB/LLDB integration available
- **Debugging**: GDB/LLDB integration available

View file

@ -160,8 +160,8 @@ Best car for Track: laguna, Condition: Dry, RQ: 85 -> McLaren F1 (1993) with tim
```
**Track name format:**
- Use internal names: `laguna`, `monaco`, `testBowl`
- Full mappings in `translate_track_name()` function
- Use display names: `Laguna Seca`, `Monaco`, `Test Bowl`
- Full mappings loaded from `track_names.json`
**Condition options:**
- `Dry` - Standard asphalt
@ -169,6 +169,43 @@ Best car for Track: laguna, Condition: Dry, RQ: 85 -> McLaren F1 (1993) with tim
- `Dirt` - Off-road tracks
- `Snow` - Winter conditions
## Daily Workflow
The recommended daily workflow uses the `full_script` command, which runs `fetch` followed by all analysis commands in a single pipeline:
```bash
cargo run --bin records -- full_script
```
### What `full_script` Does
1. **Fetches** fresh data from the community repository
2. **Runs** all analysis commands sequentially
3. **Generates** all output CSV files
### Output Files
| File | Purpose | Format |
|------|---------|--------|
| `best.csv` | Ranked list of best cars — evaluate if a car is worth keeping | `RQ,Brand,Model,Year,Count` |
| `details.csv` | Detailed timing data for all tracks/conditions — find best config for a car | `RQ,TrackID,Condition,CarRQ,Brand,Model,Year,Config,Time` |
| `add.csv` | Cars to add to wishlist — missing cars not yet wishlisted, prefixed with `+` | `+RQ,Brand,Model,Year` |
| `remove.csv` | Cars to remove from wishlist — stale wishlist entries, prefixed with `-` | `-RQ,Brand,Model,Year` |
| `trash.csv` | Owned cars that can be safely removed | `RQ,Brand,Model,Year` |
| `sorted.csv` | Best cars sorted by count (descending) | `RQ,Brand,Model,Year,Count` |
| `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
1. **Run `full_script`** to get fresh data and full analysis
2. **Review `best.csv`** to see how your cars rank overall
3. **Check `add.csv`** for cars worth adding to your wishlist (prefixed with `+`)
4. **Check `remove.csv`** for wishlist entries to remove (prefixed with `-`)
5. **Review `trash.csv`** for owned cars you can safely sell
6. **Use `details.csv`** to find the optimal tune config for a specific car
7. **Check `very_best.csv`** for the absolute top performers (count ≥ 10)
## Advanced Usage
### RQ Range Analysis
@ -241,16 +278,24 @@ The tool generates CSV files for external analysis:
- Import into spreadsheet for sorting/filtering
- Track performance across time
#### missing.csv
- Cars to acquire
- Prioritize by count and RQ relevance
- Plan acquisition strategy
#### details.csv
- Comprehensive timing database
- Advanced analysis and research
- Historical performance tracking
#### add.csv / remove.csv
- Wishlist management
- `+` prefix indicates cars to add
- `-` prefix indicates cars to remove
#### trash.csv
- Cars safe to sell from your collection
- Cross-reference with your garage before selling
#### sorted.csv / very_best.csv
- Sorted rankings by versatility
- `very_best.csv` filters to count ≥ 10 for top-tier recommendations
### Spreadsheet Analysis Tips
**Excel/Google Sheets setup:**
@ -265,7 +310,7 @@ The tool generates CSV files for external analysis:
#### "Please provide a command"
**Problem:** No arguments provided
**Solution:** Use `best`, `missing`, `trash`, `details`, or `track`
**Solution:** Use `best`, `missing`, `trash`, `details`, `track`, or `full_script`
#### "Could not read my cars file"
**Problem:** `my_cars.json` not found or not readable
@ -303,9 +348,9 @@ The tool generates CSV files for external analysis:
## Tips and Best Practices
### Regular Maintenance
1. **Weekly**: Run `fetch` to get latest data
2. **Monthly**: Review `missing` cars for new acquisitions
3. **Quarterly**: Use `trash` to optimize collection
1. **Daily**: Run `full_script` to get latest data and full analysis
2. **Weekly**: Review `missing` cars for new acquisitions
3. **Monthly**: Use `trash` to optimize collection
### Data-Driven Decisions
1. **Trust the numbers**: Count-based recommendations are data-driven
@ -331,4 +376,4 @@ The tool generates CSV files for external analysis:
---
**Remember**: This tool provides data-driven insights, but ultimately you're the best judge of which cars you enjoy driving. Use the analysis as a guide, not a strict rulebook.
**Remember**: This tool provides data-driven insights, but ultimately you're the best judge of which cars you enjoy driving. Use the analysis as a guide, not a strict rulebook.