Possible working ETL

This commit is contained in:
Eric Gullickson
2025-12-15 18:19:55 -06:00
parent 1fc69b7779
commit 1e599e334f
110 changed files with 4843 additions and 2078706 deletions

View File

@@ -1,275 +0,0 @@
# Automotive Vehicle Selection Database - ETL Documentation
## Overview
This ETL pipeline creates a PostgreSQL database optimized for cascading dropdown vehicle selection:
**Year → Make → Model → Trim → Engine/Transmission**
## Database Schema
### Tables
1. **engines** - Simplified engine specifications
- id (Primary Key)
- name (Display format: "V8 3.5L", "L4 2.0L Turbo", "V6 6.2L Supercharged")
2. **transmissions** - Simplified transmission specifications
- id (Primary Key)
- type (Display format: "8-Speed Automatic", "6-Speed Manual", "CVT")
3. **vehicle_options** - Denormalized vehicle configurations
- Year, Make (Title Case: "Ford", "Acura", "Land Rover"), Model, Trim
- Foreign keys to engines and transmissions
- Optimized indexes for dropdown queries
### Views
- `available_years` - All distinct years
- `makes_by_year` - Makes grouped by year
- `models_by_year_make` - Models grouped by year/make
- `trims_by_year_make_model` - Trims grouped by year/make/model
- `complete_vehicle_configs` - Full vehicle details with engine/transmission
### Functions
- `get_makes_for_year(year)` - Returns makes for a specific year
- `get_models_for_year_make(year, make)` - Returns models for year/make
- `get_trims_for_year_make_model(year, make, model)` - Returns trims
- `get_options_for_vehicle(year, make, model, trim)` - Returns engine/transmission options
## Data Sources
### Primary Source
**makes-filter/*.json** (57 makes)
- Filtered manufacturer data
- Year/model/trim/engine hierarchy
- Engine specs as simple strings (e.g., "2.0L I4")
### Detailed Specs
**engines.json** (30,066+ records)
- Complete engine specifications
- Performance data, fuel economy
- Transmission details
**automobiles.json** (7,207 models)
- Model descriptions
- Used for hybrid backfill of recent years (2023-2025)
**brands.json** (124 brands)
- Brand metadata
- Used for brand name mapping
## ETL Process
### Step 1: Load Source Data
- Load `engines.json` (30,066 records)
- Load `brands.json` (124 brands)
- Load `automobiles.json` (7,207 models)
- Load all `makes-filter/*.json` files (55 files)
### Step 2: Transform Brand Names
- Convert ALL CAPS brand names to Title Case ("FORD" → "Ford")
- Preserve acronyms (BMW, GMC, KIA remain uppercase)
- Handle special cases (DeLorean, McLaren)
### Step 3: Process Engine Specifications
- Extract engine specs from engines.json
- Create simplified display names (e.g., "V8 3.5L Turbo")
- Normalize displacement (Cm3 → Liters) for matching
- Build engine cache with (displacement, configuration) keys
- Generate engines SQL with only id and name columns
### Step 4: Process Transmission Specifications
- Extract transmission specs from engines.json
- Create simplified display names (e.g., "8-Speed Automatic")
- Parse speed count and transmission type
- Build transmission cache for linking
- Generate transmissions SQL with only id and type columns
### Step 5: Process Makes-Filter Data
- Read all JSON files from `makes-filter/`
- Extract year/make/model/trim/engine combinations
- Match engine strings to detailed specs using displacement + configuration
- Link transmissions to vehicle records (98.9% success rate)
- Apply year filter (1980 and newer only)
- Build vehicle_options records
### Step 6: Hybrid Backfill
- Check `automobiles.json` for recent years (2023-2025)
- Add any missing year/make/model combinations
- Only backfill for filtered makes
- Link transmissions for backfilled records
- Limit to 3 engines per backfilled model
### Step 7: Generate SQL Output
- Write SQL files with proper escaping (newlines, quotes, special characters)
- Convert empty strings to NULL for data integrity
- Use batched inserts (1000 records per batch)
- Output to `output/` directory
## Running the ETL
### Prerequisites
- Docker container `mvp-postgres` running
- Python 3 (no additional dependencies required)
- JSON source files in project root
### Quick Start
```bash
# Step 1: Generate SQL files from JSON data
python3 etl_generate_sql.py
# Step 2: Import SQL files into database
./import_data.sh
```
### What Gets Generated
- `output/01_engines.sql` (~632KB, 30,066 records)
- `output/02_transmissions.sql` (~21KB, 828 records)
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
## Query Examples
### Get all available years
```sql
SELECT * FROM available_years;
```
### Get makes for 2024
```sql
SELECT * FROM get_makes_for_year(2024);
```
### Get models for 2025 Ford
```sql
SELECT * FROM get_models_for_year_make(2025, 'Ford');
```
### Get trims for 2025 Ford F-150
```sql
SELECT * FROM get_trims_for_year_make_model(2025, 'Ford', 'f-150');
```
### Get engine/transmission options for specific vehicle
```sql
SELECT * FROM get_options_for_vehicle(2025, 'Ford', 'f-150', 'XLT');
```
### Complete vehicle configurations
```sql
SELECT * FROM complete_vehicle_configs
WHERE year = 2025 AND make = 'Ford' AND model = 'f-150'
LIMIT 10;
```
## Performance Optimization
### Indexes Created
- `idx_vehicle_year` - Single column index on year
- `idx_vehicle_make` - Single column index on make
- `idx_vehicle_model` - Single column index on model
- `idx_vehicle_year_make` - Composite index for year/make queries
- `idx_vehicle_year_make_model` - Composite index for year/make/model queries
- `idx_vehicle_year_make_model_trim` - Composite index for full cascade
### Query Performance
Dropdown queries are optimized to return results in < 50ms for typical datasets.
## Data Matching Logic
### Brand Name Transformation
- Source data (brands.json) stores names in ALL CAPS: "FORD", "ACURA", "ALFA ROMEO"
- ETL converts to Title Case: "Ford", "Acura", "Alfa Romeo"
- Preserves acronyms: BMW, GMC, KIA, MINI, FIAT, RAM
- Special cases: DeLorean, McLaren
### Engine Matching
The ETL uses intelligent pattern matching to link simple engine strings from makes-filter to detailed specs:
1. **Parse engine string**: Extract displacement (e.g., "2.0L") and configuration (e.g., "I4")
2. **Normalize displacement**: Convert Cm3 to Liters ("3506 Cm3" → "3.5L")
3. **Match to cache**: Look up in engine cache by (displacement, configuration)
4. **Create display name**: Format as "V8 3.5L", "L4 2.0L Turbo", etc.
### Transmission Linking
- Transmission data is embedded in engines.json under "Transmission Specs"
- Each engine record includes gearbox type (e.g., "6-Speed Manual")
- ETL links transmissions to vehicle records based on engine match
- Success rate: 98.9% (1,109,510 of 1,122,644 records)
- Unlinked records: primarily electric vehicles without traditional transmissions
### Configuration Equivalents
- `I4` = `L4` = `INLINE-4` = `4 Inline`
- `V6` = `V-6`
- `V8` = `V-8`
## Filtered Makes (53 Total)
All brand names are stored in Title Case format for user-friendly display.
### American Brands (12)
Acura, Buick, Cadillac, Chevrolet, Chrysler, Dodge, Ford, GMC, Hummer, Jeep, Lincoln, RAM
### Luxury/Performance (13)
Aston Martin, Bentley, Ferrari, Lamborghini, Maserati, McLaren, Porsche, Rolls Royce, Tesla, Jaguar, Audi, BMW, Land Rover
### Japanese (8)
Honda, Infiniti, Lexus, Mazda, Mitsubishi, Nissan, Subaru, Toyota
### European (9)
Alfa Romeo, FIAT, MINI, Saab, Saturn, Scion, Smart, Volkswagen, Volvo
### Other (11)
Genesis, Geo, Hyundai, KIA, Lucid, Polestar, Rivian, Lotus, Mercury, Oldsmobile, Plymouth, Pontiac
## Troubleshooting
### Container Not Running
```bash
docker compose up -d
docker compose ps
```
### Database Connection Issues
Check connection parameters in `etl_vehicle_data.py`:
```python
DB_CONFIG = {
'host': 'localhost',
'database': 'motovaultpro',
'user': 'postgres',
'password': 'postgres',
'port': 5432
}
```
### Missing JSON Files
Ensure these files exist in project root:
- `engines.json`
- `automobiles.json`
- `brands.json`
- `makes-filter/*.json` (57 files)
### Python Dependencies
```bash
pip3 install psycopg2-binary
```
## Expected Results
After successful ETL:
- **Engines**: 30,066 records
- **Transmissions**: 828 records
- **Vehicle Options**: 1,122,644 configurations
- **Years**: 47 years (1980-2026)
- **Makes**: 53 manufacturers
- **Models**: 1,741 unique models
- **Transmission Linking**: 98.9% success rate
- **Output Files**: ~52MB total (632KB engines + 21KB transmissions + 51MB vehicles)
## Next Steps
1. Create API endpoints for dropdown queries
2. Add caching layer for frequently accessed queries
3. Implement full-text search for models
4. Add vehicle images and detailed specs display
5. Create admin interface for data management

View File

@@ -1,168 +0,0 @@
# Database Update: 1980+ Year Filter Applied
## Summary
The database has been successfully updated to exclude vehicles older than 1980.
---
## Changes Applied
### Before Filter
- **Total Vehicles:** 1,213,401
- **Year Range:** 1918-2026 (93 years)
- **Database Size:** 219MB
### After Filter (1980+)
- **Total Vehicles:** 1,122,644
- **Year Range:** 1980-2026 (47 years)
- **Records Filtered:** 90,757 vehicles removed
- **Reduction:** 7.5%
---
## Validation Results
**Year Range Verified:**
- Earliest Year: 1980
- Latest Year: 2026
- Total Years: 47
**No Pre-1980 Vehicles:**
- Vehicles before 1980: 0
**Data Integrity:**
- Engines: 30,066
- Makes: 53
- Models: 1,741
- All dropdown functions working correctly
---
## Technical Implementation
### 1. ETL Script Modified (`etl_generate_sql.py`)
Added year filter constant:
```python
# Year filter - only include vehicles 1980 or newer
self.min_year = 1980
```
Applied filter in two locations:
1. **process_makes_filter()** - Filters records from makes-filter JSON files
2. **hybrid_backfill()** - Ensures backfilled records also respect the filter
### 2. SQL Files Regenerated
- `output/01_engines.sql` - ~632KB (simplified to id and name only)
- `output/02_transmissions.sql` - ~21KB (new file, id and type only)
- `output/03_vehicle_options.sql` - ~51MB (reduced from 56MB)
- Total batches: 1,123 (reduced from 1,214)
### 3. Data Transformation
**Engine Names:** Simplified to user-friendly display format
- Example: "V8 3.5L Turbo", "L4 2.0L", "V6 6.2L Supercharged"
**Transmission Types:** Simplified to user-friendly display format
- Example: "8-Speed Automatic", "6-Speed Manual", "CVT"
**Brand Names:** Converted from ALL CAPS to Title Case
- Example: "FORD" → "Ford", "LAND ROVER" → "Land Rover"
- Acronyms preserved: BMW, GMC, KIA, MINI, FIAT, RAM
### 4. Database Re-imported
Successfully imported filtered data with zero pre-1980 vehicles.
---
## How to Change the Year Filter
To use a different year cutoff (e.g., 1990, 2000), edit `etl_generate_sql.py`:
```python
class VehicleSQLGenerator:
def __init__(self):
# Change this value to your desired minimum year
self.min_year = 1990 # Example: filter to 1990+
```
Then regenerate and re-import:
```bash
python3 etl_generate_sql.py
./import_data.sh
```
---
## Database Statistics (1980+)
| Metric | Count |
|--------|-------|
| **Engines** | 30,066 |
| **Transmissions** | 828 |
| **Vehicle Options** | 1,122,644 |
| **Years** | 47 (1980-2026) |
| **Makes** | 53 |
| **Models** | 1,741 |
| **Transmission Linking** | 98.9% success |
| **Database Size** | ~250MB |
---
## Available Years
Years available in database: 1980, 1981, 1982, ..., 2024, 2025, 2026
Total: 47 consecutive years
---
## Impact on Dropdown Queries
All dropdown cascade queries remain fully functional:
```sql
-- Get years (now starts at 1980)
SELECT * FROM available_years;
-- Get makes for 1980
SELECT * FROM get_makes_for_year(1980);
-- Get makes for 2025
SELECT * FROM get_makes_for_year(2025);
```
No changes required to API or query logic.
---
## Files Updated
| File | Change |
|------|--------|
| `etl_generate_sql.py` | Added min_year filter, brand name transformation, simplified display formats |
| `output/01_engines.sql` | Regenerated with simplified format (id, name only) |
| `output/02_transmissions.sql` | New file with transmission data (id, type only) |
| `output/03_vehicle_options.sql` | Regenerated (90K fewer records, transmission linking added) |
| Database `engines` table | Re-imported with simplified schema |
| Database `transmissions` table | Newly created and populated |
| Database `vehicle_options` table | Re-imported with 1980+ filter and transmission links |
---
## Next Steps
The database is **ready for use** with the 1980+ filter applied.
If you need to:
- **Change the year filter:** Edit `min_year` in `etl_generate_sql.py` and re-run
- **Restore all years:** Set `min_year = 0` and re-run
- **Add more filters:** Modify the filter logic in `process_makes_filter()` method
---
*Filter applied: 2025-11-10*
*Minimum year: 1980*

View File

@@ -1,285 +0,0 @@
# Automotive Vehicle Selection Database - Implementation Summary
## Status: ✅ COMPLETED & OPTIMIZED
The ETL pipeline has been successfully implemented, optimized, and executed. The database is now populated with clean, user-friendly data ready for production use.
---
## Database Statistics
| Metric | Count |
|--------|-------|
| **Engines** | 30,066 |
| **Transmissions** | 828 |
| **Vehicle Options** | 1,122,644 |
| **Years** | 47 (1980-2026) |
| **Makes** | 53 |
| **Models** | 1,741 |
### Data Quality Metrics
- **Transmission Linking Success**: 98.9% (1,109,510 of 1,122,644 records)
- **Records with NULL Engine/Transmission**: 1.1% (11,951 records - primarily electric vehicles)
- **Year Filter Applied**: 1980 and newer only
---
## What Was Implemented
### 1. Database Schema (`migrations/001_create_vehicle_database.sql`)
**Tables:**
- `engines` - Simplified engine specifications (id, name)
- Names formatted as: "V8 3.5L", "L4 2.0L Turbo", "V6 6.2L Supercharged"
- `transmissions` - Simplified transmission specifications (id, type)
- Types formatted as: "8-Speed Automatic", "6-Speed Manual", "CVT"
- `vehicle_options` - Denormalized table optimized for dropdown queries (year, make, model, trim, engine_id, transmission_id)
- Make names in Title Case: "Acura", "Ford", "BMW" (not ALL CAPS)
**Views:**
- `available_years` - All distinct years
- `makes_by_year` - Makes grouped by year
- `models_by_year_make` - Models grouped by year/make
- `trims_by_year_make_model` - Trims grouped by year/make/model
- `complete_vehicle_configs` - Full vehicle details with engine info
**Functions:**
- `get_makes_for_year(year)` - Returns available makes for a specific year
- `get_models_for_year_make(year, make)` - Returns models for year/make combination
- `get_trims_for_year_make_model(year, make, model)` - Returns trims for specific vehicle
- `get_options_for_vehicle(year, make, model, trim)` - Returns engine/transmission options
**Indexes:**
- Single column indexes on year, make, model, trim
- Composite indexes for optimal cascade query performance:
- `idx_vehicle_year_make`
- `idx_vehicle_year_make_model`
- `idx_vehicle_year_make_model_trim`
### 2. ETL Script (`etl_generate_sql.py`)
A Python script that processes JSON source files and generates SQL import files:
**Data Sources Processed:**
- `engines.json` (30,066 records) - Detailed engine specifications
- `automobiles.json` (7,207 records) - Vehicle models
- `brands.json` (124 records) - Brand information
- `makes-filter/*.json` (55 files) - Filtered manufacturer data
**ETL Process:**
1. **Extract** - Loads all JSON source files
2. **Transform**
- Converts brand names from ALL CAPS to Title Case ("FORD" → "Ford")
- Creates simplified engine display names (e.g., "V8 3.5L Turbo")
- Extracts configuration (V8, I4, L6), displacement, and aspiration
- Handles missing displacement by parsing from engine name
- Creates simplified transmission display names (e.g., "8-Speed Automatic")
- Extracts speed count and type (Manual, Automatic, CVT, Dual-Clutch)
- Normalizes displacement units (Cm3 → Liters) for matching
- Matches simple engine strings (e.g., "2.0L I4") to detailed specs
- Links transmissions to vehicle records (98.9% success rate)
- Filters vehicles to 1980 and newer only
- Performs hybrid backfill for recent years (2023-2025)
3. **Load** - Generates clean, optimized SQL import files
- Proper SQL escaping (newlines, quotes, special characters)
- Empty strings converted to NULL for data integrity
- Batched inserts for optimal performance
**Output Files:**
- `output/01_engines.sql` (~632KB, 30,066 records) - Only id and name columns
- `output/02_transmissions.sql` (~21KB, 828 records) - Only id and type columns
- `output/03_vehicle_options.sql` (~51MB, 1,122,644 records)
### 3. Import Script (`import_data.sh`)
Bash script that:
1. Runs database schema migration
2. Imports engines from SQL file
3. Imports transmissions from SQL file
4. Imports vehicle options from SQL file
5. Validates imported data with queries
---
## How to Use the Database
### Running the ETL Pipeline
```bash
# Step 1: Generate SQL files from JSON data
python3 etl_generate_sql.py
# Step 2: Import SQL files into database
./import_data.sh
```
### Example Dropdown Queries
**Get available years:**
```sql
SELECT * FROM available_years;
```
**Get makes for 2025:**
```sql
SELECT * FROM get_makes_for_year(2025);
```
**Get Ford models for 2025:**
```sql
SELECT * FROM get_models_for_year_make(2025, 'Ford');
```
**Get trims for 2025 Ford F-150:**
```sql
SELECT * FROM get_trims_for_year_make_model(2025, 'Ford', 'f-150');
```
**Get complete vehicle configuration:**
```sql
SELECT * FROM complete_vehicle_configs
WHERE year = 2025 AND make = 'Ford' AND model = 'f-150'
LIMIT 10;
```
### Accessing the Database
```bash
# Via Docker exec
docker exec -it mvp-postgres psql -U postgres -d motovaultpro
# Direct SQL query
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT * FROM available_years;"
```
---
## Data Flow: Year → Make → Model → Trim → Engine
The database is designed to support cascading dropdowns for vehicle selection:
1. **User selects Year** → Query: `get_makes_for_year(year)`
2. **User selects Make** → Query: `get_models_for_year_make(year, make)`
3. **User selects Model** → Query: `get_trims_for_year_make_model(year, make, model)`
4. **User selects Trim** → Query: `get_options_for_vehicle(year, make, model, trim)`
Each query is optimized with composite indexes for sub-50ms response times.
---
## Files Created
| File | Description | Size |
|------|-------------|------|
| `migrations/001_create_vehicle_database.sql` | Database schema | ~8KB |
| `etl_generate_sql.py` | ETL script (generates SQL files) | ~20KB |
| `import_data.sh` | Import script | ~2KB |
| `output/01_engines.sql` | Engine data | 34MB |
| `output/03_vehicle_options.sql` | Vehicle options data | 56MB |
| `ETL_README.md` | Detailed documentation | ~8KB |
| `IMPLEMENTATION_SUMMARY.md` | This file | ~5KB |
---
## Key Design Decisions
### 1. SQL File Generation (Not Direct DB Connection)
- **Why:** Avoids dependency installation in Docker container
- **Benefit:** Clean separation of ETL and import processes
- **Trade-off:** Requires intermediate storage (90MB of SQL files)
### 2. Denormalized vehicle_options Table
- **Why:** Optimized for read-heavy dropdown queries
- **Benefit:** Single table queries with composite indexes = fast lookups
- **Trade-off:** Some data duplication (1.2M records)
### 3. Hybrid Backfill for Recent Years
- **Why:** makes-filter data may not include latest 2023-2025 models
- **Benefit:** Database includes most recent vehicle data
- **Trade-off:** Slight data inconsistency (backfilled records marked with "Base" trim)
### 4. Engine Matching by Displacement + Configuration
- **Why:** makes-filter has simple strings ("2.0L I4"), engines.json has detailed specs
- **Benefit:** Links dropdown data to rich engine specifications
- **Trade-off:** ~0 matches if displacement/config formats don't align perfectly
---
## Known Limitations
1. **Electric Vehicles Have NULL Engine/Transmission IDs (1.1%)**
- Occurs when engine string from makes-filter doesn't match traditional displacement patterns
- Example: Tesla models with "Electric" motors don't have displacement specs
- Affects 11,951 of 1,122,644 records
- Future enhancement: Add electric motor specifications
2. **Model Names Have Inconsistencies**
- Some models use underscores (`bronco_sport` vs `Bronco Sport`)
- Model name casing varies between sources
- Future enhancement: Normalize model names to Title Case
3. **Engine Configuration Variations**
- Some engines show "4 Inline" while others show "L4" or "I4"
- All refer to inline 4-cylinder but use different notation
- Source data inconsistency from autoevolution.com
---
## Next Steps / Recommendations
### Immediate
1. ✅ Database is functional and ready for API integration
2. ✅ Dropdown queries are working and optimized
### Short Term
1. **Clean up model names** - Remove HTML entities, normalize formatting
2. **Add transmission data** - Find alternative source or manual entry
3. **Filter year range** - Add view for "modern vehicles" (e.g., 2000+)
4. **Add vehicle images** - Link to photo URLs from automobiles.json
### Medium Term
1. **Create REST API** - Build endpoints for dropdown queries
2. **Add caching layer** - Redis/Memcached for frequently accessed data
3. **Full-text search** - PostgreSQL FTS for model name searching
4. **Admin interface** - CRUD operations for data management
### Long Term
1. **Real-time updates** - Webhook/API to sync with autoevolution.com
2. **User preferences** - Save favorite vehicles, comparison features
3. **Analytics** - Track popular makes/models, search patterns
4. **Mobile optimization** - Optimize queries for mobile app usage
---
## Performance Notes
- **Index Coverage:** All dropdown queries use composite indexes
- **Expected Query Time:** < 50ms for typical dropdown query
- **Database Size:** ~250MB with all data and indexes
- **Batch Insert Performance:** 1000 records per batch = optimal
---
## Testing Checklist
- [x] Schema migration runs successfully
- [x] Engines import (30,066 records)
- [x] Vehicle options import (1,213,401 records)
- [x] available_years view returns data
- [x] get_makes_for_year() function works
- [x] get_models_for_year_make() function works
- [x] get_trims_for_year_make_model() function works
- [x] Composite indexes created
- [x] Foreign key relationships established
- [x] Year range validated (1918-2026)
- [x] Make count validated (53 makes)
---
## Conclusion
The automotive vehicle selection database is **complete and operational**. The database contains over 1.2 million vehicle configurations spanning 93 years and 53 manufacturers, optimized for cascading dropdown queries with sub-50ms response times.
The ETL pipeline is **production-ready** and can be re-run at any time to refresh data from updated JSON sources. All scripts are documented and executable with a single command.
**Status: ✅ READY FOR API DEVELOPMENT**

View File

@@ -1,117 +0,0 @@
# Quick Start Guide - Automotive Vehicle Database
## Database Status: ✅ OPERATIONAL
- **30,066** engines
- **828** transmissions
- **1,122,644** vehicle configurations
- **47** years (1980-2026)
- **53** makes
- **1,741** models
- **98.9%** transmission linking success
---
## Access the Database
```bash
docker exec -it mvp-postgres psql -U postgres -d motovaultpro
```
---
## Essential Queries
### 1. Get All Available Years
```sql
SELECT * FROM available_years;
```
### 2. Get Makes for a Specific Year
```sql
SELECT * FROM get_makes_for_year(2024);
```
### 3. Get Models for Year + Make
```sql
SELECT * FROM get_models_for_year_make(2024, 'Ford');
```
### 4. Get Trims for Year + Make + Model
```sql
SELECT * FROM get_trims_for_year_make_model(2024, 'Ford', 'f-150');
```
### 5. Get Complete Vehicle Details
```sql
SELECT * FROM complete_vehicle_configs
WHERE year = 2024
AND make = 'Ford'
AND model = 'f-150'
LIMIT 10;
```
---
## Refresh the Database
```bash
# Re-generate SQL files from JSON source data
python3 etl_generate_sql.py
# Re-import into database
./import_data.sh
```
---
## Files Overview
| File | Purpose | Size |
|------|---------|------|
| `etl_generate_sql.py` | Generate SQL import files from JSON | ~20KB |
| `import_data.sh` | Import SQL files into database | ~2KB |
| `migrations/001_create_vehicle_database.sql` | Database schema | ~8KB |
| `output/01_engines.sql` | Engine data (id, name only) | ~632KB |
| `output/02_transmissions.sql` | Transmission data (id, type only) | ~21KB |
| `output/03_vehicle_options.sql` | Vehicle configurations | ~51MB |
| **Total Output** | | **~52MB** |
---
## Database Schema
```
engines
├── id (PK)
└── name (e.g., "V8 3.5L Turbo", "L4 2.0L")
transmissions
├── id (PK)
└── type (e.g., "8-Speed Automatic", "6-Speed Manual")
vehicle_options
├── id (PK)
├── year (1980-2026)
├── make (Title Case: "Ford", "Acura", "Land Rover")
├── model
├── trim
├── engine_id (FK → engines)
└── transmission_id (FK → transmissions)
```
---
## Performance
- **Query Time:** < 50ms (composite indexes)
- **Database Size:** ~250MB (with indexes)
- **SQL Import Files:** ~52MB total
- **Batch Insert Size:** 1,000 records per batch
---
## Support
- **Full Documentation:** See `ETL_README.md`
- **Implementation Details:** See `IMPLEMENTATION_SUMMARY.md`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,653 +0,0 @@
#!/usr/bin/env python3
"""
ETL Script for Automotive Vehicle Selection Database
Generates SQL import files from local scraped data only (no network).
Output is constrained to a configurable year window (default 20002026).
"""
import json
import os
import re
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
class VehicleSQLGenerator:
def __init__(self):
self.makes_filter_dir = Path("makes-filter")
self.engines_data: List[Dict] = []
self.automobiles_data: List[Dict] = []
self.brands_data: List[Dict] = []
# Year window (configurable)
self.min_year = int(os.getenv("MIN_YEAR", "2000"))
self.max_year = int(os.getenv("MAX_YEAR", "2026"))
# Output SQL files
self.engines_sql_file = "output/01_engines.sql"
self.transmissions_sql_file = "output/02_transmissions.sql"
self.vehicles_sql_file = "output/03_vehicle_options.sql"
# Data structures populated during ETL
self.brand_name_map: Dict[str, str] = {}
self.known_models_by_make: Dict[str, Set[str]] = defaultdict(set)
# Baseline records from makes-filter with trims/engines per year/make/model
self.baseline_records: List[Dict] = []
self.evidence_by_model: Dict[Tuple[str, str], List[Dict]] = defaultdict(list)
self.vehicle_records: List[Dict] = []
# Dimension maps (populated after vehicle records are built)
self.engine_name_to_id: Dict[str, int] = {}
self.trans_name_to_id: Dict[str, int] = {}
# ------------------------------------------------------------------
# Loading and helper utilities
# ------------------------------------------------------------------
def load_json_files(self):
print("\n📂 Loading source JSON files...")
with open("engines.json", "r", encoding="utf-8") as f:
self.engines_data = json.load(f)
print(f" ✓ Loaded {len(self.engines_data):,} engine records")
with open("automobiles.json", "r", encoding="utf-8") as f:
self.automobiles_data = json.load(f)
print(f" ✓ Loaded {len(self.automobiles_data):,} automobile records")
with open("brands.json", "r", encoding="utf-8") as f:
self.brands_data = json.load(f)
print(f" ✓ Loaded {len(self.brands_data):,} brand records")
self.build_brand_name_map()
def build_brand_name_map(self):
keep_uppercase = {
"BMW",
"GMC",
"AC",
"MG",
"KIA",
"MINI",
"FIAT",
"RAM",
"KTM",
"FSO",
"ARO",
"TVR",
"NIO",
}
special_cases = {"delorean": "DeLorean", "mclaren": "McLaren"}
for brand in self.brands_data:
raw = brand.get("name", "").strip()
if not raw:
continue
slug = raw.lower().replace(" ", "_")
if slug in special_cases:
canonical = special_cases[slug]
elif raw in keep_uppercase:
canonical = raw
else:
canonical = raw.title()
self.brand_name_map[slug] = canonical
self.brand_name_map[raw.lower()] = canonical
def get_canonical_make_name(self, name: str) -> str:
slug = name.lower().replace(" ", "_")
if slug in self.brand_name_map:
return self.brand_name_map[slug]
spaced = slug.replace("_", " ")
if spaced in self.brand_name_map:
return self.brand_name_map[spaced]
return spaced.title()
def format_model_name(self, model_slug: str) -> str:
if not model_slug:
return ""
return model_slug.replace("_", " ").strip().title()
def classify_fuel_label(self, fuel: str) -> str:
fuel_lower = (fuel or "").lower()
if "electric" in fuel_lower:
return "Electric"
if "diesel" in fuel_lower:
return "Diesel"
if "hybrid" in fuel_lower:
return "Hybrid"
return "Gas"
def normalize_engine_display(self, display: Optional[str]) -> Optional[str]:
if not display:
return None
cleaned = display.strip()
return cleaned if cleaned else None
# ------------------------------------------------------------------
# Parsing helpers for evidence building
# ------------------------------------------------------------------
def parse_year_range_from_name(self, name: str) -> Optional[Tuple[int, int]]:
"""
Extract year or year range from automobile name.
Examples:
"CHEVROLET Corvette C4 Convertible 1984-1996 ..." -> (1984, 1996)
"2021-Present" -> (2021, self.max_year)
"2024 ..." -> (2024, 2024)
"""
match = re.search(r"(19|20)\d{2}(?:-(\d{4}|Present))?", name)
if not match:
return None
start = int(match.group(0).split("-")[0])
end_part = None
if "-" in match.group(0):
end_str = match.group(0).split("-")[1]
if end_str.lower() == "present":
end_part = self.max_year
else:
end_part = int(end_str)
end = end_part if end_part else start
return (max(start, self.min_year), min(end, self.max_year))
def split_model_and_trim(
self, make: str, candidate: str, known_models: Set[str]
) -> Tuple[Optional[str], Optional[str]]:
"""
Split the automobile title (with years removed) into model + trim by matching
the longest known model prefix. Returns (model, trim).
"""
candidate_clean = candidate.strip()
if not candidate_clean:
return (None, None)
# Try longest model name first
for model in sorted(known_models, key=len, reverse=True):
pattern = re.compile(rf"^{re.escape(model)}\b", re.IGNORECASE)
match = pattern.match(candidate_clean)
if match:
remaining = candidate_clean[match.end() :].strip()
model_name = model
trim_name = remaining if remaining else "Base"
return (model_name, trim_name)
# If nothing matched, give up to avoid inventing models
return (None, None)
# ------------------------------------------------------------------
# Engine / transmission formatting helpers
# ------------------------------------------------------------------
def extract_engine_specs(self, engine_record: Dict) -> Dict:
specs = engine_record.get("specs", {})
engine_specs = specs.get("Engine Specs", {})
trans_specs = specs.get("Transmission Specs", {})
return {
"name": engine_record.get("name", ""),
"displacement": engine_specs.get("Displacement:", ""),
"configuration": engine_specs.get("Cylinders:", ""),
"horsepower": engine_specs.get("Power:", ""),
"torque": engine_specs.get("Torque:", ""),
"fuel_type": engine_specs.get("Fuel:", ""),
"fuel_system": engine_specs.get("Fuel System:", ""),
"aspiration": engine_specs.get("Aspiration:", ""),
"transmission_type": trans_specs.get("Gearbox:", ""),
"drive_type": trans_specs.get("Drive Type:", ""),
"specs_json": specs,
}
def normalize_displacement(self, disp_str: str) -> Optional[str]:
if not disp_str:
return None
disp_str = disp_str.strip()
if disp_str.upper().endswith("L"):
match = re.search(r"(\d+\.?\d*)", disp_str)
if match:
liters = float(match.group(1))
return f"{liters:.1f}L"
cm3_match = re.search(r"(\d+)\s*Cm3", disp_str, re.IGNORECASE)
if cm3_match:
cm3 = int(cm3_match.group(1))
liters = cm3 / 1000.0
return f"{liters:.1f}L"
return None
def format_engine_display(self, specs: Dict) -> Optional[str]:
parts: List[str] = []
config = specs.get("configuration", "").strip()
if config:
parts.append(config.upper())
disp = self.normalize_displacement(specs.get("displacement", ""))
if not disp:
name = specs.get("name", "")
disp_match = re.search(r"(\d+\.?\d*)\s*L", name, re.IGNORECASE)
if disp_match:
disp = f"{float(disp_match.group(1)):.1f}L"
if disp:
parts.append(disp)
aspiration = specs.get("aspiration", "").strip()
fuel_system = specs.get("fuel_system", "").strip()
combined = f"{aspiration} {fuel_system}".lower()
if combined and "naturally aspirated" not in combined:
if "turbo" in combined:
parts.append("Turbo")
elif "supercharg" in combined:
parts.append("Supercharged")
if not parts:
return None
return " ".join(parts)
def format_transmission_display(self, trans_type: str, speeds: Optional[str]) -> str:
trans_clean = trans_type.strip() if trans_type else ""
if trans_clean:
trans_lower = trans_clean.lower()
if "cvt" in trans_lower:
return "CVT"
speed_text = None
if speeds:
speed_text = f"{speeds}-Speed"
elif trans_clean:
speed_match = re.search(r"(\d+)[- ]?[Ss]peed", trans_clean)
if speed_match:
speed_text = f"{speed_match.group(1)}-Speed"
kind = None
if trans_clean:
lower = trans_clean.lower()
if "manual" in lower:
kind = "Manual"
elif "automatic" in lower or "auto" in lower:
kind = "Automatic"
elif "direct" in lower or "dct" in lower:
kind = "Dual-Clutch"
if speed_text and kind:
return f"{speed_text} {kind}"
if kind:
return kind
if speed_text:
return f"{speed_text} Automatic"
return "Automatic"
def normalize_engine_string(self, engine: str) -> Optional[str]:
if not engine:
return None
eng = engine.strip()
if not eng:
return None
return eng
# ------------------------------------------------------------------
# Phase 1: Build baseline from makes-filter (year/make/model + trims/engines)
# ------------------------------------------------------------------
def build_known_models(self):
print("\n📖 Building known models from makes-filter...")
if not self.makes_filter_dir.exists():
raise FileNotFoundError("makes-filter directory not found")
for json_file in sorted(self.makes_filter_dir.glob("*.json")):
make_name = self.get_canonical_make_name(json_file.stem)
with open(json_file, "r", encoding="utf-8") as f:
make_data = json.load(f)
for _, year_entries in make_data.items():
for year_entry in year_entries:
year = int(year_entry.get("year", 0))
if year < self.min_year or year > self.max_year:
continue
for model in year_entry.get("models", []):
model_name = self.format_model_name(model.get("name", ""))
if model_name:
self.known_models_by_make[make_name].add(model_name)
total_models = sum(len(v) for v in self.known_models_by_make.values())
print(f" ✓ Collected {total_models} model names across makes")
def build_baseline_records(self):
print("\n🧩 Building baseline records (year/make/model + trims/engines)...")
records: List[Dict] = []
for json_file in sorted(self.makes_filter_dir.glob("*.json")):
make_name = self.get_canonical_make_name(json_file.stem)
with open(json_file, "r", encoding="utf-8") as f:
make_data = json.load(f)
for _, year_entries in make_data.items():
for year_entry in year_entries:
year = int(year_entry.get("year", 0))
if year < self.min_year or year > self.max_year:
continue
for model in year_entry.get("models", []):
model_name = self.format_model_name(model.get("name", ""))
if not model_name:
continue
engines = [self.normalize_engine_string(e) for e in model.get("engines", [])]
engines = [e for e in engines if e]
submodels = model.get("submodels", [])
if not submodels:
submodels = ["Base"]
trims_payload = []
for trim in submodels:
trim_name = self.format_model_name(trim)
trims_payload.append({"trim": trim_name or "Base", "engines": engines})
records.append({"year": year, "make": make_name, "model": model_name, "trims": trims_payload})
self.baseline_records = sorted(records, key=lambda r: (r["year"], r["make"].lower(), r["model"].lower()))
print(f" ✓ Baseline records: {len(self.baseline_records):,}")
# ------------------------------------------------------------------
# Phase 2: Build evidence from automobiles + engines
# ------------------------------------------------------------------
def build_automobile_evidence(self):
print("\n🔎 Building automobile evidence (trims/years/engines/transmissions)...")
brand_lookup = {b.get("id"): self.get_canonical_make_name(b.get("name", "")) for b in self.brands_data}
# Build quick index: automobile_id -> engines
engines_by_auto: Dict[int, List[Dict]] = defaultdict(list)
for engine in self.engines_data:
auto_id = engine.get("automobile_id")
if auto_id:
engines_by_auto[auto_id].append(engine)
for auto in self.automobiles_data:
auto_id = auto.get("id")
brand_id = auto.get("brand_id")
make = brand_lookup.get(brand_id)
if not make:
continue
year_range = self.parse_year_range_from_name(auto.get("name", ""))
if not year_range:
continue
year_start, year_end = year_range
if year_end < self.min_year or year_start > self.max_year:
continue
known_models = self.known_models_by_make.get(make, set())
if not known_models:
continue
name_clean = auto.get("name", "")
# Remove make prefix if present
name_clean = re.sub(rf"^{re.escape(make)}\s+", "", name_clean, flags=re.IGNORECASE)
# Remove year substring
name_clean = re.sub(r"(19|20)\d{2}(-\d{4}|-Present)?", "", name_clean).strip()
model, trim = self.split_model_and_trim(make, self.format_model_name(name_clean), known_models)
if not model:
continue
trim = self.format_model_name(trim or "Base")
engine_displays: Set[str] = set()
fuel_labels: Set[str] = set()
transmission_displays: Set[str] = set()
for engine_record in engines_by_auto.get(auto_id, []):
specs = self.extract_engine_specs(engine_record)
display = self.normalize_engine_display(self.format_engine_display(specs))
fuel_label = self.classify_fuel_label(specs.get("fuel_type", ""))
fuel_labels.add(fuel_label)
if display:
engine_displays.add(display)
trans_type = specs.get("transmission_type", "")
speed_match = re.search(r"(\d+)", trans_type) if trans_type else None
speeds = speed_match.group(1) if speed_match else None
trans_display = self.format_transmission_display(trans_type, speeds)
if trans_display:
transmission_displays.add(trans_display)
self.evidence_by_model[(make, model)].append(
{
"year_start": year_start,
"year_end": year_end,
"trim": trim,
"engines": engine_displays,
"transmissions": transmission_displays,
"fuel_labels": fuel_labels,
}
)
total_entries = sum(len(v) for v in self.evidence_by_model.values())
print(f" ✓ Evidence entries: {total_entries:,}")
# ------------------------------------------------------------------
# Phase 3: Build vehicle records combining baseline + evidence
# ------------------------------------------------------------------
def build_vehicle_records(self):
print("\n🚗 Building vehicle option records...")
records: List[Dict] = []
for baseline in self.baseline_records:
year = baseline["year"]
make = baseline["make"]
model = baseline["model"]
trims_payload = baseline["trims"]
evidence_entries = self.evidence_by_model.get((make, model), [])
applicable = [e for e in evidence_entries if e["year_start"] <= year <= e["year_end"]]
# Build evidence map per trim (case-insensitive match)
evidence_by_trim: Dict[str, Dict[str, Set[str]]] = defaultdict(lambda: {"engines": set(), "transmissions": set(), "fuel_labels": set()})
for entry in applicable:
trim_key = entry["trim"].lower()
evidence_by_trim[trim_key]["engines"].update(entry["engines"])
evidence_by_trim[trim_key]["transmissions"].update(entry["transmissions"])
evidence_by_trim[trim_key]["fuel_labels"].update(entry["fuel_labels"])
# Include evidence-only trims not present in makes-filter (if any)
all_trim_keys = set()
for t in trims_payload:
all_trim_keys.add(t["trim"].lower())
for trim_key in evidence_by_trim.keys():
all_trim_keys.add(trim_key)
if not all_trim_keys:
all_trim_keys.add("base")
for trim_key in sorted(all_trim_keys):
# If evidence exists for this trim but none cover this year, skip (avoids impossible year/trim combos)
evidence_for_trim = [e for e in evidence_entries if e["trim"].lower() == trim_key]
if evidence_for_trim and trim_key not in evidence_by_trim:
continue
# Determine trim display
trim_display = None
for t in trims_payload:
if t["trim"].lower() == trim_key:
trim_display = t["trim"]
break
if not trim_display:
# Use evidence trim name if not in makes-filter
for entry in applicable:
if entry["trim"].lower() == trim_key:
trim_display = entry["trim"]
break
if not trim_display:
trim_display = "Base"
# Engines: start from makes-filter engines for this trim
engines_set: Set[str] = set()
for t in trims_payload:
if t["trim"].lower() == trim_key:
for eng in t.get("engines", []):
norm = self.normalize_engine_string(eng)
if norm:
engines_set.add(norm)
# Overlay evidence engines for this trim
if trim_key in evidence_by_trim:
engines_set.update(evidence_by_trim[trim_key]["engines"])
# Fuel labels from evidence for fallback
fuel_labels = evidence_by_trim.get(trim_key, {}).get("fuel_labels", set())
if engines_set:
engine_names = sorted(engines_set)
else:
fallback_fuel = None
if fuel_labels:
if "Electric" in fuel_labels:
fallback_fuel = "Electric"
elif "Diesel" in fuel_labels:
fallback_fuel = "Diesel"
elif "Hybrid" in fuel_labels:
fallback_fuel = "Hybrid"
engine_names = [fallback_fuel or "Gas"]
transmissions_set: Set[str] = set()
if trim_key in evidence_by_trim:
transmissions_set.update(evidence_by_trim[trim_key]["transmissions"])
trans_names = sorted(transmissions_set) if transmissions_set else ["Manual", "Automatic"]
for engine_name in engine_names:
for trans_name in trans_names:
records.append(
{
"year": year,
"make": make,
"model": model,
"trim": trim_display,
"engine_name": engine_name,
"trans_name": trans_name,
}
)
# Deduplicate fact rows
unique_set = set()
deduped_records = []
for r in records:
key = (r["year"], r["make"].lower(), r["model"].lower(), r["trim"].lower(), r["engine_name"].lower(), r["trans_name"].lower())
if key in unique_set:
continue
unique_set.add(key)
deduped_records.append(r)
self.vehicle_records = deduped_records
print(f" ✓ Vehicle records after dedupe: {len(self.vehicle_records):,}")
# ------------------------------------------------------------------
# Phase 4: Assign dimension IDs and write SQL
# ------------------------------------------------------------------
def assign_dimension_ids(self):
engine_names = sorted({r["engine_name"] for r in self.vehicle_records})
trans_names = sorted({r["trans_name"] for r in self.vehicle_records})
self.engine_name_to_id = {name: idx + 1 for idx, name in enumerate(engine_names)}
self.trans_name_to_id = {name: idx + 1 for idx, name in enumerate(trans_names)}
def write_engines_sql(self):
os.makedirs("output", exist_ok=True)
with open(self.engines_sql_file, "w", encoding="utf-8") as f:
f.write("-- Engines data import\n-- Generated by ETL script\n\nBEGIN;\n\n")
values = []
for name, idx in sorted(self.engine_name_to_id.items(), key=lambda x: x[1]):
values.append(f"({idx},'{self.sql_escape_literal(name)}')")
f.write("INSERT INTO engines (id, name) VALUES\n")
f.write(",\n".join(values))
f.write(";\n\n")
f.write(f"SELECT setval('engines_id_seq', {len(self.engine_name_to_id)});\n\nCOMMIT;\n")
def write_transmissions_sql(self):
os.makedirs("output", exist_ok=True)
with open(self.transmissions_sql_file, "w", encoding="utf-8") as f:
f.write("-- Transmissions data import\n-- Generated by ETL script\n\nBEGIN;\n\n")
values = []
for name, idx in sorted(self.trans_name_to_id.items(), key=lambda x: x[1]):
values.append(f"({idx},'{self.sql_escape_literal(name)}')")
f.write("INSERT INTO transmissions (id, type) VALUES\n")
f.write(",\n".join(values))
f.write(";\n\n")
f.write(f"SELECT setval('transmissions_id_seq', {len(self.trans_name_to_id)});\n\nCOMMIT;\n")
def write_vehicle_options_sql(self):
os.makedirs("output", exist_ok=True)
with open(self.vehicles_sql_file, "w", encoding="utf-8") as f:
f.write("-- Vehicle options data import\n-- Generated by ETL script\n\nBEGIN;\n\n")
batch_size = 1000
total = len(self.vehicle_records)
for start in range(0, total, batch_size):
end = min(start + batch_size, total)
batch = self.vehicle_records[start:end]
f.write("INSERT INTO vehicle_options (year, make, model, trim, engine_id, transmission_id) VALUES\n")
values = []
for record in batch:
engine_id = self.engine_name_to_id[record["engine_name"]]
trans_id = self.trans_name_to_id[record["trans_name"]]
values.append(
f"({record['year']},'{self.sql_escape_literal(record['make'])}','{self.sql_escape_literal(record['model'])}','{self.sql_escape_literal(record['trim'])}',{engine_id},{trans_id})"
)
f.write(",\n".join(values))
f.write(";\n\n")
f.write("COMMIT;\n")
# ------------------------------------------------------------------
# Utility
# ------------------------------------------------------------------
def sql_escape_literal(self, value: str) -> str:
return value.replace("\\", "\\\\").replace("'", "''")
# ------------------------------------------------------------------
# Stats
# ------------------------------------------------------------------
def generate_stats(self):
stats = {
"min_year": min(r["year"] for r in self.vehicle_records) if self.vehicle_records else None,
"max_year": max(r["year"] for r in self.vehicle_records) if self.vehicle_records else None,
"vehicle_records": len(self.vehicle_records),
"engines": len(self.engine_name_to_id),
"transmissions": len(self.trans_name_to_id),
"makes": len({r["make"] for r in self.vehicle_records}),
"models": len({(r["make"], r["model"]) for r in self.vehicle_records}),
}
with open("output/stats.txt", "w", encoding="utf-8") as f:
f.write("=" * 60 + "\n")
f.write("ETL Statistics\n")
f.write("=" * 60 + "\n\n")
for key, value in stats.items():
formatted = f"{value:,}" if isinstance(value, int) else value
f.write(f"{key.replace('_', ' ').title()}: {formatted}\n")
print("\n📊 Statistics:")
for key, value in stats.items():
formatted = f"{value:,}" if isinstance(value, int) else value
print(f" {key.replace('_', ' ').title()}: {formatted}")
# ------------------------------------------------------------------
# Run
# ------------------------------------------------------------------
def run(self):
try:
print("=" * 60)
print("🚀 Automotive Vehicle ETL - SQL Generator")
print(f" Year Window: {self.min_year}{self.max_year}")
print("=" * 60)
self.load_json_files()
self.build_known_models()
self.build_baseline_records()
self.build_automobile_evidence()
self.build_vehicle_records()
self.assign_dimension_ids()
self.write_engines_sql()
self.write_transmissions_sql()
self.write_vehicle_options_sql()
self.generate_stats()
print("\n" + "=" * 60)
print("✅ SQL Files Generated Successfully!")
print("=" * 60)
print("\nGenerated files:")
print(f" - {self.engines_sql_file}")
print(f" - {self.transmissions_sql_file}")
print(f" - {self.vehicles_sql_file}")
print(f" - output/stats.txt")
print("\nNext step: Import SQL files into database")
print(" ./import_data.sh")
except Exception as e:
print(f"\n❌ ETL Pipeline Failed: {e}")
import traceback
traceback.print_exc()
raise
if __name__ == "__main__":
VehicleSQLGenerator().run()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,506 +0,0 @@
{
"aston_martin": [
{
"year": "2023",
"models": [
{
"name": "vantage",
"engines": [
"4.0L V8",
"5.2L V12"
],
"submodels": [
"AMR",
"V12",
"Base"
]
}
]
},
{
"year": "2020",
"models": [
{
"name": "db11",
"engines": [
"4.0L V8"
],
"submodels": []
},
{
"name": "dbs",
"engines": [
"5.2L V12"
],
"submodels": []
},
{
"name": "vantage",
"engines": [
"4.0L V8",
"5.2L V12"
],
"submodels": [
"AMR",
"V12",
"Base"
]
}
]
},
{
"year": "2019",
"models": [
{
"name": "vantage",
"engines": [
"4.0L V8",
"5.2L V12"
],
"submodels": [
"AMR",
"V12",
"Base"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "rapide",
"engines": [
"6.0L V12"
],
"submodels": []
}
]
},
{
"year": "2017",
"models": [
{
"name": "v12_vantage",
"engines": [
"6.0L V12"
],
"submodels": [
"Base",
"S"
]
},
{
"name": "vanquish",
"engines": [
"6.0L V12"
],
"submodels": [
"Carbon",
"Base",
"Volante"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "rapide",
"engines": [
"6.0L V12"
],
"submodels": []
},
{
"name": "v12_vantage",
"engines": [
"6.0L V12"
],
"submodels": [
"Base",
"S"
]
},
{
"name": "vanquish",
"engines": [
"6.0L V12"
],
"submodels": [
"Carbon",
"Base",
"Volante"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "rapide",
"engines": [
"6.0L V12"
],
"submodels": []
},
{
"name": "v12_vantage",
"engines": [
"6.0L V12"
],
"submodels": [
"Base",
"S"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
},
{
"name": "vanquish",
"engines": [
"6.0L V12"
],
"submodels": [
"Carbon",
"Base",
"Volante"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
},
{
"name": "vanquish",
"engines": [
"6.0L V12"
],
"submodels": [
"Carbon",
"Base",
"Volante"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "v12_vantage",
"engines": [
"6.0L V12"
],
"submodels": [
"Base",
"S"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2010",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "v8_vantage",
"engines": [
"4.3L V8",
"4.7L V8"
],
"submodels": [
"GT",
"S",
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "db9",
"engines": [
"6.0L V12"
],
"submodels": [
"Volante",
"Base"
]
},
{
"name": "vantage",
"engines": [
"4.0L V8",
"5.2L V12"
],
"submodels": [
"AMR",
"V12",
"Base"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "db7",
"engines": [
"6.0L V12"
],
"submodels": [
"Vantage Volante",
"Vantage"
]
}
]
},
{
"year": "2001",
"models": [
{
"name": "db7",
"engines": [
"6.0L V12"
],
"submodels": [
"Vantage Volante",
"Vantage"
]
}
]
},
{
"year": "1993",
"models": [
{
"name": "virage",
"engines": [
"5.3L V8"
],
"submodels": [
"Volante"
]
}
]
},
{
"year": "1990",
"models": [
{
"name": "virage",
"engines": [
"5.3L V8"
],
"submodels": [
"Volante"
]
}
]
},
{
"year": "1983",
"models": [
{
"name": "v-8",
"engines": [
"5.3L V8"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,427 +0,0 @@
{
"bentley": [
{
"year": "2023",
"models": [
{
"name": "flying_spur",
"engines": [
"2.9L V6 MILD HYBRID EV- (MHEV)",
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Hybrid",
"V8",
"W12",
"S Hybrid",
"Base"
]
}
]
},
{
"year": "2022",
"models": [
{
"name": "flying_spur",
"engines": [
"2.9L V6 MILD HYBRID EV- (MHEV)",
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Hybrid",
"V8",
"W12",
"S Hybrid",
"Base"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
},
{
"name": "flying_spur",
"engines": [
"2.9L V6 MILD HYBRID EV- (MHEV)",
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Hybrid",
"V8",
"W12",
"S Hybrid",
"Base"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "bentayga",
"engines": [
"6.0L W12"
],
"submodels": [
"W12 Signature",
"Black Edition"
]
},
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
},
{
"name": "flying_spur",
"engines": [
"2.9L V6 MILD HYBRID EV- (MHEV)",
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Hybrid",
"V8",
"W12",
"S Hybrid",
"Base"
]
},
{
"name": "mulsanne",
"engines": [
"6.8L V8"
],
"submodels": [
"Base",
"Speed"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
},
{
"name": "mulsanne",
"engines": [
"6.8L V8"
],
"submodels": [
"Base",
"Speed"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
},
{
"name": "flying_spur",
"engines": [
"2.9L V6 MILD HYBRID EV- (MHEV)",
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Hybrid",
"V8",
"W12",
"S Hybrid",
"Base"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "arnage",
"engines": [
"4.4L V8",
"6.8L V8"
],
"submodels": [
"Base",
"R"
]
},
{
"name": "continental",
"engines": [
"4.0L V8",
"6.0L W12 FLEX",
"6.0L W12"
],
"submodels": [
"Base",
"GTC",
"Flying Spur Speed",
"GT V8 S",
"GTC V8 S",
"Flying Spur",
"GT",
"GT Speed"
]
}
]
},
{
"year": "1999",
"models": [
{
"name": "arnage",
"engines": [
"4.4L V8",
"6.8L V8"
],
"submodels": [
"Base",
"R"
]
}
]
},
{
"year": "1997",
"models": [
{
"name": "brooklands",
"engines": [
"6.8L V8"
],
"submodels": []
}
]
},
{
"year": "1996",
"models": [
{
"name": "azure",
"engines": [],
"submodels": []
}
]
},
{
"year": "1989",
"models": [
{
"name": "turbo_r",
"engines": [
"6.8L V8"
],
"submodels": []
}
]
},
{
"year": "1963",
"models": [
{
"name": "s3_series",
"engines": [
"6.2L V8"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,607 +0,0 @@
{
"ferrari": [
{
"year": "2024",
"models": [
{
"name": "296_gts",
"engines": [
"3.0L V6 PLUG-IN HYBRID EV- (PHEV)"
],
"submodels": []
}
]
},
{
"year": "2022",
"models": [
{
"name": "f8_spider",
"engines": [
"3.9L V8"
],
"submodels": []
}
]
},
{
"year": "2019",
"models": [
{
"name": "portofino",
"engines": [
"3.9L V8"
],
"submodels": []
}
]
},
{
"year": "2018",
"models": [
{
"name": "488_spider",
"engines": [
"3.9L V8"
],
"submodels": []
}
]
},
{
"year": "2017",
"models": [
{
"name": "gtc4lusso",
"engines": [
"6.3L V12"
],
"submodels": []
}
]
},
{
"year": "2016",
"models": [
{
"name": "488_gtb",
"engines": [
"3.9L V8"
],
"submodels": []
},
{
"name": "ff",
"engines": [
"6.3L V12"
],
"submodels": []
}
]
},
{
"year": "2015",
"models": [
{
"name": "458_italia",
"engines": [
"4.5L V8"
],
"submodels": [
"Base"
]
},
{
"name": "458_spider",
"engines": [
"4.5L V8"
],
"submodels": []
},
{
"name": "california_t",
"engines": [
"3.8L V8",
"3.9L V8"
],
"submodels": []
},
{
"name": "f12_berlinetta",
"engines": [
"6.3L V12"
],
"submodels": []
}
]
},
{
"year": "2014",
"models": [
{
"name": "458_italia",
"engines": [
"4.5L V8"
],
"submodels": [
"Base"
]
},
{
"name": "california",
"engines": [
"4.3L V8"
],
"submodels": []
},
{
"name": "laferrari",
"engines": [
"6.3L V12"
],
"submodels": []
}
]
},
{
"year": "2013",
"models": [
{
"name": "458_italia",
"engines": [
"4.5L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "458_italia",
"engines": [
"4.5L V8"
],
"submodels": [
"Base"
]
},
{
"name": "ff",
"engines": [
"6.3L V12"
],
"submodels": []
}
]
},
{
"year": "2010",
"models": [
{
"name": "458_italia",
"engines": [
"4.5L V8"
],
"submodels": [
"Base"
]
},
{
"name": "california",
"engines": [
"4.3L V8"
],
"submodels": []
}
]
},
{
"year": "2009",
"models": [
{
"name": "599_gtb",
"engines": [
"6.0L V12"
],
"submodels": []
}
]
},
{
"year": "2008",
"models": [
{
"name": "599_gtb",
"engines": [
"6.0L V12"
],
"submodels": []
},
{
"name": "f430",
"engines": [
"4.3L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "f430",
"engines": [
"4.3L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "612_scaglietti",
"engines": [
"5.7L V12"
],
"submodels": []
},
{
"name": "f430",
"engines": [
"4.3L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "f430",
"engines": [
"4.3L V8"
],
"submodels": [
"Spider",
"Base"
]
},
{
"name": "superamerica",
"engines": [
"5.7L V12"
],
"submodels": []
}
]
},
{
"year": "2004",
"models": [
{
"name": "360",
"engines": [
"3.6L V8"
],
"submodels": [
"Challenge Stradale",
"Modena",
"Spider"
]
},
{
"name": "575_m_maranello",
"engines": [
"5.7L V12"
],
"submodels": []
},
{
"name": "enzo",
"engines": [],
"submodels": []
}
]
},
{
"year": "2003",
"models": [
{
"name": "360",
"engines": [
"3.6L V8"
],
"submodels": [
"Challenge Stradale",
"Modena",
"Spider"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "360",
"engines": [
"3.6L V8"
],
"submodels": [
"Challenge Stradale",
"Modena",
"Spider"
]
}
]
},
{
"year": "2001",
"models": [
{
"name": "360",
"engines": [
"3.6L V8"
],
"submodels": [
"Challenge Stradale",
"Modena",
"Spider"
]
}
]
},
{
"year": "2000",
"models": [
{
"name": "360",
"engines": [
"3.6L V8"
],
"submodels": [
"Challenge Stradale",
"Modena",
"Spider"
]
}
]
},
{
"year": "1998",
"models": [
{
"name": "456_gt",
"engines": [],
"submodels": []
}
]
},
{
"year": "1997",
"models": [
{
"name": "550_maranello",
"engines": [
"5.5L V12"
],
"submodels": []
},
{
"name": "f355_spider",
"engines": [
"3.5L V8"
],
"submodels": []
}
]
},
{
"year": "1996",
"models": [
{
"name": "f355_spider",
"engines": [
"3.5L V8"
],
"submodels": []
}
]
},
{
"year": "1995",
"models": [
{
"name": "f355_berlinetta",
"engines": [],
"submodels": []
}
]
},
{
"year": "1992",
"models": [
{
"name": "348_tb",
"engines": [
"3.4L V8"
],
"submodels": []
}
]
},
{
"year": "1991",
"models": [
{
"name": "mondial_t",
"engines": [
"3.4L V8"
],
"submodels": []
},
{
"name": "testarossa",
"engines": [
"4.9L H12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1990",
"models": [
{
"name": "348_ts",
"engines": [
"3.4L V8"
],
"submodels": []
}
]
},
{
"year": "1987",
"models": [
{
"name": "328_gts",
"engines": [
"3.2L V8"
],
"submodels": []
},
{
"name": "mondial_3_2",
"engines": [
"3.2L V8"
],
"submodels": []
},
{
"name": "testarossa",
"engines": [
"4.9L H12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1985",
"models": [
{
"name": "308_gts",
"engines": [
"3.0L V8"
],
"submodels": [
"Base",
"Quattrovalvole"
]
}
]
},
{
"year": "1983",
"models": [
{
"name": "308_gts",
"engines": [
"3.0L V8"
],
"submodels": [
"Base",
"Quattrovalvole"
]
}
]
},
{
"year": "1980",
"models": [
{
"name": "308_gts",
"engines": [
"3.0L V8"
],
"submodels": [
"Base",
"Quattrovalvole"
]
}
]
},
{
"year": "1977",
"models": [
{
"name": "308_gtb",
"engines": [
"3.0L V8"
],
"submodels": []
}
]
},
{
"year": "1972",
"models": [
{
"name": "365_gtc_4",
"engines": [
"4.4L V12"
],
"submodels": []
},
{
"name": "dino_246_gt",
"engines": [
"2.4L V6"
],
"submodels": []
}
]
},
{
"year": "1966",
"models": [
{
"name": "275_gtb",
"engines": [
"3.3L V12"
],
"submodels": []
},
{
"name": "500_superfast",
"engines": [
"5.0L V12"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,414 +0,0 @@
{
"genesis": [
{
"year": "2024",
"models": [
{
"name": "g90",
"engines": [
"3.3L V6",
"3.5L V6 MILD HYBRID EV- (MHEV)",
"5.0L V8"
],
"submodels": [
"E-Supercharger",
"Ultimate",
"Premium"
]
},
{
"name": "gv70",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Base",
"1.5 RS CVT Honda SENSING",
"Sport Plus",
"Prestige",
"Select",
"Advanced Plus",
"Sport Advanced",
"Advanced",
"Sport Prestige"
]
}
]
},
{
"year": "2023",
"models": [
{
"name": "g70",
"engines": [
"2.0L I4",
"3.3L V6"
],
"submodels": [
"Launch Edition",
"Base",
"Design",
"3.3T RWD",
"Advanced",
"Sport Prestige",
"Elite",
"Dynamic",
"Prestige",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "gv70",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Base",
"1.5 RS CVT Honda SENSING",
"Sport Plus",
"Prestige",
"Select",
"Advanced Plus",
"Sport Advanced",
"Advanced",
"Sport Prestige"
]
},
{
"name": "gv80",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Advanced",
"Advanced+",
"Prestige"
]
}
]
},
{
"year": "2022",
"models": [
{
"name": "g70",
"engines": [
"2.0L I4",
"3.3L V6"
],
"submodels": [
"Launch Edition",
"Base",
"Design",
"3.3T RWD",
"Advanced",
"Sport Prestige",
"Elite",
"Dynamic",
"Prestige",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "gv70",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Base",
"1.5 RS CVT Honda SENSING",
"Sport Plus",
"Prestige",
"Select",
"Advanced Plus",
"Sport Advanced",
"Advanced",
"Sport Prestige"
]
},
{
"name": "gv80",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Advanced",
"Advanced+",
"Prestige"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "g70",
"engines": [
"2.0L I4",
"3.3L V6"
],
"submodels": [
"Launch Edition",
"Base",
"Design",
"3.3T RWD",
"Advanced",
"Sport Prestige",
"Elite",
"Dynamic",
"Prestige",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "gv80",
"engines": [
"2.5L I4",
"3.5L V6"
],
"submodels": [
"Advanced",
"Advanced+",
"Prestige"
]
}
]
},
{
"year": "2020",
"models": [
{
"name": "g70",
"engines": [
"2.0L I4",
"3.3L V6"
],
"submodels": [
"Launch Edition",
"Base",
"Design",
"3.3T RWD",
"Advanced",
"Sport Prestige",
"Elite",
"Dynamic",
"Prestige",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
}
]
},
{
"year": "2019",
"models": [
{
"name": "g70",
"engines": [
"2.0L I4",
"3.3L V6"
],
"submodels": [
"Launch Edition",
"Base",
"Design",
"3.3T RWD",
"Advanced",
"Sport Prestige",
"Elite",
"Dynamic",
"Prestige",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "g90",
"engines": [
"3.3L V6",
"3.5L V6 MILD HYBRID EV- (MHEV)",
"5.0L V8"
],
"submodels": [
"E-Supercharger",
"Ultimate",
"Premium"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "g90",
"engines": [
"3.3L V6",
"3.5L V6 MILD HYBRID EV- (MHEV)",
"5.0L V8"
],
"submodels": [
"E-Supercharger",
"Ultimate",
"Premium"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "g80",
"engines": [
"2.5L I4",
"3.3L V6",
"3.5L V6",
"3.8L V6",
"5.0L V8"
],
"submodels": [
"Base",
"Advanced",
"Sport Prestige",
"5.0",
"Prestige",
"1.5 RS CVT Honda SENSING",
"3.8"
]
},
{
"name": "g90",
"engines": [
"3.3L V6",
"3.5L V6 MILD HYBRID EV- (MHEV)",
"5.0L V8"
],
"submodels": [
"E-Supercharger",
"Ultimate",
"Premium"
]
}
]
}
]
}

View File

@@ -1,383 +0,0 @@
{
"geo": [
{
"year": "1997",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1996",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1995",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1994",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1993",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "storm",
"engines": [
"1.6L I4"
],
"submodels": [
"2+2",
"2+2 GSi"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1992",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "storm",
"engines": [
"1.6L I4"
],
"submodels": [
"2+2",
"2+2 GSi"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1991",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "storm",
"engines": [
"1.6L I4"
],
"submodels": [
"2+2",
"2+2 GSi"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1990",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "prizm",
"engines": [
"1.6L I4",
"1.8L I4"
],
"submodels": [
"GSi",
"LSi",
"Base"
]
},
{
"name": "storm",
"engines": [
"1.6L I4"
],
"submodels": [
"2+2",
"2+2 GSi"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
},
{
"year": "1989",
"models": [
{
"name": "metro",
"engines": [
"1.0L L3",
"1.3L I4"
],
"submodels": [
"XFi",
"LSi",
"Base"
]
},
{
"name": "tracker",
"engines": [
"1.6L I4"
],
"submodels": [
"LSi",
"Base"
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,269 +0,0 @@
{
"hummer": [
{
"year": "2010",
"models": [
{
"name": "h3",
"engines": [
"3.5L L5",
"3.7L L5",
"5.3L V8"
],
"submodels": [
"Championship Series",
"X",
"Alpha",
"Adventure",
"Luxury",
"Base"
]
},
{
"name": "h3t",
"engines": [
"3.7L L5",
"5.3L V8 FLEX",
"5.3L V8"
],
"submodels": [
"Base",
"Alpha"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
},
{
"name": "h3",
"engines": [
"3.5L L5",
"3.7L L5",
"5.3L V8"
],
"submodels": [
"Championship Series",
"X",
"Alpha",
"Adventure",
"Luxury",
"Base"
]
},
{
"name": "h3t",
"engines": [
"3.7L L5",
"5.3L V8 FLEX",
"5.3L V8"
],
"submodels": [
"Base",
"Alpha"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
},
{
"name": "h3",
"engines": [
"3.5L L5",
"3.7L L5",
"5.3L V8"
],
"submodels": [
"Championship Series",
"X",
"Alpha",
"Adventure",
"Luxury",
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
},
{
"name": "h3",
"engines": [
"3.5L L5",
"3.7L L5",
"5.3L V8"
],
"submodels": [
"Championship Series",
"X",
"Alpha",
"Adventure",
"Luxury",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
},
{
"name": "h3",
"engines": [
"3.5L L5",
"3.7L L5",
"5.3L V8"
],
"submodels": [
"Championship Series",
"X",
"Alpha",
"Adventure",
"Luxury",
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
}
]
},
{
"year": "2003",
"models": [
{
"name": "h1",
"engines": [
"6.5L V8"
],
"submodels": [
"Base"
]
},
{
"name": "h2",
"engines": [
"6.0L V8",
"6.2L V8 FLEX",
"6.2L V8"
],
"submodels": [
"Special Edition",
"Adventure",
"Base",
"Luxury"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "h1",
"engines": [
"6.5L V8"
],
"submodels": [
"Base"
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,221 +0,0 @@
{
"lamborghini": [
{
"year": "2023",
"models": [
{
"name": "urus",
"engines": [
"4.0L V8"
],
"submodels": [
"Base",
"Performante"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "huracan",
"engines": [
"5.2L V10"
],
"submodels": []
}
]
},
{
"year": "2018",
"models": [
{
"name": "urus",
"engines": [
"4.0L V8"
],
"submodels": [
"Base",
"Performante"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "gallardo",
"engines": [
"5.0L V10",
"5.2L V10"
],
"submodels": [
"Spyder",
"LP550-2",
"Superleggera",
"Base"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "gallardo",
"engines": [
"5.0L V10",
"5.2L V10"
],
"submodels": [
"Spyder",
"LP550-2",
"Superleggera",
"Base"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "gallardo",
"engines": [
"5.0L V10",
"5.2L V10"
],
"submodels": [
"Spyder",
"LP550-2",
"Superleggera",
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "gallardo",
"engines": [
"5.0L V10",
"5.2L V10"
],
"submodels": [
"Spyder",
"LP550-2",
"Superleggera",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "gallardo",
"engines": [
"5.0L V10",
"5.2L V10"
],
"submodels": [
"Spyder",
"LP550-2",
"Superleggera",
"Base"
]
},
{
"name": "murcielago",
"engines": [
"6.2L V12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "murcielago",
"engines": [
"6.2L V12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2003",
"models": [
{
"name": "murcielago",
"engines": [
"6.2L V12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "murcielago",
"engines": [
"6.2L V12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1992",
"models": [
{
"name": "diablo",
"engines": [],
"submodels": []
}
]
},
{
"year": "1988",
"models": [
{
"name": "countach",
"engines": [
"5.2L V12"
],
"submodels": []
}
]
},
{
"year": "1974",
"models": [
{
"name": "urraco",
"engines": [
"2.5L V8"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,829 +0,0 @@
{
"lotus": [
{
"year": "2024",
"models": [
{
"name": "emira",
"engines": [
"2.0L I4",
"3.5L V6"
],
"submodels": [
"First Edition",
"Base"
]
}
]
},
{
"year": "2023",
"models": [
{
"name": "emira",
"engines": [
"2.0L I4",
"3.5L V6"
],
"submodels": [
"First Edition",
"Base"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "evora_gt",
"engines": [
"3.5L V6"
],
"submodels": []
}
]
},
{
"year": "2020",
"models": [
{
"name": "evora_gt",
"engines": [
"3.5L V6"
],
"submodels": []
}
]
},
{
"year": "2017",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
}
]
},
{
"year": "2010",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "evora",
"engines": [
"3.5L V6"
],
"submodels": [
"S SR",
"Sports Racer",
"400",
"S",
"Base"
]
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "europa_s",
"engines": [],
"submodels": []
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
},
{
"name": "exige",
"engines": [
"1.8L I4"
],
"submodels": [
"S",
"Base",
"S 240"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "2003",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "2001",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "2000",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "1999",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "1998",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "1997",
"models": [
{
"name": "elise",
"engines": [
"1.8L I4"
],
"submodels": [
"R",
"111 S",
"250 Cup",
"111s",
"111R",
"111",
"1.8",
"SC",
"Base"
]
}
]
},
{
"year": "1994",
"models": [
{
"name": "esprit",
"engines": [
"2.2L I4"
],
"submodels": [
"S4",
"Turbo SE",
"Turbo"
]
}
]
},
{
"year": "1991",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1990",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
},
{
"name": "esprit",
"engines": [
"2.2L I4"
],
"submodels": [
"S4",
"Turbo SE",
"Turbo"
]
}
]
},
{
"year": "1989",
"models": [
{
"name": "esprit",
"engines": [
"2.2L I4"
],
"submodels": [
"S4",
"Turbo SE",
"Turbo"
]
}
]
},
{
"year": "1987",
"models": [
{
"name": "esprit",
"engines": [
"2.2L I4"
],
"submodels": [
"S4",
"Turbo SE",
"Turbo"
]
}
]
},
{
"year": "1972",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1971",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1969",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1967",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1966",
"models": [
{
"name": "elan",
"engines": [
"1.6L I4"
],
"submodels": [
"S4",
"2",
"2S 130",
"Base"
]
}
]
},
{
"year": "1964",
"models": [
{
"name": "seven",
"engines": [],
"submodels": []
}
]
},
{
"year": "1962",
"models": [
{
"name": "super_seven",
"engines": [
"1.5L I4"
],
"submodels": []
}
]
}
]
}

View File

@@ -1,14 +0,0 @@
{
"lucid": [
{
"year": "2023",
"models": [
{
"name": "air",
"engines": [],
"submodels": []
}
]
}
]
}

View File

@@ -1,692 +0,0 @@
{
"maserati": [
{
"year": "2023",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
}
]
},
{
"year": "2022",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "levante",
"engines": [
"3.0L V6"
],
"submodels": [
"S",
"Modena",
"Base"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
}
]
},
{
"year": "2020",
"models": [
{
"name": "levante",
"engines": [
"3.0L V6"
],
"submodels": [
"S",
"Modena",
"Base"
]
}
]
},
{
"year": "2019",
"models": [
{
"name": "levante",
"engines": [
"3.0L V6"
],
"submodels": [
"S",
"Modena",
"Base"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "levante",
"engines": [
"3.0L V6"
],
"submodels": [
"S",
"Modena",
"Base"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "levante",
"engines": [
"3.0L V6"
],
"submodels": [
"S",
"Modena",
"Base"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "ghibli",
"engines": [
"3.0L V6"
],
"submodels": [
"Modena",
"S",
"Base",
"Modena Q4",
"S Q4"
]
},
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
}
]
},
{
"year": "2010",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "granturismo",
"engines": [
"4.2L V8",
"4.7L V8"
],
"submodels": [
"Base",
"S",
"MC",
"1.5 RS CVT Honda SENSING"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "gransport",
"engines": [
"4.2L V8"
],
"submodels": [
"Base"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "gransport",
"engines": [
"4.2L V8"
],
"submodels": [
"Base"
]
},
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "coupe",
"engines": [
"4.2L V8"
],
"submodels": [
"GT"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
},
{
"name": "spyder",
"engines": [
"4.2L V8"
],
"submodels": [
"GT"
]
}
]
},
{
"year": "2003",
"models": [
{
"name": "coupe",
"engines": [
"4.2L V8"
],
"submodels": [
"GT"
]
},
{
"name": "spyder",
"engines": [
"4.2L V8"
],
"submodels": [
"GT"
]
}
]
},
{
"year": "2001",
"models": [
{
"name": "3200gt",
"engines": [],
"submodels": [
"Base"
]
}
]
},
{
"year": "2000",
"models": [
{
"name": "3200gt",
"engines": [],
"submodels": [
"Base"
]
}
]
},
{
"year": "1999",
"models": [
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "1980",
"models": [
{
"name": "quattroporte",
"engines": [
"3.0L V6",
"3.2L V8",
"3.8L V8",
"4.2L V8",
"4.7L V8",
"4.9L V8"
],
"submodels": [
"Executive GT",
"Sport GT S",
"Evoluzione",
"GTS",
"Sport GT",
"S Q4",
"Base"
]
}
]
},
{
"year": "1964",
"models": [
{
"name": "sebring",
"engines": [
"3.5L L6"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,159 +0,0 @@
{
"mclaren": [
{
"year": "2024",
"models": [
{
"name": "artura",
"engines": [
"3.0L V6 PLUG-IN HYBRID EV- (PHEV)"
],
"submodels": []
}
]
},
{
"year": "2022",
"models": [
{
"name": "720s",
"engines": [
"4.0L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "gt",
"engines": [
"4.0L V8"
],
"submodels": []
}
]
},
{
"year": "2019",
"models": [
{
"name": "600lt",
"engines": [
"3.8L V8"
],
"submodels": []
},
{
"name": "720s",
"engines": [
"4.0L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "570s",
"engines": [
"3.8L V8"
],
"submodels": []
},
{
"name": "720s",
"engines": [
"4.0L V8"
],
"submodels": [
"Spider",
"Base"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "570gt",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
},
{
"year": "2016",
"models": [
{
"name": "650s",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
},
{
"year": "2015",
"models": [
{
"name": "650s",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
},
{
"year": "2014",
"models": [
{
"name": "mp4-12c",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
},
{
"year": "2013",
"models": [
{
"name": "mp4-12c",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
},
{
"year": "2012",
"models": [
{
"name": "mp4-12c",
"engines": [
"3.8L V8"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
{
"polestar": [
{
"year": "2022",
"models": [
{
"name": "polestar_2",
"engines": [],
"submodels": []
}
]
},
{
"year": "2021",
"models": [
{
"name": "polestar_1",
"engines": [
"2.0L I4 PLUG-IN HYBRID EV- (PHEV)"
],
"submodels": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
{
"rivian": [
{
"year": "2024",
"models": [
{
"name": "r1t",
"engines": [],
"submodels": []
}
]
}
]
}

View File

@@ -1,171 +0,0 @@
{
"rolls_royce": [
{
"year": "2023",
"models": [
{
"name": "ghost",
"engines": [
"6.7L V12"
],
"submodels": []
}
]
},
{
"year": "2006",
"models": [
{
"name": "phantom",
"engines": [
"6.7L V12"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1997",
"models": [
{
"name": "silver_spur",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1991",
"models": [
{
"name": "silver_spirit",
"engines": [
"6.8L V8"
],
"submodels": []
},
{
"name": "silver_spur",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1990",
"models": [
{
"name": "silver_spirit",
"engines": [
"6.8L V8"
],
"submodels": []
}
]
},
{
"year": "1986",
"models": [
{
"name": "silver_spirit",
"engines": [
"6.8L V8"
],
"submodels": []
}
]
},
{
"year": "1985",
"models": [
{
"name": "silver_spur",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1976",
"models": [
{
"name": "corniche",
"engines": [
"6.8L V8"
],
"submodels": []
}
]
},
{
"year": "1974",
"models": [
{
"name": "silver_shadow",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1972",
"models": [
{
"name": "silver_shadow",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1971",
"models": [
{
"name": "silver_shadow",
"engines": [
"6.8L V8"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "1960",
"models": [
{
"name": "phantom",
"engines": [
"6.7L V12"
],
"submodels": [
"Base"
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,505 +0,0 @@
{
"scion": [
{
"year": "2016",
"models": [
{
"name": "fr-s",
"engines": [
"2.0L H4"
],
"submodels": [
"Series 10",
"Base"
]
},
{
"name": "ia",
"engines": [
"1.5L I4"
],
"submodels": []
},
{
"name": "im",
"engines": [
"1.8L I4"
],
"submodels": []
},
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "fr-s",
"engines": [
"2.0L H4"
],
"submodels": [
"Series 10",
"Base"
]
},
{
"name": "iq",
"engines": [
"1.3L I4"
],
"submodels": [
"Base"
]
},
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "fr-s",
"engines": [
"2.0L H4"
],
"submodels": [
"Series 10",
"Base"
]
},
{
"name": "iq",
"engines": [
"1.3L I4"
],
"submodels": [
"Base"
]
},
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "fr-s",
"engines": [
"2.0L H4"
],
"submodels": [
"Series 10",
"Base"
]
},
{
"name": "iq",
"engines": [
"1.3L I4"
],
"submodels": [
"Base"
]
},
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "iq",
"engines": [
"1.3L I4"
],
"submodels": [
"Base"
]
},
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2010",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xd",
"engines": [
"1.8L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xa",
"engines": [
"1.5L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "tc",
"engines": [
"2.4L I4",
"2.5L I4"
],
"submodels": [
"Spec",
"Base"
]
},
{
"name": "xa",
"engines": [
"1.5L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "xa",
"engines": [
"1.5L I4"
],
"submodels": [
"Base"
]
},
{
"name": "xb",
"engines": [
"1.5L I4",
"2.4L I4"
],
"submodels": [
"Base"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "xa",
"engines": [
"1.5L I4"
],
"submodels": [
"Base"
]
}
]
}
]
}

View File

@@ -1,945 +0,0 @@
{
"smart": [
{
"year": "2020",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2014",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2011",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2010",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2009",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2008",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2007",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2006",
"models": [
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
},
{
"name": "roadster",
"engines": [
"0.7L L3"
],
"submodels": [
"Coupe",
"Roadster",
"Roadster Coupe",
"Base"
]
}
]
},
{
"year": "2005",
"models": [
{
"name": "cabrio",
"engines": [
"0.7L L3"
],
"submodels": [
"Passion"
]
},
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
},
{
"name": "roadster",
"engines": [
"0.7L L3"
],
"submodels": [
"Coupe",
"Roadster",
"Roadster Coupe",
"Base"
]
}
]
},
{
"year": "2004",
"models": [
{
"name": "cabrio",
"engines": [
"0.7L L3"
],
"submodels": [
"Passion"
]
},
{
"name": "city-coupe",
"engines": [
"0.7L L3"
],
"submodels": [
"Base",
"Passion"
]
},
{
"name": "forfour",
"engines": [
"0.9L L3",
"1.0L L3",
"1.5L I4"
],
"submodels": [
"Base",
"W 453",
"Prime",
"Prime Premium",
"Passion",
"Pulse"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
},
{
"name": "roadster",
"engines": [
"0.7L L3"
],
"submodels": [
"Coupe",
"Roadster",
"Roadster Coupe",
"Base"
]
}
]
},
{
"year": "2003",
"models": [
{
"name": "city-coupe",
"engines": [
"0.7L L3"
],
"submodels": [
"Base",
"Passion"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
},
{
"name": "roadster",
"engines": [
"0.7L L3"
],
"submodels": [
"Coupe",
"Roadster",
"Roadster Coupe",
"Base"
]
}
]
},
{
"year": "2002",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2001",
"models": [
{
"name": "city_coupe",
"engines": [],
"submodels": [
"Pulse",
"Passion"
]
},
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "2000",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
},
{
"year": "1999",
"models": [
{
"name": "fortwo",
"engines": [
"0.7L L3",
"0.8L L3",
"0.9L L3",
"1.0L L3"
],
"submodels": [
"Turbo",
"Iceshine",
"Bo Concept",
"EQ",
"Electric Drive",
"Brabus Cabrio",
"Black",
"1.5 RS CVT Honda SENSING",
"Proxy",
"GrandStyle",
"CDI",
"Brabus",
"Prime",
"MHD",
"Pulse",
"Passion Cabrio",
"Pure",
"Base",
"Passion"
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,376 +0,0 @@
{
"tesla": [
{
"year": "2024",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "y",
"engines": [],
"submodels": [
"Mayor",
"Performance",
"Long Range"
]
}
]
},
{
"year": "2023",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "y",
"engines": [],
"submodels": [
"Mayor",
"Performance",
"Long Range"
]
}
]
},
{
"year": "2022",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
},
{
"name": "x",
"engines": [],
"submodels": [
"75D",
"Long Range Plus",
"Plaid",
"100D",
"90D"
]
},
{
"name": "y",
"engines": [],
"submodels": [
"Mayor",
"Performance",
"Long Range"
]
}
]
},
{
"year": "2021",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
},
{
"name": "y",
"engines": [],
"submodels": [
"Mayor",
"Performance",
"Long Range"
]
}
]
},
{
"year": "2020",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
},
{
"name": "x",
"engines": [],
"submodels": [
"75D",
"Long Range Plus",
"Plaid",
"100D",
"90D"
]
},
{
"name": "y",
"engines": [],
"submodels": [
"Mayor",
"Performance",
"Long Range"
]
}
]
},
{
"year": "2019",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "x",
"engines": [],
"submodels": [
"75D",
"Long Range Plus",
"Plaid",
"100D",
"90D"
]
}
]
},
{
"year": "2018",
"models": [
{
"name": "3",
"engines": [],
"submodels": [
"Long Range AWD",
"Performance",
"Standard Plus",
"Base",
"Long Range"
]
},
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
},
{
"name": "x",
"engines": [],
"submodels": [
"75D",
"Long Range Plus",
"Plaid",
"100D",
"90D"
]
}
]
},
{
"year": "2017",
"models": [
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
}
]
},
{
"year": "2016",
"models": [
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
},
{
"name": "x",
"engines": [],
"submodels": [
"75D",
"Long Range Plus",
"Plaid",
"100D",
"90D"
]
}
]
},
{
"year": "2015",
"models": [
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
}
]
},
{
"year": "2013",
"models": [
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
}
]
},
{
"year": "2012",
"models": [
{
"name": "s",
"engines": [],
"submodels": [
"70D",
"85D",
"60",
"75D",
"100D",
"Long Range Plus",
"Base",
"Plaid",
"90D"
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,632 +0,0 @@
-- Engines data import
-- Generated by ETL script
BEGIN;
INSERT INTO engines (id, name) VALUES
(1,'0.6L -2'),
(2,'0.6L -2 BATTERY W/RANGE EXT (BEV REX)'),
(3,'0.6L L2'),
(4,'0.7L L3'),
(5,'0.8L L3'),
(6,'0.9L L3'),
(7,'1.0L I4'),
(8,'1.0L L3'),
(9,'1.0L L3 FULL HYBRID EV- (FHEV)'),
(10,'1.2L H4'),
(11,'1.2L I4'),
(12,'1.2L L3'),
(13,'1.2L L3 FULL HYBRID EV- (FHEV)'),
(14,'1.3L H4'),
(15,'1.3L I4'),
(16,'1.3L I4 ELECTRIC'),
(17,'1.3L I4 FULL HYBRID EV- (FHEV)'),
(18,'1.3L L3'),
(19,'1.3L R2'),
(20,'1.4L I4'),
(21,'1.4L I4 FULL HYBRID EV- (FHEV)'),
(22,'1.4L I4 PLUG-IN HYBRID EV- (PHEV)'),
(23,'1.5L'),
(24,'1.5L H4'),
(25,'1.5L I4'),
(26,'1.5L I4 ELECTRIC'),
(27,'1.5L I4 FULL HYBRID EV- (FHEV)'),
(28,'1.5L I4 MILD HYBRID EV- (MHEV)'),
(29,'1.5L I4 PLUG-IN HYBRID EV- (PHEV)'),
(30,'1.5L L3'),
(31,'1.5L L3 PLUG-IN HYBRID EV- (PHEV)'),
(32,'1.6L H4'),
(33,'1.6L I4'),
(34,'1.6L I4 CNG'),
(35,'1.6L I4 FLEX'),
(36,'1.6L I4 FULL HYBRID EV- (FHEV)'),
(37,'1.6L I4 PLUG-IN HYBRID EV- (PHEV)'),
(38,'1.6L L3'),
(39,'1.6L Turbo'),
(40,'1.7L H4'),
(41,'1.7L I4'),
(42,'1.7L I4 CNG'),
(43,'1.8L H4'),
(44,'1.8L I4'),
(45,'1.8L I4 CNG'),
(46,'1.8L I4 ELECTRIC'),
(47,'1.8L I4 FLEX'),
(48,'1.8L I4 FULL HYBRID EV- (FHEV)'),
(49,'1.8L I4 PLUG-IN HYBRID EV- (PHEV)'),
(50,'1.9L H4'),
(51,'1.9L I4'),
(52,'2 0.9L'),
(53,'2-ROTOR ROTARY 1.3L'),
(54,'2.0L H4'),
(55,'2.0L H4 FULL HYBRID EV- (FHEV)'),
(56,'2.0L H4 PLUG-IN HYBRID EV- (PHEV)'),
(57,'2.0L H6'),
(58,'2.0L I4'),
(59,'2.0L I4 BI-FUEL'),
(60,'2.0L I4 FLEX'),
(61,'2.0L I4 FULL HYBRID EV- (FHEV)'),
(62,'2.0L I4 MILD HYBRID EV- (MHEV)'),
(63,'2.0L I4 PLUG-IN HYBRID EV- (PHEV)'),
(64,'2.0L I6'),
(65,'2.0L L5'),
(66,'2.0L Turbo'),
(67,'2.0L V4'),
(68,'2.1L H4'),
(69,'2.1L I4'),
(70,'2.2L H4'),
(71,'2.2L H6'),
(72,'2.2L I4'),
(73,'2.2L I4 BI-FUEL'),
(74,'2.2L I4 CNG'),
(75,'2.2L I4 FLEX'),
(76,'2.2L I5'),
(77,'2.2L I6'),
(78,'2.3L H6'),
(79,'2.3L I4'),
(80,'2.3L I4 FULL HYBRID EV- (FHEV)'),
(81,'2.3L L5'),
(82,'2.3L V6'),
(83,'2.4L H4'),
(84,'2.4L I4'),
(85,'2.4L I4 FLEX'),
(86,'2.4L I4 FULL HYBRID EV- (FHEV)'),
(87,'2.4L I4 MILD HYBRID EV- (MHEV)'),
(88,'2.4L I4 PLUG-IN HYBRID EV- (PHEV)'),
(89,'2.4L L5'),
(90,'2.4L L6'),
(91,'2.4L Turbo'),
(92,'2.5L'),
(93,'2.5L H4'),
(94,'2.5L H6'),
(95,'2.5L I4'),
(96,'2.5L I4 CNG'),
(97,'2.5L I4 FLEX'),
(98,'2.5L I4 FULL HYBRID EV- (FHEV)'),
(99,'2.5L I4 LPG'),
(100,'2.5L I4 MILD HYBRID EV- (MHEV)'),
(101,'2.5L I4 PLUG-IN HYBRID EV- (PHEV)'),
(102,'2.5L I5'),
(103,'2.5L I6'),
(104,'2.5L L5'),
(105,'2.5L V6'),
(106,'2.6L I4'),
(107,'2.7L H6'),
(108,'2.7L I4'),
(109,'2.7L I6'),
(110,'2.7L L5'),
(111,'2.7L V6'),
(112,'2.7L V6 FLEX'),
(113,'2.8L I4'),
(114,'2.8L I6'),
(115,'2.8L L6'),
(116,'2.8L V6'),
(117,'2.9L H6'),
(118,'2.9L I4'),
(119,'2.9L L6'),
(120,'2.9L V6'),
(121,'2.9L V6 MILD HYBRID EV- (MHEV)'),
(122,'2.9L V6 PLUG-IN HYBRID EV- (PHEV)'),
(123,'3 0.9L Turbo'),
(124,'3 1.0L'),
(125,'3 1.2L'),
(126,'3.0L H6'),
(127,'3.0L I4'),
(128,'3.0L I6'),
(129,'3.0L I6 FULL HYBRID EV- (FHEV)'),
(130,'3.0L I6 MILD HYBRID EV- (MHEV)'),
(131,'3.0L I6 PLUG-IN HYBRID EV- (PHEV)'),
(132,'3.0L L6'),
(133,'3.0L L6 MILD HYBRID EV- (MHEV)'),
(134,'3.0L L6 PLUG-IN HYBRID EV- (PHEV)'),
(135,'3.0L V6'),
(136,'3.0L V6 FLEX'),
(137,'3.0L V6 FULL HYBRID EV- (FHEV)'),
(138,'3.0L V6 MILD HYBRID EV- (MHEV)'),
(139,'3.0L V6 PLUG-IN HYBRID EV- (PHEV)'),
(140,'3.0L V8'),
(141,'3.1L V6'),
(142,'3.2L H6'),
(143,'3.2L I6'),
(144,'3.2L L5'),
(145,'3.2L L6'),
(146,'3.2L V6'),
(147,'3.2L V8'),
(148,'3.3L H6'),
(149,'3.3L L6'),
(150,'3.3L V6'),
(151,'3.3L V6 CNG'),
(152,'3.3L V6 FLEX'),
(153,'3.3L V6 FULL HYBRID EV- (FHEV)'),
(154,'3.4L H6'),
(155,'3.4L I4'),
(156,'3.4L V6'),
(157,'3.4L V6 MILD HYBRID EV- (MHEV)'),
(158,'3.4L V8'),
(159,'3.5L I5'),
(160,'3.5L I6'),
(161,'3.5L L5'),
(162,'3.5L Turbo'),
(163,'3.5L V6'),
(164,'3.5L V6 FLEX'),
(165,'3.5L V6 FULL HYBRID EV- (FHEV)'),
(166,'3.5L V6 MILD HYBRID EV- (MHEV)'),
(167,'3.5L V8'),
(168,'3.6L'),
(169,'3.6L H6'),
(170,'3.6L I6'),
(171,'3.6L L6'),
(172,'3.6L V6'),
(173,'3.6L V6 BI-FUEL'),
(174,'3.6L V6 FLEX'),
(175,'3.6L V6 MILD HYBRID EV- (MHEV)'),
(176,'3.6L V8'),
(177,'3.7L H6'),
(178,'3.7L I5'),
(179,'3.7L L5'),
(180,'3.7L L6'),
(181,'3.7L V6'),
(182,'3.7L V6 CNG'),
(183,'3.7L V6 FLEX'),
(184,'3.7L V6 LPG'),
(185,'3.7L V8'),
(186,'3.8L H6'),
(187,'3.8L I6'),
(188,'3.8L V6'),
(189,'3.8L V6 FLEX'),
(190,'3.8L V8'),
(191,'3.9L L6'),
(192,'3.9L V6'),
(193,'3.9L V6 FLEX'),
(194,'3.9L V8'),
(195,'4 1.0L'),
(196,'4 1.2L'),
(197,'4 1.3L'),
(198,'4 1.4L'),
(199,'4 1.4L Turbo'),
(200,'4 1.6L'),
(201,'4 1.6L Turbo'),
(202,'4 1.8L'),
(203,'4 1.8L Turbo'),
(204,'4 2.0L'),
(205,'4 2.4L'),
(206,'4 2.5L'),
(207,'4 IN-LINE 1.8L'),
(208,'4 INLINE 1.5L'),
(209,'4 INLINE 2.4L'),
(210,'4.0L H6'),
(211,'4.0L I4'),
(212,'4.0L L6'),
(213,'4.0L V6'),
(214,'4.0L V6 FLEX'),
(215,'4.0L V8'),
(216,'4.0L V8 MILD HYBRID EV- (MHEV)'),
(217,'4.0L V8 PLUG-IN HYBRID EV- (PHEV)'),
(218,'4.0L W8'),
(219,'4.1L I6'),
(220,'4.1L L6'),
(221,'4.1L V6'),
(222,'4.1L V8'),
(223,'4.2L I6'),
(224,'4.2L L6'),
(225,'4.2L V6'),
(226,'4.2L V8'),
(227,'4.3L V6'),
(228,'4.3L V6 FLEX'),
(229,'4.3L V8'),
(230,'4.4L V8'),
(231,'4.4L V8 MILD HYBRID EV- (MHEV)'),
(232,'4.4L V8 PLUG-IN HYBRID EV- (PHEV)'),
(233,'4.5L L6'),
(234,'4.5L V8'),
(235,'4.6L V8'),
(236,'4.6L V8 CNG'),
(237,'4.6L V8 FLEX'),
(238,'4.7L V8'),
(239,'4.7L V8 FLEX'),
(240,'4.8L V8'),
(241,'4.8L V8 FLEX'),
(242,'4.9L L6'),
(243,'4.9L L6 BI-FUEL'),
(244,'4.9L V10'),
(245,'4.9L V8'),
(246,'5 2.0L Turbo'),
(247,'5 2.4L Turbo'),
(248,'5.0L V10'),
(249,'5.0L V12'),
(250,'5.0L V8'),
(251,'5.0L V8 FLEX'),
(252,'5.1L V8'),
(253,'5.2L V10'),
(254,'5.2L V12'),
(255,'5.2L V8'),
(256,'5.2L V8 CNG'),
(257,'5.3L V8'),
(258,'5.3L V8 FLEX'),
(259,'5.3L V8 FULL HYBRID EV- (FHEV)'),
(260,'5.3L V8 MILD HYBRID EV- (MHEV)'),
(261,'5.4L V12'),
(262,'5.4L V8'),
(263,'5.4L V8 BI-FUEL'),
(264,'5.4L V8 CNG'),
(265,'5.4L V8 FLEX'),
(266,'5.4L V8 LPG'),
(267,'5.5L V8'),
(268,'5.6L V8'),
(269,'5.6L V8 FLEX'),
(270,'5.7L V12'),
(271,'5.7L V8'),
(272,'5.7L V8 CNG'),
(273,'5.7L V8 FLEX'),
(274,'5.7L V8 FULL HYBRID EV- (FHEV)'),
(275,'5.7L V8 MILD HYBRID EV- (MHEV)'),
(276,'5.8L V8'),
(277,'5.9L L6'),
(278,'5.9L V8'),
(279,'6 3.0L'),
(280,'6 3.0L Turbo'),
(281,'6 3.2L'),
(282,'6 3.5L'),
(283,'6 3.7L'),
(284,'6 4.3L'),
(285,'6.0L L6'),
(286,'6.0L V12'),
(287,'6.0L V8'),
(288,'6.0L V8 BI-FUEL'),
(289,'6.0L V8 CNG'),
(290,'6.0L V8 ELECTRIC/FLEX'),
(291,'6.0L V8 FLEX'),
(292,'6.0L V8 FULL HYBRID EV- (FHEV)'),
(293,'6.0L V8 FULL HYBRID EV-FLEX (FHEV)'),
(294,'6.0L W12'),
(295,'6.0L W12 FLEX'),
(296,'6.1L V8'),
(297,'6.2L V12'),
(298,'6.2L V8'),
(299,'6.2L V8 FLEX'),
(300,'6.3L V12'),
(301,'6.3L V8'),
(302,'6.3L W12'),
(303,'6.4L V8'),
(304,'6.5L V8'),
(305,'6.6L'),
(306,'6.6L V12'),
(307,'6.6L V8'),
(308,'6.6L V8 FLEX'),
(309,'6.7L L6'),
(310,'6.7L V12'),
(311,'6.7L V8'),
(312,'6.8L V10'),
(313,'6.8L V10 CNG'),
(314,'6.8L V10 LPG'),
(315,'6.8L V8'),
(316,'6.9L V8'),
(317,'7.0L V8'),
(318,'7.2L V8'),
(319,'7.3L V8'),
(320,'7.4L V8'),
(321,'7.5L V8'),
(322,'7.6L V8'),
(323,'7.7L V8'),
(324,'8 0.6L'),
(325,'8 4.8L'),
(326,'8 5.0L'),
(327,'8 5.3L'),
(328,'8 6.0L'),
(329,'8 6.2L'),
(330,'8 6.6L'),
(331,'8.0L V10'),
(332,'8.1L V8'),
(333,'8.2L V8'),
(334,'8.3L V10'),
(335,'8.4L V10'),
(336,'B4 1.5L'),
(337,'B4 1.6L'),
(338,'B4 2.0L'),
(339,'B4 2.0L Turbo'),
(340,'B4 2.4L'),
(341,'B4 2.4L Turbo'),
(342,'B4 2.5L'),
(343,'B4 2.5L Turbo'),
(344,'B4 3.6L'),
(345,'B6 2.5L'),
(346,'B6 3.0L'),
(347,'B6 3.0L Turbo'),
(348,'B6 3.1L'),
(349,'B6 3.4L'),
(350,'B6 3.6L'),
(351,'B6 3.8L Turbo'),
(352,'B6 4.0L'),
(353,'EV400'),
(354,'Electric'),
(355,'Gas'),
(356,'H4 1.6L'),
(357,'H4 1.6L Turbo'),
(358,'H4 2.0L'),
(359,'H4 2.0L Turbo'),
(360,'H4 2.5L'),
(361,'H4 2.5L Turbo'),
(362,'H6 3.0L'),
(363,'H6 3.0L Turbo'),
(364,'H6 3.4L'),
(365,'H6 3.6L'),
(366,'H6 3.6L Turbo'),
(367,'H6 3.8L'),
(368,'H6 3.8L Turbo'),
(369,'H6 4.0L'),
(370,'H6 4.0L Turbo'),
(371,'Hybrid'),
(372,'L10 6.8L'),
(373,'L2 0.6L'),
(374,'L2 0.9L'),
(375,'L2 0.9L Turbo'),
(376,'L3'),
(377,'L3 0.6L Turbo'),
(378,'L3 0.7L'),
(379,'L3 0.7L Turbo'),
(380,'L3 0.8L'),
(381,'L3 0.8L Turbo'),
(382,'L3 0.9L Turbo'),
(383,'L3 1.0L'),
(384,'L3 1.0L Turbo'),
(385,'L3 1.1L'),
(386,'L3 1.1L Turbo'),
(387,'L3 1.2L'),
(388,'L3 1.2L Turbo'),
(389,'L3 1.4L'),
(390,'L3 1.4L Turbo'),
(391,'L3 1.5L'),
(392,'L3 1.5L Turbo'),
(393,'L3 1.6L Turbo'),
(394,'L4 0.7L'),
(395,'L4 0.9L'),
(396,'L4 1.0L'),
(397,'L4 1.0L Turbo'),
(398,'L4 1.1L'),
(399,'L4 1.1L Turbo'),
(400,'L4 1.2L'),
(401,'L4 1.2L Turbo'),
(402,'L4 1.3L'),
(403,'L4 1.3L Turbo'),
(404,'L4 1.4L'),
(405,'L4 1.4L Turbo'),
(406,'L4 1.5L'),
(407,'L4 1.5L Turbo'),
(408,'L4 1.6L'),
(409,'L4 1.6L Turbo'),
(410,'L4 1.7L'),
(411,'L4 1.7L Turbo'),
(412,'L4 1.8L'),
(413,'L4 1.8L Supercharged'),
(414,'L4 1.8L Turbo'),
(415,'L4 1.9L'),
(416,'L4 1.9L Turbo'),
(417,'L4 2.0L'),
(418,'L4 2.0L Supercharged'),
(419,'L4 2.0L Turbo'),
(420,'L4 2.1L'),
(421,'L4 2.1L Turbo'),
(422,'L4 2.2L'),
(423,'L4 2.2L Turbo'),
(424,'L4 2.3L'),
(425,'L4 2.3L Turbo'),
(426,'L4 2.4L'),
(427,'L4 2.4L Turbo'),
(428,'L4 2.5L'),
(429,'L4 2.5L Supercharged'),
(430,'L4 2.5L Turbo'),
(431,'L4 2.6L'),
(432,'L4 2.7L'),
(433,'L4 2.7L Turbo'),
(434,'L4 2.8L'),
(435,'L4 2.8L Turbo'),
(436,'L4 2.9L'),
(437,'L4 20.0L Turbo'),
(438,'L4 3.0L'),
(439,'L4 3.0L Turbo'),
(440,'L4 3.2L'),
(441,'L4 3.2L Turbo'),
(442,'L4 3.5L'),
(443,'L4 3.6L'),
(444,'L4 3.9L'),
(445,'L4 4.2L Turbo'),
(446,'L5 0.5L Turbo'),
(447,'L5 1.4L Turbo'),
(448,'L5 2.0L'),
(449,'L5 2.0L Turbo'),
(450,'L5 2.3L'),
(451,'L5 2.3L Turbo'),
(452,'L5 2.4L'),
(453,'L5 2.4L Turbo'),
(454,'L5 2.5L'),
(455,'L5 2.5L Turbo'),
(456,'L5 2.7L'),
(457,'L5 3.1L'),
(458,'L5 3.2L Turbo'),
(459,'L5 3.5L'),
(460,'L5 3.6L'),
(461,'L5 3.7L'),
(462,'L6 1.9L'),
(463,'L6 2.0L'),
(464,'L6 2.2L'),
(465,'L6 2.5L'),
(466,'L6 2.6L Turbo'),
(467,'L6 2.8L'),
(468,'L6 2.9L'),
(469,'L6 2.9L Turbo'),
(470,'L6 3.0L'),
(471,'L6 3.0L Turbo'),
(472,'L6 3.1L'),
(473,'L6 3.2L'),
(474,'L6 3.6L'),
(475,'L6 3.7L Turbo'),
(476,'L6 4.0L'),
(477,'L6 4.2L'),
(478,'L6 4.2L Turbo'),
(479,'L6 4.8L Turbo'),
(480,'L6 5.3L'),
(481,'L6 6.7L'),
(482,'L7 3.0L Turbo'),
(483,'R2 1.3L'),
(484,'V* 4.2L Turbo'),
(485,'V10 4.9L'),
(486,'V10 4.9L Turbo'),
(487,'V10 5.0L'),
(488,'V10 5.2L'),
(489,'V10 8.0L'),
(490,'V10 8.3L'),
(491,'V10 8.4L'),
(492,'V12 5.2L Turbo'),
(493,'V12 5.7L'),
(494,'V12 5.9L'),
(495,'V12 5.9L Turbo'),
(496,'V12 6.0L'),
(497,'V12 6.2L'),
(498,'V12 6.3L'),
(499,'V12 6.3L Turbo'),
(500,'V12 6.5L'),
(501,'V4 2.5L Turbo'),
(502,'V5 2.3L'),
(503,'V5 2.5L'),
(504,'V6'),
(505,'V6 0.6L'),
(506,'V6 1.4L'),
(507,'V6 2.0L'),
(508,'V6 2.0L Turbo'),
(509,'V6 2.1L'),
(510,'V6 2.3L'),
(511,'V6 2.3L Turbo'),
(512,'V6 2.4L'),
(513,'V6 2.5L'),
(514,'V6 2.5L Turbo'),
(515,'V6 2.6L'),
(516,'V6 2.7L'),
(517,'V6 2.7L Turbo'),
(518,'V6 2.8L'),
(519,'V6 2.8L Turbo'),
(520,'V6 2.9L'),
(521,'V6 2.9L Turbo'),
(522,'V6 3.0L'),
(523,'V6 3.0L Supercharged'),
(524,'V6 3.0L Turbo'),
(525,'V6 3.1L'),
(526,'V6 3.2L'),
(527,'V6 3.2L Supercharged'),
(528,'V6 3.2L Turbo'),
(529,'V6 3.3L'),
(530,'V6 3.3L Turbo'),
(531,'V6 3.4L'),
(532,'V6 3.4L Turbo'),
(533,'V6 3.5L'),
(534,'V6 3.5L Supercharged'),
(535,'V6 3.5L Turbo'),
(536,'V6 3.6L'),
(537,'V6 3.6L Turbo'),
(538,'V6 3.7L'),
(539,'V6 3.7L Turbo'),
(540,'V6 3.8L'),
(541,'V6 3.8L Supercharged'),
(542,'V6 3.8L Turbo'),
(543,'V6 3.9L'),
(544,'V6 4.0L'),
(545,'V6 4.0L Turbo'),
(546,'V6 4.2L'),
(547,'V6 4.3L'),
(548,'V6 4.6L'),
(549,'V6 4.9L'),
(550,'V6 5.7L'),
(551,'V6 6.0L'),
(552,'V8'),
(553,'V8 0.0L'),
(554,'V8 0.0L Supercharged'),
(555,'V8 3.2L'),
(556,'V8 3.5L'),
(557,'V8 3.6L'),
(558,'V8 3.7L'),
(559,'V8 3.8L'),
(560,'V8 3.8L Turbo'),
(561,'V8 3.9L'),
(562,'V8 3.9L Turbo'),
(563,'V8 4.0L'),
(564,'V8 4.0L Turbo'),
(565,'V8 4.1L'),
(566,'V8 4.1L Turbo'),
(567,'V8 4.2L'),
(568,'V8 4.2L Turbo'),
(569,'V8 4.3L'),
(570,'V8 4.4L'),
(571,'V8 4.4L Supercharged'),
(572,'V8 4.4L Turbo'),
(573,'V8 4.5L'),
(574,'V8 4.5L Turbo'),
(575,'V8 4.6L'),
(576,'V8 4.6L Turbo'),
(577,'V8 4.7L'),
(578,'V8 4.8L'),
(579,'V8 4.8L Turbo'),
(580,'V8 4.9L'),
(581,'V8 5.0L'),
(582,'V8 5.0L Supercharged'),
(583,'V8 5.0L Turbo'),
(584,'V8 5.2L'),
(585,'V8 5.2L Supercharged'),
(586,'V8 5.2L Turbo'),
(587,'V8 5.3L'),
(588,'V8 5.3L Turbo'),
(589,'V8 5.4L'),
(590,'V8 5.4L Supercharged'),
(591,'V8 5.5L'),
(592,'V8 5.5L Turbo'),
(593,'V8 5.6L'),
(594,'V8 5.6L Turbo'),
(595,'V8 5.7L'),
(596,'V8 5.7L Turbo'),
(597,'V8 5.8L'),
(598,'V8 5.8L Supercharged'),
(599,'V8 5.9L'),
(600,'V8 6.0L'),
(601,'V8 6.0L Turbo'),
(602,'V8 6.1L'),
(603,'V8 6.2L'),
(604,'V8 6.2L Supercharged'),
(605,'V8 6.2L Turbo'),
(606,'V8 6.4L'),
(607,'V8 6.4L Turbo'),
(608,'V8 6.5L'),
(609,'V8 6.6L'),
(610,'V8 6.6L Turbo'),
(611,'V8 6.8L Turbo'),
(612,'V8 7.0L'),
(613,'V8 7.3L Turbo'),
(614,'V8 Turbo'),
(615,'VR5 2.3L'),
(616,'VR6 2.8L'),
(617,'VR6 3.6L'),
(618,'W12 6.0L'),
(619,'W12 6.0L Turbo'),
(620,'W12 6.3L'),
(621,'W12 6.3L Turbo'),
(622,'W8 4.0L');
SELECT setval('engines_id_seq', 622);
COMMIT;

View File

@@ -1,29 +0,0 @@
-- Transmissions data import
-- Generated by ETL script
BEGIN;
INSERT INTO transmissions (id, type) VALUES
(1,'1-Speed Automatic'),
(2,'10-Speed Automatic'),
(3,'2-Speed Automatic'),
(4,'3-Speed Automatic'),
(5,'4-Speed Automatic'),
(6,'4-Speed Manual'),
(7,'5-Speed Automatic'),
(8,'5-Speed Manual'),
(9,'6-Speed Automatic'),
(10,'6-Speed Dual-Clutch'),
(11,'6-Speed Manual'),
(12,'7-Speed Automatic'),
(13,'7-Speed Dual-Clutch'),
(14,'7-Speed Manual'),
(15,'8-Speed Automatic'),
(16,'9-Speed Automatic'),
(17,'Automatic'),
(18,'CVT'),
(19,'Manual');
SELECT setval('transmissions_id_seq', 19);
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
============================================================
ETL Statistics
============================================================
Min Year: 2,000
Max Year: 2,026
Vehicle Records: 1,675,335
Engines: 622
Transmissions: 19
Makes: 54
Models: 1,881

View File

@@ -1,112 +0,0 @@
#!/usr/bin/env python3
"""
Post-import QA validation for vehicle dropdown data.
Runs basic duplicate and range checks against the motovaultpro Postgres container.
"""
import os
import subprocess
import sys
def run_psql(query: str) -> str:
cmd = [
"docker",
"exec",
"mvp-postgres",
"psql",
"-U",
"postgres",
"-d",
"motovaultpro",
"-At",
"-c",
query,
]
return subprocess.check_output(cmd, text=True)
def check_container():
try:
subprocess.check_output(["docker", "ps"], text=True)
except Exception:
print("❌ Docker not available.")
sys.exit(1)
try:
containers = subprocess.check_output(
["docker", "ps", "--filter", "name=mvp-postgres", "--format", "{{.Names}}"],
text=True,
).strip()
if not containers:
print("❌ mvp-postgres container not running.")
sys.exit(1)
except Exception as exc:
print(f"❌ Failed to check containers: {exc}")
sys.exit(1)
def main():
check_container()
print("🔍 Running QA checks...\n")
queries = {
"engine_duplicate_names": """
SELECT COUNT(*) FROM (
SELECT LOWER(name) as n, COUNT(*) c
FROM engines
GROUP BY 1 HAVING COUNT(*) > 1
) t;
""",
"transmission_duplicate_types": """
SELECT COUNT(*) FROM (
SELECT LOWER(type) as t, COUNT(*) c
FROM transmissions
GROUP BY 1 HAVING COUNT(*) > 1
) t;
""",
"vehicle_option_duplicates": """
SELECT COUNT(*) FROM (
SELECT year, make, model, trim, engine_id, transmission_id, COUNT(*) c
FROM vehicle_options
GROUP BY 1,2,3,4,5,6 HAVING COUNT(*) > 1
) t;
""",
"year_range": """
SELECT MIN(year) || ' - ' || MAX(year) FROM vehicle_options;
""",
"counts": """
SELECT
(SELECT COUNT(*) FROM engines) AS engines,
(SELECT COUNT(*) FROM transmissions) AS transmissions,
(SELECT COUNT(*) FROM vehicle_options) AS vehicle_options;
""",
}
results = {}
for key, query in queries.items():
try:
results[key] = run_psql(query).strip()
except subprocess.CalledProcessError as exc:
print(f"❌ Query failed ({key}): {exc}")
sys.exit(1)
print(f"Engine duplicate names: {results['engine_duplicate_names']}")
print(f"Transmission duplicate types: {results['transmission_duplicate_types']}")
print(f"Vehicle option duplicates: {results['vehicle_option_duplicates']}")
print(f"Year range: {results['year_range']}")
print(f"Counts (engines, transmissions, vehicle_options): {results['counts']}")
if (
results["engine_duplicate_names"] == "0"
and results["transmission_duplicate_types"] == "0"
and results["vehicle_option_duplicates"] == "0"
):
print("\n✅ QA checks passed.")
else:
print("\n❌ QA checks found issues.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python3
"""
Generate SQL import files from a VehAPI snapshot SQLite database.
Reads observed compatibility pairs from the snapshot (trim-filtered engine<->transmission pairs)
and produces:
- output/01_engines.sql
- output/02_transmissions.sql
- output/03_vehicle_options.sql
No legacy JSON or network calls are used. The snapshot path is provided via CLI flag.
"""
import argparse
import os
import sqlite3
from pathlib import Path
from typing import Dict, Iterable, List, Sequence
BATCH_SIZE = 1000
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate SQL files from a VehAPI snapshot (SQLite).",
)
parser.add_argument(
"--snapshot-path",
type=Path,
default=os.environ.get("SNAPSHOT_PATH"),
help="Path to snapshots/<date>/snapshot.sqlite produced by vehapi_fetch_snapshot.py (or env SNAPSHOT_PATH)",
)
parser.add_argument(
"--output-dir",
type=Path,
default=Path("output"),
help="Directory to write SQL output files (default: output)",
)
return parser.parse_args()
def load_pairs(snapshot_path: Path) -> List[sqlite3.Row]:
if not snapshot_path.exists():
raise FileNotFoundError(f"Snapshot not found: {snapshot_path}")
conn = sqlite3.connect(snapshot_path)
conn.row_factory = sqlite3.Row
try:
cursor = conn.execute(
"""
SELECT
year,
make,
model,
trim,
engine_display,
engine_canon,
engine_bucket,
trans_display,
trans_canon,
trans_bucket
FROM pairs
ORDER BY year, make, model, trim, engine_canon, trans_canon
"""
)
rows = cursor.fetchall()
except sqlite3.Error as exc:
raise RuntimeError(f"Failed to read pairs from snapshot: {exc}") from exc
finally:
conn.close()
if not rows:
raise ValueError("Snapshot contains no rows in pairs table.")
return rows
def choose_engine_label(engine_display: str, engine_bucket: str, engine_canon: str) -> str:
"""
Use VehAPI display string when present, otherwise fall back to the bucket label,
and finally to the canonical key to avoid empty names.
"""
if engine_display:
return engine_display
if engine_bucket:
return engine_bucket
return engine_canon
def choose_trans_label(trans_display: str, trans_bucket: str, trans_canon: str) -> str:
if trans_display:
return trans_display
if trans_bucket:
return trans_bucket
return trans_canon
def build_engine_dimension(rows: Sequence[sqlite3.Row]) -> Dict[str, Dict]:
engines: Dict[str, Dict] = {}
for row in rows:
canon = row["engine_canon"]
if canon is None or canon == "":
raise ValueError(f"Missing engine_canon for row: {dict(row)}")
if canon in engines:
continue
engines[canon] = {
"id": len(engines) + 1,
"name": choose_engine_label(row["engine_display"], row["engine_bucket"], canon),
"fuel_type": row["engine_bucket"] or None,
}
return engines
def build_transmission_dimension(rows: Sequence[sqlite3.Row]) -> Dict[str, Dict]:
transmissions: Dict[str, Dict] = {}
for row in rows:
canon = row["trans_canon"]
if canon is None or canon == "":
raise ValueError(f"Missing trans_canon for row: {dict(row)}")
if canon in transmissions:
continue
transmissions[canon] = {
"id": len(transmissions) + 1,
"type": choose_trans_label(row["trans_display"], row["trans_bucket"], canon),
}
return transmissions
def build_vehicle_options(
rows: Sequence[sqlite3.Row],
engine_map: Dict[str, Dict],
trans_map: Dict[str, Dict],
) -> List[Dict]:
options: List[Dict] = []
for row in rows:
engine_canon = row["engine_canon"]
trans_canon = row["trans_canon"]
options.append(
{
"year": int(row["year"]),
"make": row["make"],
"model": row["model"],
"trim": row["trim"],
"engine_id": engine_map[engine_canon]["id"],
"transmission_id": trans_map[trans_canon]["id"],
}
)
return options
def sql_value(value):
if value is None:
return "NULL"
if isinstance(value, str):
return "'" + value.replace("'", "''") + "'"
return str(value)
def chunked(seq: Iterable[Dict], size: int) -> Iterable[List[Dict]]:
chunk: List[Dict] = []
for item in seq:
chunk.append(item)
if len(chunk) >= size:
yield chunk
chunk = []
if chunk:
yield chunk
def write_insert_file(
path: Path,
table: str,
columns: Sequence[str],
rows: Sequence[Dict],
):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
f.write(f"-- Auto-generated by etl_generate_sql.py\n")
if not rows:
f.write(f"-- No rows for {table}\n")
return
for batch in chunked(rows, BATCH_SIZE):
values_sql = ",\n".join(
"(" + ",".join(sql_value(row[col]) for col in columns) + ")"
for row in batch
)
f.write(f"INSERT INTO {table} ({', '.join(columns)}) VALUES\n{values_sql};\n\n")
def main():
args = parse_args()
snapshot_path: Path = args.snapshot_path
output_dir: Path = args.output_dir
if snapshot_path is None:
raise SystemExit("Snapshot path is required. Pass --snapshot-path or set SNAPSHOT_PATH.")
print(f"Reading snapshot: {snapshot_path}")
rows = load_pairs(snapshot_path)
years = sorted({int(row["year"]) for row in rows})
print(f" Loaded {len(rows):,} observed engine<->transmission pairs across {len(years)} years")
engines = build_engine_dimension(rows)
transmissions = build_transmission_dimension(rows)
vehicle_options = build_vehicle_options(rows, engines, transmissions)
print(f"Engines: {len(engines):,}")
print(f"Transmissions: {len(transmissions):,}")
print(f"Vehicle options (observed pairs): {len(vehicle_options):,}")
write_insert_file(
output_dir / "01_engines.sql",
"engines",
["id", "name", "fuel_type"],
engines.values(),
)
write_insert_file(
output_dir / "02_transmissions.sql",
"transmissions",
["id", "type"],
transmissions.values(),
)
write_insert_file(
output_dir / "03_vehicle_options.sql",
"vehicle_options",
["year", "make", "model", "trim", "engine_id", "transmission_id"],
vehicle_options,
)
print("\nSQL files generated:")
print(f" - {output_dir / '01_engines.sql'}")
print(f" - {output_dir / '02_transmissions.sql'}")
print(f" - {output_dir / '03_vehicle_options.sql'}")
print(f"\nYear coverage: {years[0]}-{years[-1]}")
if __name__ == "__main__":
main()

View File

@@ -1,37 +1,37 @@
#!/bin/bash
# Import generated SQL files into PostgreSQL database
# Run this after etl_generate_sql.py has created the SQL files
# Offline import of generated SQL files into PostgreSQL (no network).
set -e
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "=========================================="
echo "📥 Automotive Database Import"
echo "📥 Automotive Database Import (offline)"
echo "=========================================="
echo ""
# Check if Docker container is running
require_file() {
if [ ! -f "$1" ]; then
echo "❌ Missing required file: $1"
exit 1
fi
}
if ! docker ps --filter "name=mvp-postgres" --format "{{.Names}}" | grep -q "mvp-postgres"; then
echo "❌ Error: mvp-postgres container is not running"
exit 1
fi
echo "✓ Docker container mvp-postgres is running"
echo ""
require_file "output/01_engines.sql"
require_file "output/02_transmissions.sql"
require_file "output/03_vehicle_options.sql"
# Check if output directory exists
if [ ! -d "output" ]; then
echo "❌ Error: output directory not found"
echo "Please run etl_generate_sql.py first to generate SQL files"
exit 1
fi
# Run schema migration first
echo "📋 Step 1: Running database schema migration..."
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < migrations/001_create_vehicle_database.sql
echo "✓ Schema migration completed"
echo ""
# Truncate tables for a clean rerun
echo "🧹 Step 2: Truncating existing data..."
docker exec -i mvp-postgres psql -U postgres -d motovaultpro <<'EOF'
TRUNCATE TABLE vehicle_options RESTART IDENTITY CASCADE;
@@ -41,38 +41,31 @@ EOF
echo "✓ Tables truncated"
echo ""
# Import engines
echo "📥 Step 3: Importing engines..."
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/01_engines.sql
echo "✓ Engines imported"
echo ""
# Import transmissions
echo "📥 Step 4: Importing transmissions..."
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/02_transmissions.sql
echo "✓ Transmissions imported"
echo ""
# Import vehicle options
echo "📥 Step 5: Importing vehicle options (this may take a minute)..."
echo "📥 Step 5: Importing vehicle options (observed pairs only)..."
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/03_vehicle_options.sql
echo "✓ Vehicle options imported"
echo ""
# Verify data
echo "=========================================="
echo "✅ Import completed successfully!"
echo "✅ Import completed"
echo "=========================================="
echo ""
echo "🔍 Database verification:"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as engine_count FROM engines;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as transmission_count FROM transmissions;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as vehicle_count FROM vehicle_options;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as engines FROM engines;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as transmissions FROM transmissions;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT COUNT(*) as vehicle_options FROM vehicle_options;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT MIN(year) as min_year, MAX(year) as max_year FROM vehicle_options;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT DISTINCT year FROM vehicle_options ORDER BY year LIMIT 5;"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT DISTINCT year FROM vehicle_options ORDER BY year DESC LIMIT 5;"
echo ""
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT * FROM available_years;"
echo ""
echo "📊 Sample query - 2024 makes:"
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT * FROM get_makes_for_year(2024) LIMIT 10;"
echo ""
echo "✓ Database is ready for use!"
echo "✓ Database ready for dropdown use."

View File

@@ -72,6 +72,8 @@ CREATE INDEX idx_vehicle_trim ON vehicle_options(trim);
CREATE INDEX idx_vehicle_year_make ON vehicle_options(year, make);
CREATE INDEX idx_vehicle_year_make_model ON vehicle_options(year, make, model);
CREATE INDEX idx_vehicle_year_make_model_trim ON vehicle_options(year, make, model, trim);
CREATE INDEX idx_vehicle_year_make_model_trim_engine ON vehicle_options(year, make, model, trim, engine_id);
CREATE INDEX idx_vehicle_year_make_model_trim_trans ON vehicle_options(year, make, model, trim, transmission_id);
-- Views for dropdown queries
@@ -189,6 +191,91 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
-- Helper functions for trim-level options and pair-safe filtering
CREATE OR REPLACE FUNCTION get_transmissions_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
RETURNS TABLE(
transmission_id INTEGER,
transmission_type VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
t.id,
t.type
FROM vehicle_options vo
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
ORDER BY t.type ASC;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_engines_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
RETURNS TABLE(
engine_id INTEGER,
engine_name VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
e.id,
e.name
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
ORDER BY e.name ASC;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_transmissions_for_vehicle_engine(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR, p_engine_name VARCHAR)
RETURNS TABLE(
transmission_id INTEGER,
transmission_type VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
t.id,
t.type
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
AND e.name = p_engine_name
ORDER BY t.type ASC;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_engines_for_vehicle_trans(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR, p_trans_type VARCHAR)
RETURNS TABLE(
engine_id INTEGER,
engine_name VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
e.id,
e.name
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
AND t.type = p_trans_type
ORDER BY e.name ASC;
END;
$$ LANGUAGE plpgsql;
COMMENT ON TABLE vehicle_options IS 'Denormalized table optimized for cascading dropdown queries';
COMMENT ON TABLE engines IS 'Engine specifications with detailed technical data';
COMMENT ON TABLE transmissions IS 'Transmission specifications';

View File

@@ -0,0 +1,22 @@
-- Auto-generated by etl_generate_sql.py
INSERT INTO engines (id, name, fuel_type) VALUES
(1,'Gas','Gas'),
(2,'2.0L 150 hp I4','Gas'),
(3,'2.4L 201 hp I4','Gas'),
(4,'3.5L 290 hp V6','Gas'),
(5,'3.5L 273 hp V6','Gas'),
(6,'3.5L 310 hp V6','Gas'),
(7,'2.4L 206 hp I4','Gas'),
(8,'2.0L 220 hp I4','Gas'),
(9,'1.8L 170 hp I4','Gas'),
(10,'Diesel','Diesel'),
(11,'2.0L 150 hp I4 Diesel','Diesel'),
(12,'2.0L 220 hp I4 Flex Fuel Vehicle','Gas'),
(13,'3.0L 310 hp V6','Gas'),
(14,'3.0L 240 hp V6 Diesel','Diesel'),
(15,'4.0L 435 hp V8','Diesel'),
(16,'3.0L 333 hp V6','Gas'),
(17,'6.3L 500 hp W12','Gas'),
(18,'2.0L 200 hp I4','Gas'),
(19,'3.0L 272 hp V6','Gas');

View File

@@ -0,0 +1,13 @@
-- Auto-generated by etl_generate_sql.py
INSERT INTO transmissions (id, type) VALUES
(1,'Automatic'),
(2,'Manual'),
(3,'5-Speed Automatic'),
(4,'6-Speed Manual'),
(5,'6-Speed Automatic'),
(6,'8-Speed Dual Clutch'),
(7,'9-Speed Automatic'),
(8,'6-Speed Dual Clutch'),
(9,'8-Speed Automatic'),
(10,'Continuously Variable Transmission');

View File

@@ -0,0 +1,281 @@
-- Auto-generated by etl_generate_sql.py
INSERT INTO vehicle_options (year, make, model, trim, engine_id, transmission_id) VALUES
(2015,'Acura','ILX','2.0L',1,1),
(2015,'Acura','ILX','2.0L',1,2),
(2015,'Acura','ILX','2.0L FWD',2,3),
(2015,'Acura','ILX','2.0L FWD with Premium Package',2,3),
(2015,'Acura','ILX','2.0L FWD with Technology Package',2,3),
(2015,'Acura','ILX','2.0L Technology',1,1),
(2015,'Acura','ILX','2.0L Technology',1,2),
(2015,'Acura','ILX','2.0L w/Premium Package',1,1),
(2015,'Acura','ILX','2.0L w/Premium Package',1,2),
(2015,'Acura','ILX','2.4L FWD with Premium Package',2,3),
(2015,'Acura','ILX','2.4L FWD with Premium Package',3,4),
(2015,'Acura','ILX','2.4L w/Premium Package',1,1),
(2015,'Acura','ILX','2.4L w/Premium Package',1,2),
(2015,'Acura','ILX','FWD with Dynamic Package',2,3),
(2015,'Acura','MDX','3.5L',1,1),
(2015,'Acura','MDX','3.5L',1,2),
(2015,'Acura','MDX','3.5L Advance Pkg w/Entertainment Pkg',1,1),
(2015,'Acura','MDX','3.5L Advance Pkg w/Entertainment Pkg',1,2),
(2015,'Acura','MDX','3.5L Technology Package',1,1),
(2015,'Acura','MDX','3.5L Technology Package',1,2),
(2015,'Acura','MDX','3.5L Technology Pkg/w Entertainment Pkg',1,1),
(2015,'Acura','MDX','3.5L Technology Pkg/w Entertainment Pkg',1,2),
(2015,'Acura','MDX','3.5L w/Technology & Entertainment Pkgs',1,1),
(2015,'Acura','MDX','3.5L w/Technology & Entertainment Pkgs',1,2),
(2015,'Acura','MDX','FWD',4,5),
(2015,'Acura','MDX','FWD with Advance and Entertainment Package',4,5),
(2015,'Acura','MDX','FWD with Technology Package',4,5),
(2015,'Acura','MDX','FWD with Technology and Entertainment Package',4,5),
(2015,'Acura','MDX','SH-AWD',4,5),
(2015,'Acura','MDX','SH-AWD with Advance and Entertainment Package',4,5),
(2015,'Acura','MDX','SH-AWD with Elite Package',4,5),
(2015,'Acura','MDX','SH-AWD with Navigation',4,5),
(2015,'Acura','MDX','SH-AWD with Technology Package',4,5),
(2015,'Acura','MDX','SH-AWD with Technology and Entertainment Package',4,5),
(2015,'Acura','RDX','AWD',5,5),
(2015,'Acura','RDX','AWD with Technology Package',5,5),
(2015,'Acura','RDX','Base',1,1),
(2015,'Acura','RDX','Base',1,2),
(2015,'Acura','RDX','FWD',5,5),
(2015,'Acura','RDX','FWD with Technology Package',5,5),
(2015,'Acura','RDX','Technology Package',1,1),
(2015,'Acura','RDX','Technology Package',1,2),
(2015,'Acura','RLX','Advance Package',1,1),
(2015,'Acura','RLX','Advance Package',1,2),
(2015,'Acura','RLX','Base',1,1),
(2015,'Acura','RLX','Base',1,2),
(2015,'Acura','RLX','FWD',6,5),
(2015,'Acura','RLX','FWD',1,1),
(2015,'Acura','RLX','FWD',1,2),
(2015,'Acura','RLX','FWD with Advance Package',6,5),
(2015,'Acura','RLX','FWD with Elite Package',6,5),
(2015,'Acura','RLX','FWD with Krell Audio Package',6,5),
(2015,'Acura','RLX','FWD with Navigation',6,5),
(2015,'Acura','RLX','FWD with Technology Package',6,5),
(2015,'Acura','RLX','Navigation',1,1),
(2015,'Acura','RLX','Navigation',1,2),
(2015,'Acura','RLX','Technology Package',1,1),
(2015,'Acura','RLX','Technology Package',1,2),
(2015,'Acura','RLX Hybrid Sport','SH-AWD',1,1),
(2015,'Acura','RLX Hybrid Sport','SH-AWD',1,2),
(2015,'Acura','TLX','Base',1,1),
(2015,'Acura','TLX','Base',1,2),
(2015,'Acura','TLX','FWD',7,6),
(2015,'Acura','TLX','FWD with Technology Package',7,6),
(2015,'Acura','TLX','SH-AWD with Elite Package',4,7),
(2015,'Acura','TLX','Tech',1,1),
(2015,'Acura','TLX','Tech',1,2),
(2015,'Acura','TLX','V6',1,1),
(2015,'Acura','TLX','V6',1,2),
(2015,'Acura','TLX','V6 Advance',1,1),
(2015,'Acura','TLX','V6 Advance',1,2),
(2015,'Acura','TLX','V6 FWD',4,7),
(2015,'Acura','TLX','V6 FWD with Advance Package',4,7),
(2015,'Acura','TLX','V6 FWD with Technology Package',4,7),
(2015,'Acura','TLX','V6 SH-AWD',4,7),
(2015,'Acura','TLX','V6 SH-AWD with Advance Package',4,7),
(2015,'Acura','TLX','V6 SH-AWD with Technology Package',4,7),
(2015,'Acura','TLX','V6 Tech',1,1),
(2015,'Acura','TLX','V6 Tech',1,2),
(2015,'Acura','TLX','V6 with Elite Package',4,7),
(2015,'Audi','A3','1.8T Komfort Sedan FWD',8,8),
(2015,'Audi','A3','1.8T Premium',1,1),
(2015,'Audi','A3','1.8T Premium',1,2),
(2015,'Audi','A3','1.8T Premium Cabriolet FWD',9,8),
(2015,'Audi','A3','1.8T Premium Plus',1,1),
(2015,'Audi','A3','1.8T Premium Plus',1,2),
(2015,'Audi','A3','1.8T Premium Plus Cabriolet FWD',9,8),
(2015,'Audi','A3','1.8T Premium Plus Sedan FWD',9,8),
(2015,'Audi','A3','1.8T Premium Sedan FWD',9,8),
(2015,'Audi','A3','1.8T Prestige',1,1),
(2015,'Audi','A3','1.8T Prestige',1,2),
(2015,'Audi','A3','1.8T Prestige Cabriolet FWD',9,8),
(2015,'Audi','A3','1.8T Prestige Sedan FWD',9,8),
(2015,'Audi','A3','1.8T Prestige Sedan FWD',8,8),
(2015,'Audi','A3','1.8T Progressiv Sedan FWD',8,8),
(2015,'Audi','A3','2.0 TDI Premium',10,1),
(2015,'Audi','A3','2.0 TDI Premium',10,2),
(2015,'Audi','A3','2.0 TDI Premium Plus',10,1),
(2015,'Audi','A3','2.0 TDI Premium Plus',10,2),
(2015,'Audi','A3','2.0 TDI Premium Plus Sedan FWD',11,8),
(2015,'Audi','A3','2.0 TDI Premium Sedan FWD',11,8),
(2015,'Audi','A3','2.0 TDI Prestige',10,1),
(2015,'Audi','A3','2.0 TDI Prestige',10,2),
(2015,'Audi','A3','2.0 TDI Prestige Sedan FWD',11,8),
(2015,'Audi','A3','2.0T Premium',1,1),
(2015,'Audi','A3','2.0T Premium',1,2),
(2015,'Audi','A3','2.0T Premium Plus',1,1),
(2015,'Audi','A3','2.0T Premium Plus',1,2),
(2015,'Audi','A3','2.0T Prestige',1,1),
(2015,'Audi','A3','2.0T Prestige',1,2),
(2015,'Audi','A3','2.0T quattro Komfort Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Komfort Sedan AWD',8,8),
(2015,'Audi','A3','2.0T quattro Premium Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Premium Plus Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Premium Plus Sedan AWD',8,8),
(2015,'Audi','A3','2.0T quattro Premium Sedan AWD',8,8),
(2015,'Audi','A3','2.0T quattro Prestige Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Prestige Sedan AWD',8,8),
(2015,'Audi','A3','2.0T quattro Progressiv Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Progressiv Sedan AWD',8,8),
(2015,'Audi','A3','2.0T quattro Technik Cabriolet AWD',8,8),
(2015,'Audi','A3','2.0T quattro Technik FWD',1,1),
(2015,'Audi','A3','2.0T quattro Technik FWD',1,2),
(2015,'Audi','A3','2.0T quattro Technik Sedan AWD',8,8),
(2015,'Audi','A3','TDI Komfort Sedan FWD',8,8),
(2015,'Audi','A3','TDI Progressiv Sedan FWD',8,8),
(2015,'Audi','A3','TDI Technik Sedan FWD',8,8),
(2015,'Audi','A4','2.0T FrontTrak Komfort FWD',8,4),
(2015,'Audi','A4','2.0T FrontTrak Komfort FWD',8,9),
(2015,'Audi','A4','2.0T Premium',1,1),
(2015,'Audi','A4','2.0T Premium',1,2),
(2015,'Audi','A4','2.0T Premium FWD',8,10),
(2015,'Audi','A4','2.0T Premium Plus',1,1),
(2015,'Audi','A4','2.0T Premium Plus',1,2),
(2015,'Audi','A4','2.0T Premium Plus FWD',8,10),
(2015,'Audi','A4','2.0T Premium Plus Sedan FWD',8,10),
(2015,'Audi','A4','2.0T Premium Sedan FWD',8,10),
(2015,'Audi','A4','2.0T Prestige',1,1),
(2015,'Audi','A4','2.0T Prestige',1,2),
(2015,'Audi','A4','2.0T Prestige FWD',8,4),
(2015,'Audi','A4','2.0T Prestige FWD',8,9),
(2015,'Audi','A4','2.0T Prestige Sedan FWD',8,10),
(2015,'Audi','A4','2.0T quattro Komfort AWD',8,4),
(2015,'Audi','A4','2.0T quattro Komfort AWD',8,9),
(2015,'Audi','A4','2.0T quattro Premium AWD',8,4),
(2015,'Audi','A4','2.0T quattro Premium AWD',8,9),
(2015,'Audi','A4','2.0T quattro Premium Plus AWD',8,4),
(2015,'Audi','A4','2.0T quattro Premium Plus AWD',8,9),
(2015,'Audi','A4','2.0T quattro Premium Plus Sedan AWD',8,4),
(2015,'Audi','A4','2.0T quattro Premium Plus Sedan AWD',8,9),
(2015,'Audi','A4','2.0T quattro Premium Sedan AWD',8,4),
(2015,'Audi','A4','2.0T quattro Premium Sedan AWD',8,9),
(2015,'Audi','A4','2.0T quattro Prestige AWD',8,4),
(2015,'Audi','A4','2.0T quattro Prestige AWD',8,9),
(2015,'Audi','A4','2.0T quattro Prestige Sedan AWD',8,4),
(2015,'Audi','A4','2.0T quattro Prestige Sedan AWD',8,9),
(2015,'Audi','A4','2.0T quattro Progressiv AWD',8,4),
(2015,'Audi','A4','2.0T quattro Progressiv AWD',8,9),
(2015,'Audi','A4','2.0T quattro Technik AWD',8,4),
(2015,'Audi','A4','2.0T quattro Technik AWD',8,9),
(2015,'Audi','A4 Allroad','2.0T quattro Komfort AWD',12,9),
(2015,'Audi','A4 Allroad','2.0T quattro Premium AWD',8,9),
(2015,'Audi','A4 Allroad','2.0T quattro Premium AWD',12,9),
(2015,'Audi','A4 Allroad','2.0T quattro Premium Plus AWD',8,9),
(2015,'Audi','A4 Allroad','2.0T quattro Premium Plus AWD',12,9),
(2015,'Audi','A4 Allroad','2.0T quattro Prestige AWD',8,9),
(2015,'Audi','A4 Allroad','2.0T quattro Prestige AWD',12,9),
(2015,'Audi','A4 Allroad','2.0T quattro Progressiv AWD',12,9),
(2015,'Audi','A4 Allroad','2.0T quattro Technik AWD',12,9),
(2015,'Audi','A5','2.0T Premium',1,1),
(2015,'Audi','A5','2.0T Premium',1,2),
(2015,'Audi','A5','2.0T Premium Plus',1,1),
(2015,'Audi','A5','2.0T Premium Plus',1,2),
(2015,'Audi','A5','2.0T quattro Komfort Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Komfort Coupe AWD',8,9),
(2015,'Audi','A5','2.0T quattro Premium Cabriolet AWD',8,9),
(2015,'Audi','A5','2.0T quattro Premium Cabriolet AWD',12,9),
(2015,'Audi','A5','2.0T quattro Premium Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Premium Coupe AWD',8,9),
(2015,'Audi','A5','2.0T quattro Premium Plus Cabriolet AWD',8,9),
(2015,'Audi','A5','2.0T quattro Premium Plus Cabriolet AWD',12,9),
(2015,'Audi','A5','2.0T quattro Premium Plus Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Premium Plus Coupe AWD',8,9),
(2015,'Audi','A5','2.0T quattro Prestige Cabriolet AWD',8,4),
(2015,'Audi','A5','2.0T quattro Prestige Cabriolet AWD',8,9),
(2015,'Audi','A5','2.0T quattro Prestige Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Prestige Coupe AWD',8,9),
(2015,'Audi','A5','2.0T quattro Progressiv Cabriolet AWD',8,4),
(2015,'Audi','A5','2.0T quattro Progressiv Cabriolet AWD',8,9),
(2015,'Audi','A5','2.0T quattro Progressiv Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Progressiv Coupe AWD',8,9),
(2015,'Audi','A5','2.0T quattro Progressiv Coupe AWD',1,1),
(2015,'Audi','A5','2.0T quattro Progressiv Coupe AWD',1,2),
(2015,'Audi','A5','2.0T quattro Technik Cabriolet AWD',8,4),
(2015,'Audi','A5','2.0T quattro Technik Cabriolet AWD',8,9),
(2015,'Audi','A5','2.0T quattro Technik Coupe AWD',8,4),
(2015,'Audi','A5','2.0T quattro Technik Coupe AWD',8,9),
(2015,'Audi','A6','2.0T Premium',1,1),
(2015,'Audi','A6','2.0T Premium',1,2),
(2015,'Audi','A6','2.0T Premium Plus Sedan FWD',8,10),
(2015,'Audi','A6','2.0T Premium Sedan FWD',8,10),
(2015,'Audi','A6','2.0T Premium Sedan FWD',13,9),
(2015,'Audi','A6','2.0T quattro Premium Plus Sedan AWD',8,9),
(2015,'Audi','A6','2.0T quattro Premium Sedan AWD',8,9),
(2015,'Audi','A6','2.0T quattro Progressiv Sedan AWD',13,9),
(2015,'Audi','A6','2.0T quattro Technik Sedan AWD',13,9),
(2015,'Audi','A6','3.0 TDI Premium Plus',10,1),
(2015,'Audi','A6','3.0 TDI Premium Plus',10,2),
(2015,'Audi','A6','3.0 TDI quattro Premium Plus Sedan AWD',14,9),
(2015,'Audi','A6','3.0 TDI quattro Prestige Sedan AWD',14,9),
(2015,'Audi','A6','3.0 TDI quattro Progressiv Sedan AWD',13,9),
(2015,'Audi','A6','3.0 TDI quattro Technik Sedan AWD',13,9),
(2015,'Audi','A6','3.0T Premium Plus',1,1),
(2015,'Audi','A6','3.0T Premium Plus',1,2),
(2015,'Audi','A6','3.0T Prestige',1,1),
(2015,'Audi','A6','3.0T Prestige',1,2),
(2015,'Audi','A6','3.0T quattro Premium Plus Sedan AWD',13,9),
(2015,'Audi','A6','3.0T quattro Prestige Sedan AWD',13,9),
(2015,'Audi','A6','3.0T quattro Progressiv Sedan AWD',13,9),
(2015,'Audi','A6','3.0T quattro Technik Sedan AWD',13,9),
(2015,'Audi','A7','3.0 TDI Premium Plus',10,1),
(2015,'Audi','A7','3.0 TDI Premium Plus',10,2),
(2015,'Audi','A7','3.0 TDI quattro Premium Plus AWD',14,9),
(2015,'Audi','A7','3.0 TDI quattro Premium Plus AWD',13,9),
(2015,'Audi','A7','3.0 TDI quattro Prestige AWD',14,9),
(2015,'Audi','A7','3.0 TDI quattro Progressiv AWD',13,9),
(2015,'Audi','A7','3.0 TDI quattro Technik AWD',13,9),
(2015,'Audi','A7','3.0T Premium Plus',1,1),
(2015,'Audi','A7','3.0T Premium Plus',1,2),
(2015,'Audi','A7','3.0T Prestige',1,1),
(2015,'Audi','A7','3.0T Prestige',1,2),
(2015,'Audi','A7','3.0T quattro Premium Plus AWD',13,9),
(2015,'Audi','A7','3.0T quattro Prestige AWD',13,9),
(2015,'Audi','A7','3.0T quattro Progressiv AWD',13,9),
(2015,'Audi','A7','3.0T quattro Technik AWD',13,9),
(2015,'Audi','A8','3.0 TDI quattro AWD',15,9),
(2015,'Audi','A8','3.0T',1,1),
(2015,'Audi','A8','3.0T',1,2),
(2015,'Audi','A8','3.0T quattro AWD',16,9),
(2015,'Audi','A8','4.0T',1,1),
(2015,'Audi','A8','4.0T',1,2),
(2015,'Audi','A8','4.0T quattro AWD',15,9),
(2015,'Audi','A8','L 3.0 TDI',10,1),
(2015,'Audi','A8','L 3.0 TDI',10,2),
(2015,'Audi','A8','L 3.0 TDI quattro AWD',14,9),
(2015,'Audi','A8','L 3.0T',1,1),
(2015,'Audi','A8','L 3.0T',1,2),
(2015,'Audi','A8','L 3.0T quattro AWD',16,9),
(2015,'Audi','A8','L 4.0T',1,1),
(2015,'Audi','A8','L 4.0T',1,2),
(2015,'Audi','A8','L 4.0T quattro AWD',15,9),
(2015,'Audi','A8','L W12 6.3',1,1),
(2015,'Audi','A8','L W12 6.3',1,2),
(2015,'Audi','A8','L W12 quattro AWD',15,9),
(2015,'Audi','A8','L W12 quattro AWD',17,9),
(2015,'Audi','Q3','2.0T Premium Plus',1,1),
(2015,'Audi','Q3','2.0T Premium Plus',1,2),
(2015,'Audi','Q3','2.0T Premium Plus FWD',18,5),
(2015,'Audi','Q3','2.0T Prestige',1,1),
(2015,'Audi','Q3','2.0T Prestige',1,2),
(2015,'Audi','Q3','2.0T Prestige FWD',18,5),
(2015,'Audi','Q3','2.0T Progressiv FWD',18,5),
(2015,'Audi','Q3','2.0T Technik FWD',18,5),
(2015,'Audi','Q3','2.0T quattro Premium Plus AWD',18,5),
(2015,'Audi','Q3','2.0T quattro Prestige AWD',18,5),
(2015,'Audi','Q3','3.0T quattro Progressiv AWD',18,5),
(2015,'Audi','Q3','3.0T quattro Technik AWD',18,5),
(2015,'Audi','Q5','2.0T Premium',1,1),
(2015,'Audi','Q5','2.0T Premium',1,2),
(2015,'Audi','Q5','2.0T Premium Plus',1,1),
(2015,'Audi','Q5','2.0T Premium Plus',1,2),
(2015,'Audi','Q5','2.0T quattro Komfort AWD',19,9),
(2015,'Audi','allroad','2.0T Premium',1,1),
(2015,'Audi','allroad','2.0T Premium',1,2),
(2015,'Audi','allroad','2.0T Premium Plus',1,1),
(2015,'Audi','allroad','2.0T Premium Plus',1,2),
(2015,'Audi','allroad','2.0T Prestige',1,1),
(2015,'Audi','allroad','2.0T Prestige',1,2);

190
data/vehicle-etl/qa_validate.py Executable file
View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""
Post-import QA validation for vehicle dropdown data.
Runs basic duplicate and range checks against the motovaultpro Postgres container.
"""
import os
import subprocess
import sys
def run_psql(query: str) -> str:
cmd = [
"docker",
"exec",
"mvp-postgres",
"psql",
"-U",
"postgres",
"-d",
"motovaultpro",
"-At",
"-c",
query,
]
return subprocess.check_output(cmd, text=True)
def check_container():
try:
subprocess.check_output(["docker", "ps"], text=True)
except Exception:
print("❌ Docker not available.")
sys.exit(1)
try:
containers = subprocess.check_output(
["docker", "ps", "--filter", "name=mvp-postgres", "--format", "{{.Names}}"],
text=True,
).strip()
if not containers:
print("❌ mvp-postgres container not running.")
sys.exit(1)
except Exception as exc:
print(f"❌ Failed to check containers: {exc}")
sys.exit(1)
def check_invalid_combinations():
"""Verify known invalid combinations do not exist."""
invalid_combos = [
(1992, "Chevrolet", "Corvette", "Z06"), # Z06 started 2001
(2000, "Chevrolet", "Corvette", "35th Anniversary Edition"), # Was 1988
(2000, "Chevrolet", "Corvette", "Stingray"), # Stingray started 2014
(1995, "Ford", "Mustang", "Mach-E"), # Mach-E is 2021+
(2020, "Tesla", "Cybertruck", "Base"), # Not in production until later
]
issues = []
for year, make, model, trim in invalid_combos:
query = f"""
SELECT COUNT(*) FROM vehicle_options
WHERE year = {year}
AND make = '{make}'
AND model = '{model}'
AND trim = '{trim}'
"""
count = int(run_psql(query).strip())
if count > 0:
issues.append(f"Invalid combo found: {year} {make} {model} {trim}")
return issues
def check_trim_coverage():
"""Report on trim coverage statistics."""
query = """
SELECT
COUNT(DISTINCT (year, make, model)) as total_models,
COUNT(DISTINCT (year, make, model)) FILTER (WHERE trim = 'Base') as base_only,
COUNT(DISTINCT (year, make, model)) FILTER (WHERE trim != 'Base') as has_specific_trims
FROM vehicle_options
"""
result = run_psql(query).strip()
print(f"Trim coverage (total/base_only/has_specific_trims): {result}")
def main():
check_container()
print("🔍 Running QA checks...\n")
queries = {
"engine_duplicate_names": """
SELECT COUNT(*) FROM (
SELECT LOWER(name) as n, COUNT(*) c
FROM engines
GROUP BY 1 HAVING COUNT(*) > 1
) t;
""",
"transmission_duplicate_types": """
SELECT COUNT(*) FROM (
SELECT LOWER(type) as t, COUNT(*) c
FROM transmissions
GROUP BY 1 HAVING COUNT(*) > 1
) t;
""",
"vehicle_option_duplicates": """
SELECT COUNT(*) FROM (
SELECT year, make, model, trim, engine_id, transmission_id, COUNT(*) c
FROM vehicle_options
GROUP BY 1,2,3,4,5,6 HAVING COUNT(*) > 1
) t;
""",
"year_range": """
SELECT MIN(year) || ' - ' || MAX(year) FROM vehicle_options;
""",
"year_range_valid": """
SELECT COUNT(*) FROM (
SELECT 1 FROM vehicle_options WHERE year < 2015 OR year > 2022 LIMIT 1
) t;
""",
"counts": """
SELECT
(SELECT COUNT(*) FROM engines) AS engines,
(SELECT COUNT(*) FROM transmissions) AS transmissions,
(SELECT COUNT(*) FROM vehicle_options) AS vehicle_options;
""",
"cross_join_gaps": """
SELECT COUNT(*) FROM (
SELECT base.year, base.make, base.model, base.trim, e.engine_id, t.transmission_id
FROM (
SELECT DISTINCT year, make, model, trim FROM vehicle_options
) base
JOIN (
SELECT DISTINCT year, make, model, trim, engine_id FROM vehicle_options
) e ON base.year = e.year AND base.make = e.make AND base.model = e.model AND base.trim = e.trim
JOIN (
SELECT DISTINCT year, make, model, trim, transmission_id FROM vehicle_options
) t ON base.year = t.year AND base.make = t.make AND base.model = t.model AND base.trim = t.trim
EXCEPT
SELECT year, make, model, trim, engine_id, transmission_id FROM vehicle_options
) gap;
""",
}
results = {}
for key, query in queries.items():
try:
results[key] = run_psql(query).strip()
except subprocess.CalledProcessError as exc:
print(f"❌ Query failed ({key}): {exc}")
sys.exit(1)
issues_found = False
print(f"Engine duplicate names: {results['engine_duplicate_names']}")
print(f"Transmission duplicate types: {results['transmission_duplicate_types']}")
print(f"Vehicle option duplicates: {results['vehicle_option_duplicates']}")
print(f"Year range: {results['year_range']}")
print(f"Out-of-range years (should be 0): {results['year_range_valid']}")
print(f"Counts (engines, transmissions, vehicle_options): {results['counts']}")
print(f"Cross-join gaps (should be 0 to avoid impossible pairs): {results['cross_join_gaps']}")
if (
results["engine_duplicate_names"] != "0"
or results["transmission_duplicate_types"] != "0"
or results["vehicle_option_duplicates"] != "0"
or results["year_range_valid"] != "0"
or results["cross_join_gaps"] != "0"
):
issues_found = True
invalids = check_invalid_combinations()
if invalids:
issues_found = True
print("\n❌ Invalid combinations detected:")
for issue in invalids:
print(f" - {issue}")
else:
print("\n✅ No known invalid year/make/model/trim combos found.")
check_trim_coverage()
if not issues_found:
print("\n✅ QA checks passed.")
else:
print("\n❌ QA checks found issues.")
if __name__ == "__main__":
main()

View File

Binary file not shown.

View File

@@ -0,0 +1 @@
N9ZTsICa0gprFoXxgYRK6UApGCIVLeJlu0XR0leN

View File

@@ -0,0 +1,515 @@
#!/usr/bin/env python3
"""
Fetches VehAPI data into an offline snapshot (SQLite + meta.json).
Workflow:
1. Walks Year -> Make -> Model -> Trim -> Transmission -> Engine using VehAPI.
2. Persists observed compatibility pairs to snapshot.sqlite (no Cartesian products).
3. Stores request/response cache for resume; obeys rate limits and 429 retry-after.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import random
import sqlite3
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence
from urllib.parse import quote
try:
import requests
except ImportError: # pragma: no cover - env guard
print("[error] Missing dependency 'requests'. Install with `pip install requests`.", file=sys.stderr)
sys.exit(1)
SCRIPT_VERSION = "vehapi_fetch_snapshot.py@1.1.0"
DEFAULT_MIN_YEAR = 2015
DEFAULT_MAX_YEAR = 2022
DEFAULT_RATE_PER_SEC = 55 # stays under the 60 req/sec ceiling
MAX_ATTEMPTS = 5
FALLBACK_TRIMS = ["Base"]
FALLBACK_TRANSMISSIONS = ["Manual", "Automatic"]
DEFAULT_BASE_URL = "https://vehapi.com/api/v1/car-lists/get/car"
def canonicalize(value: str) -> str:
"""Lowercase, trim, collapse spaces, and normalize hyphens for dedupe keys."""
import re
cleaned = (value or "").strip()
cleaned = re.sub(r"[\s\u00A0]+", " ", cleaned)
cleaned = re.sub(r"[-\u2010-\u2015]+", "-", cleaned)
return cleaned.lower()
def infer_trans_bucket(trans_str: str) -> str:
lowered = (trans_str or "").lower()
if "manual" in lowered or "mt" in lowered or "m/t" in lowered:
return "Manual"
return "Automatic"
def infer_fuel_bucket(engine_str: str, trans_str: str, trim_str: str) -> str:
target = " ".join([engine_str or "", trans_str or "", trim_str or ""]).lower()
if any(token in target for token in ["electric", "ev", "battery", "motor", "kwh"]):
return "Electric"
if any(token in target for token in ["hybrid", "phev", "plug-in", "hev", "e-hybrid"]):
return "Hybrid"
if any(token in target for token in ["diesel", "tdi", "dci", "duramax", "power stroke", "cummins"]):
return "Diesel"
return "Gas"
def read_text_file(path: Path) -> str:
with path.open("r", encoding="utf-8") as fh:
return fh.read()
def read_lines(path: Path) -> List[str]:
return [line.strip() for line in read_text_file(path).splitlines() if line.strip()]
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def ensure_snapshot_dir(root: Path, custom_dir: Optional[str]) -> Path:
if custom_dir:
snapshot_dir = Path(custom_dir)
else:
today = datetime.now(timezone.utc).date().isoformat()
snapshot_dir = root / today
snapshot_dir.mkdir(parents=True, exist_ok=True)
return snapshot_dir
class RateLimiter:
"""Simple leaky bucket limiter to stay below the VehAPI threshold."""
def __init__(self, max_per_sec: int) -> None:
self.max_per_sec = max_per_sec
self._history: List[float] = []
def acquire(self) -> None:
while True:
now = time.monotonic()
window_start = now - 1
self._history = [ts for ts in self._history if ts >= window_start]
if len(self._history) < self.max_per_sec:
break
sleep_for = max(self._history[0] - window_start, 0.001)
time.sleep(sleep_for)
self._history.append(time.monotonic())
@dataclass
class FetchCounts:
pairs_inserted: int = 0
cache_hits: int = 0
fallback_transmissions: int = 0
fallback_engines: int = 0
class VehapiFetcher:
def __init__(
self,
session: requests.Session,
base_url: str,
token: str,
min_year: int,
max_year: int,
allowed_makes: Sequence[str],
snapshot_path: Path,
responses_cache: bool = True,
rate_per_sec: int = DEFAULT_RATE_PER_SEC,
) -> None:
self.session = session
self.base_url = base_url.rstrip("/")
self.token = token
self.min_year = min_year
self.max_year = max_year
self.allowed_makes = {canonicalize(m): m for m in allowed_makes}
self.snapshot_path = snapshot_path
self.conn = sqlite3.connect(self.snapshot_path)
self.conn.execute("PRAGMA journal_mode=WAL;")
self.conn.execute("PRAGMA synchronous=NORMAL;")
self._init_schema()
self.responses_cache = responses_cache
self.rate_limiter = RateLimiter(rate_per_sec)
self.counts = FetchCounts()
def _init_schema(self) -> None:
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS pairs(
year INT,
make TEXT,
model TEXT,
trim TEXT,
engine_display TEXT,
engine_canon TEXT,
engine_bucket TEXT,
trans_display TEXT,
trans_canon TEXT,
trans_bucket TEXT,
PRIMARY KEY(year, make, model, trim, engine_canon, trans_canon)
)
"""
)
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS meta(
key TEXT PRIMARY KEY,
value TEXT
)
"""
)
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS responses(
request_key TEXT PRIMARY KEY,
url TEXT,
status INT,
headers_json TEXT,
body_json TEXT,
fetched_at TEXT
)
"""
)
self.conn.commit()
def _store_meta(self, meta: Dict[str, Any]) -> None:
rows = [(k, str(v)) for k, v in meta.items()]
self.conn.executemany("INSERT OR REPLACE INTO meta(key, value) VALUES (?, ?)", rows)
self.conn.commit()
def _load_cached_response(self, request_key: str) -> Optional[Any]:
if not self.responses_cache:
return None
cur = self.conn.execute("SELECT body_json FROM responses WHERE request_key = ?", (request_key,))
row = cur.fetchone()
if not row:
return None
self.counts.cache_hits += 1
try:
return json.loads(row[0])
except Exception:
return None
def _save_response(self, request_key: str, url: str, status: int, headers: Dict[str, Any], body: Any) -> None:
self.conn.execute(
"""
INSERT OR REPLACE INTO responses(request_key, url, status, headers_json, body_json, fetched_at)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
request_key,
url,
status,
json.dumps(dict(headers), default=str),
json.dumps(body, default=str),
datetime.now(timezone.utc).isoformat(),
),
)
self.conn.commit()
def _request_json(self, path_parts: Sequence[str], label: str) -> Any:
path_parts = [str(p) for p in path_parts]
request_key = "/".join(path_parts)
cached = self._load_cached_response(request_key)
if cached is not None:
return cached
url = f"{self.base_url}/" + "/".join(quote(p, safe="") for p in path_parts)
attempts = 0
backoff = 1.0
while attempts < MAX_ATTEMPTS:
attempts += 1
self.rate_limiter.acquire()
try:
resp = self.session.get(url, headers={"Authorization": f"Bearer {self.token}"}, timeout=30)
except requests.RequestException as exc:
print(f"[warn] {label}: request error {exc}; retrying...", file=sys.stderr)
time.sleep(backoff + random.uniform(0, 0.5))
backoff = min(backoff * 2, 30)
continue
if resp.status_code == 429:
retry_after = resp.headers.get("retry-after") or resp.headers.get("Retry-After")
try:
retry_seconds = float(retry_after)
except (TypeError, ValueError):
retry_seconds = 30.0
sleep_for = retry_seconds + random.uniform(0, 3)
print(f"[info] {label}: hit 429, sleeping {sleep_for:.1f}s before retry", file=sys.stderr)
time.sleep(sleep_for)
backoff = min(backoff * 2, 30)
continue
if resp.status_code >= 500:
print(f"[warn] {label}: server {resp.status_code}, retrying...", file=sys.stderr)
time.sleep(backoff + random.uniform(0, 0.5))
backoff = min(backoff * 2, 30)
continue
if not resp.ok:
print(f"[warn] {label}: HTTP {resp.status_code}, skipping", file=sys.stderr)
return []
try:
body = resp.json()
except ValueError:
print(f"[warn] {label}: non-JSON response, skipping", file=sys.stderr)
return []
self._save_response(request_key, url, resp.status_code, resp.headers, body)
return body
print(f"[error] {label}: exhausted retries", file=sys.stderr)
return []
@staticmethod
def _extract_values(payload: Any, keys: Sequence[str]) -> List[str]:
values: List[str] = []
if isinstance(payload, dict):
payload = payload.get("data") or payload.get("results") or payload.get("items") or payload
if not payload:
return values
if isinstance(payload, list):
for item in payload:
if isinstance(item, str):
if item.strip():
values.append(item.strip())
continue
if isinstance(item, dict):
for key in keys:
val = item.get(key)
if val:
values.append(str(val).strip())
break
return values
def _record_pair(
self,
year: int,
make: str,
model: str,
trim: str,
engine_display: str,
engine_bucket: str,
trans_display: str,
trans_bucket: str,
) -> None:
engine_canon = canonicalize(engine_display)
trans_canon = canonicalize(trans_display)
cur = self.conn.execute(
"""
INSERT OR IGNORE INTO pairs(
year, make, model, trim,
engine_display, engine_canon, engine_bucket,
trans_display, trans_canon, trans_bucket
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
year,
make,
model,
trim,
engine_display.strip(),
engine_canon,
engine_bucket,
trans_display.strip(),
trans_canon,
trans_bucket,
),
)
if cur.rowcount:
self.counts.pairs_inserted += 1
def _fetch_engines_for_transmission(
self, year: int, make: str, model: str, trim: str, transmission: str, trans_bucket: str
) -> None:
path = ["engines", str(year), make, model, trim, transmission]
label = f"engines:{year}/{make}/{model}/{trim}/{transmission}"
engines_payload = self._request_json(path, label)
engines = self._extract_values(engines_payload, ["engine"])
if not engines:
engine_bucket = infer_fuel_bucket("", transmission, trim)
fallback_engine = engine_bucket
self._record_pair(year, make, model, trim, fallback_engine, engine_bucket, transmission, trans_bucket)
self.counts.fallback_engines += 1
return
for engine in engines:
engine_bucket = infer_fuel_bucket(engine, transmission, trim)
self._record_pair(year, make, model, trim, engine, engine_bucket, transmission, trans_bucket)
def _fetch_transmissions_for_trim(self, year: int, make: str, model: str, trim: str) -> None:
path = ["transmissions", str(year), make, model, trim]
label = f"transmissions:{year}/{make}/{model}/{trim}"
transmissions_payload = self._request_json(path, label)
transmissions = self._extract_values(transmissions_payload, ["transmission"])
if not transmissions:
for fallback in FALLBACK_TRANSMISSIONS:
trans_bucket = infer_trans_bucket(fallback)
engine_bucket = infer_fuel_bucket("", fallback, trim)
self._record_pair(year, make, model, trim, engine_bucket, engine_bucket, fallback, trans_bucket)
self.counts.fallback_transmissions += 1
self.counts.fallback_engines += 1
return
for trans in transmissions:
trans_bucket = infer_trans_bucket(trans)
self._fetch_engines_for_transmission(year, make, model, trim, trans, trans_bucket)
def _fetch_trims_for_model(self, year: int, make: str, model: str) -> None:
path = ["trims", str(year), make, model]
label = f"trims:{year}/{make}/{model}"
trims_payload = self._request_json(path, label)
trims = self._extract_values(trims_payload, ["trim"])
if not trims:
trims = FALLBACK_TRIMS
for trim in trims:
self._fetch_transmissions_for_trim(year, make, model, trim)
self.conn.commit()
def _fetch_models_for_make(self, year: int, make: str) -> None:
path = ["models", str(year), make]
label = f"models:{year}/{make}"
models_payload = self._request_json(path, label)
models = self._extract_values(models_payload, ["model"])
if not models:
print(f"[warn] {label}: no models returned", file=sys.stderr)
return
for model in models:
self._fetch_trims_for_model(year, make, model)
def _fetch_makes_for_year(self, year: int) -> List[str]:
path = ["makes", str(year)]
label = f"makes:{year}"
makes_payload = self._request_json(path, label)
makes = self._extract_values(makes_payload, ["make"])
filtered = []
for make in makes:
canon = canonicalize(make)
if canon in self.allowed_makes:
filtered.append(make)
return filtered
def run(self) -> FetchCounts:
for year in range(self.min_year, self.max_year + 1):
makes = self._fetch_makes_for_year(year)
if not makes:
print(f"[info] {year}: no allowed makes found, skipping", file=sys.stderr)
continue
print(f"[info] {year}: {len(makes)} makes", file=sys.stderr)
for make in makes:
print(f"[info] {year} {make}: fetching models", file=sys.stderr)
self._fetch_models_for_make(year, make)
self.conn.commit()
return self.counts
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Fetch VehAPI snapshot into SQLite.")
parser.add_argument("--min-year", type=int, default=int(read_env("MIN_YEAR", DEFAULT_MIN_YEAR)), help="Inclusive min year (default env MIN_YEAR or 2017)")
parser.add_argument("--max-year", type=int, default=int(read_env("MAX_YEAR", DEFAULT_MAX_YEAR)), help="Inclusive max year (default env MAX_YEAR or 2026)")
parser.add_argument("--snapshot-dir", type=str, help="Target snapshot directory (default snapshots/<today>)")
parser.add_argument("--base-url", type=str, default=read_env("VEHAPI_BASE_URL", DEFAULT_BASE_URL), help="VehAPI base URL (e.g. https://vehapi.com/api/v1/car-lists/get/car)")
parser.add_argument("--rate-per-sec", type=int, default=int(read_env("VEHAPI_MAX_RPS", DEFAULT_RATE_PER_SEC)), help="Max requests per second (<=60)")
parser.add_argument("--makes-file", type=str, default="source-makes.txt", help="Path to source-makes.txt")
parser.add_argument("--api-key-file", type=str, default="vehapi.key", help="Path to VehAPI bearer token file")
parser.add_argument("--no-response-cache", action="store_true", help="Disable request cache stored in snapshot.sqlite")
return parser
def read_env(key: str, default: Any) -> Any:
import os
return os.environ.get(key, default)
def main(argv: Sequence[str]) -> int:
parser = build_arg_parser()
args = parser.parse_args(argv)
base_dir = Path(__file__).resolve().parent
snapshot_root = base_dir / "snapshots"
snapshot_dir = ensure_snapshot_dir(snapshot_root, args.snapshot_dir)
snapshot_path = snapshot_dir / "snapshot.sqlite"
meta_path = snapshot_dir / "meta.json"
makes_file = (base_dir / args.makes_file).resolve()
api_key_file = (base_dir / args.api_key_file).resolve()
if not makes_file.exists():
print(f"[error] makes file not found: {makes_file}", file=sys.stderr)
return 1
if not api_key_file.exists():
print(f"[error] api key file not found: {api_key_file}", file=sys.stderr)
return 1
allowed_makes = read_lines(makes_file)
token = read_text_file(api_key_file).strip()
if not token:
print("[error] vehapi.key is empty", file=sys.stderr)
return 1
session = requests.Session()
fetcher = VehapiFetcher(
session=session,
base_url=args.base_url,
token=token,
min_year=args.min_year,
max_year=args.max_year,
allowed_makes=allowed_makes,
snapshot_path=snapshot_path,
responses_cache=not args.no_response_cache,
rate_per_sec=args.rate_per_sec,
)
started_at = datetime.now(timezone.utc)
counts = fetcher.run()
finished_at = datetime.now(timezone.utc)
meta = {
"generated_at": finished_at.isoformat(),
"started_at": started_at.isoformat(),
"min_year": args.min_year,
"max_year": args.max_year,
"script_version": SCRIPT_VERSION,
"makes_file": str(makes_file),
"makes_hash": sha256_file(makes_file),
"api_base_url": args.base_url,
"snapshot_path": str(snapshot_path),
"pairs_inserted": counts.pairs_inserted,
"fallback_transmissions": counts.fallback_transmissions,
"fallback_engines": counts.fallback_engines,
"response_cache_hits": counts.cache_hits,
}
fetcher._store_meta(meta)
with meta_path.open("w", encoding="utf-8") as fh:
json.dump(meta, fh, indent=2)
print(
f"[done] wrote snapshot to {snapshot_path} with {counts.pairs_inserted} pairs "
f"(fallback trans={counts.fallback_transmissions}, fallback engines={counts.fallback_engines}, cache hits={counts.cache_hits})",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

File diff suppressed because it is too large Load Diff