Before updates to NHTSA
This commit is contained in:
518
ETL-FIX-V2.md
Normal file
518
ETL-FIX-V2.md
Normal file
@@ -0,0 +1,518 @@
|
|||||||
|
# ETL Fix V2: Year-Accurate Vehicle Dropdown Data
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document provides a complete implementation plan for fixing the vehicle dropdown ETL to produce year-accurate data. The fix addresses impossible year/trim combinations (e.g., "1992 Corvette Z06") by using the NHTSA VPIC API for authoritative Year/Make/Model validation and automobiles.json for evidence-based trim data with year ranges.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
### Current Issues
|
||||||
|
1. **Year-inaccurate trims**: The `makes-filter/*.json` files contain ALL trims ever made for a model, applied to EVERY year
|
||||||
|
2. **Impossible combinations**: Users can select "1992 Corvette Z06" (Z06 didn't exist until 2001)
|
||||||
|
3. **Data bloat**: 400 records for 2000 Corvette with 20 trims instead of ~3-4
|
||||||
|
|
||||||
|
### Root Cause
|
||||||
|
The `makes-filter/*.json` data structure does NOT have year-specific trims. Example from `chevrolet.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"year": "2025",
|
||||||
|
"models": [{
|
||||||
|
"name": "corvette",
|
||||||
|
"submodels": ["LT", "35th Anniversary Edition", "427", "Z06", "ZR1", ...]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The same `submodels` array is repeated for every year, making ALL trims appear valid for ALL years.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Solution Architecture
|
||||||
|
|
||||||
|
### Data Sources (Priority Order)
|
||||||
|
1. **NHTSA VPIC API** - Authoritative Year/Make/Model validation
|
||||||
|
2. **automobiles.json** - Primary trim source with year-range evidence
|
||||||
|
3. **makes-filter/*.json** - Engine data enrichment
|
||||||
|
4. **Defaults** - "Base" trim, "Gas" engine, "Manual"/"Automatic" transmission
|
||||||
|
|
||||||
|
### Year Range
|
||||||
|
- Minimum: 1990
|
||||||
|
- Maximum: 2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### Phase 1: Create NHTSA Data Fetcher
|
||||||
|
|
||||||
|
**Create file:** `data/make-model-import/nhtsa_fetch.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
NHTSA VPIC API Data Fetcher
|
||||||
|
Fetches authoritative Year/Make/Model data from the US government database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
|
class NHTSAFetcher:
|
||||||
|
BASE_URL = "https://vpic.nhtsa.dot.gov/api/vehicles"
|
||||||
|
CACHE_DIR = Path("nhtsa_cache")
|
||||||
|
OUTPUT_FILE = Path("nhtsa_vehicles.json")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.min_year = int(os.getenv("MIN_YEAR", "1990"))
|
||||||
|
self.max_year = int(os.getenv("MAX_YEAR", "2026"))
|
||||||
|
self.request_delay = 0.1 # 100ms between requests
|
||||||
|
|
||||||
|
# Makes we care about (from makes-filter)
|
||||||
|
self.target_makes = self._load_target_makes()
|
||||||
|
|
||||||
|
def _load_target_makes(self) -> Set[str]:
|
||||||
|
"""Load makes from makes-filter directory."""
|
||||||
|
makes_dir = Path("makes-filter")
|
||||||
|
makes = set()
|
||||||
|
for f in makes_dir.glob("*.json"):
|
||||||
|
make_name = f.stem.replace("_", " ").title()
|
||||||
|
makes.add(make_name)
|
||||||
|
return makes
|
||||||
|
|
||||||
|
def fetch_url(self, url: str) -> dict:
|
||||||
|
"""Fetch JSON from URL with error handling."""
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(url, timeout=30) as response:
|
||||||
|
return json.loads(response.read().decode())
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
print(f" Error fetching {url}: {e}")
|
||||||
|
return {"Results": []}
|
||||||
|
|
||||||
|
def get_all_makes(self) -> List[Dict]:
|
||||||
|
"""Fetch all makes for passenger cars and trucks."""
|
||||||
|
makes = []
|
||||||
|
for vehicle_type in ["car", "truck"]:
|
||||||
|
url = f"{self.BASE_URL}/GetMakesForVehicleType/{vehicle_type}?format=json"
|
||||||
|
data = self.fetch_url(url)
|
||||||
|
makes.extend(data.get("Results", []))
|
||||||
|
return makes
|
||||||
|
|
||||||
|
def get_models_for_make_year(self, make: str, year: int) -> List[str]:
|
||||||
|
"""Fetch models for a specific make and year."""
|
||||||
|
cache_file = self.CACHE_DIR / f"{make.lower().replace(' ', '_')}_{year}.json"
|
||||||
|
|
||||||
|
# Check cache first
|
||||||
|
if cache_file.exists():
|
||||||
|
with open(cache_file) as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
url = f"{self.BASE_URL}/GetModelsForMakeYear/make/{make}/modelyear/{year}?format=json"
|
||||||
|
time.sleep(self.request_delay)
|
||||||
|
data = self.fetch_url(url)
|
||||||
|
|
||||||
|
models = list(set(r.get("Model_Name", "") for r in data.get("Results", []) if r.get("Model_Name")))
|
||||||
|
|
||||||
|
# Cache result
|
||||||
|
self.CACHE_DIR.mkdir(exist_ok=True)
|
||||||
|
with open(cache_file, "w") as f:
|
||||||
|
json.dump(models, f)
|
||||||
|
|
||||||
|
return models
|
||||||
|
|
||||||
|
def fetch_all_data(self) -> Dict:
|
||||||
|
"""Fetch all Year/Make/Model data."""
|
||||||
|
print("Fetching NHTSA data...")
|
||||||
|
|
||||||
|
# Filter to target makes
|
||||||
|
all_makes = self.get_all_makes()
|
||||||
|
target_make_names = [m["MakeName"] for m in all_makes
|
||||||
|
if m["MakeName"].title() in self.target_makes
|
||||||
|
or m["MakeName"].upper() in ["BMW", "GMC", "RAM"]]
|
||||||
|
|
||||||
|
print(f"Found {len(target_make_names)} matching makes")
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for year in range(self.min_year, self.max_year + 1):
|
||||||
|
result[str(year)] = {}
|
||||||
|
for make in target_make_names:
|
||||||
|
models = self.get_models_for_make_year(make, year)
|
||||||
|
if models:
|
||||||
|
# Normalize make name
|
||||||
|
make_normalized = make.title()
|
||||||
|
if make.upper() in ["BMW", "GMC", "RAM"]:
|
||||||
|
make_normalized = make.upper()
|
||||||
|
result[str(year)][make_normalized] = sorted(models)
|
||||||
|
print(f" Year {year}: {sum(len(v) for v in result[str(year)].values())} models")
|
||||||
|
|
||||||
|
# Save output
|
||||||
|
with open(self.OUTPUT_FILE, "w") as f:
|
||||||
|
json.dump(result, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Saved to {self.OUTPUT_FILE}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
NHTSAFetcher().fetch_all_data()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Refactor ETL Script
|
||||||
|
|
||||||
|
**Modify file:** `data/make-model-import/etl_generate_sql.py`
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
|
||||||
|
#### 2.1 Load NHTSA data as primary source
|
||||||
|
```python
|
||||||
|
def load_nhtsa_data(self):
|
||||||
|
"""Load NHTSA Year/Make/Model data."""
|
||||||
|
nhtsa_file = Path("nhtsa_vehicles.json")
|
||||||
|
if not nhtsa_file.exists():
|
||||||
|
raise FileNotFoundError("Run nhtsa_fetch.py first to generate nhtsa_vehicles.json")
|
||||||
|
|
||||||
|
with open(nhtsa_file) as f:
|
||||||
|
self.nhtsa_data = json.load(f)
|
||||||
|
print(f" Loaded NHTSA data for {len(self.nhtsa_data)} years")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Build trim evidence from automobiles.json
|
||||||
|
```python
|
||||||
|
def build_trim_evidence(self):
|
||||||
|
"""
|
||||||
|
Parse automobiles.json to build year-range evidence for trims.
|
||||||
|
"""
|
||||||
|
self.trim_evidence: Dict[Tuple[str, str], List[Dict]] = defaultdict(list)
|
||||||
|
|
||||||
|
brand_lookup = {b.get("id"): self.get_canonical_make_name(b.get("name", ""))
|
||||||
|
for b in self.brands_data}
|
||||||
|
|
||||||
|
for auto in self.automobiles_data:
|
||||||
|
brand_id = auto.get("brand_id")
|
||||||
|
make = brand_lookup.get(brand_id)
|
||||||
|
if not make:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = auto.get("name", "")
|
||||||
|
year_range = self.parse_year_range_from_name(name)
|
||||||
|
if not year_range:
|
||||||
|
continue
|
||||||
|
|
||||||
|
year_start, year_end = year_range
|
||||||
|
|
||||||
|
# Extract model and trim from name
|
||||||
|
model, trim = self.extract_model_trim_from_name(name, make)
|
||||||
|
if not model:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.trim_evidence[(make, model)].append({
|
||||||
|
"trim": trim or "Base",
|
||||||
|
"year_start": year_start,
|
||||||
|
"year_end": year_end,
|
||||||
|
"source_name": name
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f" Built trim evidence for {len(self.trim_evidence)} make/model combinations")
|
||||||
|
|
||||||
|
def extract_model_trim_from_name(self, name: str, make: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""
|
||||||
|
Extract model and trim from automobile name.
|
||||||
|
Examples:
|
||||||
|
"CHEVROLET Corvette Z06 2021-Present" -> ("Corvette", "Z06")
|
||||||
|
"2020 Chevrolet Corvette C8 Stingray" -> ("Corvette", "Stingray")
|
||||||
|
"FORD F-150 Raptor 2021-Present" -> ("F-150", "Raptor")
|
||||||
|
"""
|
||||||
|
# Remove make prefix
|
||||||
|
clean = re.sub(rf"^{re.escape(make)}\s+", "", name, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
# Remove year/range patterns
|
||||||
|
clean = re.sub(r"\d{4}(-\d{4}|-Present)?", "", clean)
|
||||||
|
|
||||||
|
# Remove common suffixes
|
||||||
|
clean = re.sub(r"\s*(Photos|engines|full specs|&).*$", "", clean, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
# Clean up extra spaces
|
||||||
|
clean = " ".join(clean.split())
|
||||||
|
|
||||||
|
# Try to match against known models
|
||||||
|
known_models = self.known_models_by_make.get(make, set())
|
||||||
|
|
||||||
|
for model in sorted(known_models, key=len, reverse=True):
|
||||||
|
pattern = re.compile(rf"^{re.escape(model)}\b\s*(.*)", re.IGNORECASE)
|
||||||
|
match = pattern.match(clean)
|
||||||
|
if match:
|
||||||
|
trim = match.group(1).strip()
|
||||||
|
# Remove generation codes like C5, C6, C7, C8
|
||||||
|
trim = re.sub(r"^C\d+\s*", "", trim)
|
||||||
|
return (model, trim if trim else None)
|
||||||
|
|
||||||
|
return (None, None)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 New trim resolution logic
|
||||||
|
```python
|
||||||
|
def get_trims_for_vehicle(self, year: int, make: str, model: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
Get valid trims for a year/make/model combination.
|
||||||
|
Uses evidence from automobiles.json, falls back to "Base".
|
||||||
|
"""
|
||||||
|
evidence = self.trim_evidence.get((make, model), [])
|
||||||
|
valid_trims = set()
|
||||||
|
|
||||||
|
for entry in evidence:
|
||||||
|
if entry['year_start'] <= year <= entry['year_end']:
|
||||||
|
valid_trims.add(entry['trim'])
|
||||||
|
|
||||||
|
# Always include "Base" as an option
|
||||||
|
valid_trims.add("Base")
|
||||||
|
|
||||||
|
return sorted(valid_trims)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 Updated vehicle record building
|
||||||
|
```python
|
||||||
|
def build_vehicle_records(self):
|
||||||
|
"""Build vehicle records using NHTSA for Y/M/M, evidence for trims."""
|
||||||
|
print("\n Building vehicle option records...")
|
||||||
|
records = []
|
||||||
|
|
||||||
|
for year_str, makes in self.nhtsa_data.items():
|
||||||
|
year = int(year_str)
|
||||||
|
if year < self.min_year or year > self.max_year:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for make, models in makes.items():
|
||||||
|
for model in models:
|
||||||
|
# Get valid trims from evidence
|
||||||
|
trims = self.get_trims_for_vehicle(year, make, model)
|
||||||
|
|
||||||
|
# Get engines from makes-filter (or default)
|
||||||
|
engines = self.get_engines_for_vehicle(year, make, model)
|
||||||
|
|
||||||
|
# Default transmissions
|
||||||
|
transmissions = ["Manual", "Automatic"]
|
||||||
|
|
||||||
|
for trim in trims:
|
||||||
|
for engine in engines:
|
||||||
|
for trans in transmissions:
|
||||||
|
records.append({
|
||||||
|
"year": year,
|
||||||
|
"make": make,
|
||||||
|
"model": model,
|
||||||
|
"trim": trim,
|
||||||
|
"engine_name": engine,
|
||||||
|
"trans_name": trans
|
||||||
|
})
|
||||||
|
|
||||||
|
# Deduplicate
|
||||||
|
unique_set = set()
|
||||||
|
deduped = []
|
||||||
|
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 not in unique_set:
|
||||||
|
unique_set.add(key)
|
||||||
|
deduped.append(r)
|
||||||
|
|
||||||
|
self.vehicle_records = deduped
|
||||||
|
print(f" Vehicle records: {len(self.vehicle_records):,}")
|
||||||
|
|
||||||
|
def get_engines_for_vehicle(self, year: int, make: str, model: str) -> List[str]:
|
||||||
|
"""Get engines from makes-filter or use defaults."""
|
||||||
|
# Try to find in makes-filter data
|
||||||
|
for baseline in self.baseline_records:
|
||||||
|
if (baseline['year'] == year and
|
||||||
|
baseline['make'].lower() == make.lower() and
|
||||||
|
baseline['model'].lower() == model.lower()):
|
||||||
|
engines = []
|
||||||
|
for trim_data in baseline.get('trims', []):
|
||||||
|
engines.extend(trim_data.get('engines', []))
|
||||||
|
if engines:
|
||||||
|
return list(set(engines))
|
||||||
|
|
||||||
|
# Default based on make/model patterns
|
||||||
|
model_lower = model.lower()
|
||||||
|
if 'electric' in model_lower or 'ev' in model_lower or 'lightning' in model_lower:
|
||||||
|
return ["Electric"]
|
||||||
|
|
||||||
|
return ["Gas"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Update Import Script
|
||||||
|
|
||||||
|
**Modify file:** `data/make-model-import/import_data.sh`
|
||||||
|
|
||||||
|
Add NHTSA cache check:
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Import generated SQL files into PostgreSQL database
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Automotive Database Import"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Check NHTSA cache freshness
|
||||||
|
NHTSA_FILE="nhtsa_vehicles.json"
|
||||||
|
CACHE_AGE_DAYS=30
|
||||||
|
|
||||||
|
if [ ! -f "$NHTSA_FILE" ]; then
|
||||||
|
echo "NHTSA data not found. Fetching..."
|
||||||
|
python3 nhtsa_fetch.py
|
||||||
|
elif [ $(find "$NHTSA_FILE" -mtime +$CACHE_AGE_DAYS 2>/dev/null | wc -l) -gt 0 ]; then
|
||||||
|
echo "NHTSA cache is stale (>$CACHE_AGE_DAYS days). Refreshing..."
|
||||||
|
python3 nhtsa_fetch.py
|
||||||
|
else
|
||||||
|
echo "Using cached NHTSA data"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Continue with existing import logic...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Update QA Validation
|
||||||
|
|
||||||
|
**Modify file:** `data/make-model-import/qa_validate.py`
|
||||||
|
|
||||||
|
Add invalid combination checks:
|
||||||
|
```python
|
||||||
|
def check_invalid_combinations():
|
||||||
|
"""Verify known invalid combinations do not exist."""
|
||||||
|
invalid_combos = [
|
||||||
|
# (year, make, model, trim) - known to be invalid
|
||||||
|
(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+
|
||||||
|
]
|
||||||
|
|
||||||
|
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: {result}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Summary
|
||||||
|
|
||||||
|
| File | Action | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `data/make-model-import/nhtsa_fetch.py` | CREATE | Fetch Year/Make/Model from NHTSA API |
|
||||||
|
| `data/make-model-import/etl_generate_sql.py` | MODIFY | Use NHTSA data, evidence-based trims |
|
||||||
|
| `data/make-model-import/import_data.sh` | MODIFY | Add NHTSA cache refresh |
|
||||||
|
| `data/make-model-import/qa_validate.py` | MODIFY | Add invalid combo checks |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Order
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Navigate to ETL directory
|
||||||
|
cd data/make-model-import
|
||||||
|
|
||||||
|
# 2. Fetch NHTSA data (creates nhtsa_vehicles.json)
|
||||||
|
python3 nhtsa_fetch.py
|
||||||
|
|
||||||
|
# 3. Generate SQL files
|
||||||
|
python3 etl_generate_sql.py
|
||||||
|
|
||||||
|
# 4. Import to database
|
||||||
|
./import_data.sh
|
||||||
|
|
||||||
|
# 5. Validate results
|
||||||
|
python3 qa_validate.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
### Before
|
||||||
|
- 2000 Corvette: 400 records, 20 trims (most invalid)
|
||||||
|
- Total records: ~1,675,335
|
||||||
|
- Many impossible year/trim combinations
|
||||||
|
|
||||||
|
### After
|
||||||
|
- 2000 Corvette: ~8 records (Base, Coupe, Convertible)
|
||||||
|
- 2015 Corvette: ~20 records (Stingray, Z06, Grand Sport, Base)
|
||||||
|
- Total records: ~400,000-600,000
|
||||||
|
- No invalid year/trim combinations
|
||||||
|
|
||||||
|
### Validation Checks
|
||||||
|
1. No 1992 Corvette Z06
|
||||||
|
2. No 2000 Corvette Stingray
|
||||||
|
3. No 1995 Mustang Mach-E
|
||||||
|
4. Year range: 1990-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Source Coverage
|
||||||
|
|
||||||
|
**automobiles.json trim coverage (samples):**
|
||||||
|
| Model | Entries | Trims Found |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| Civic | 67 | Si, Type R, eHEV, Sedan, Hatchback |
|
||||||
|
| Mustang | 38 | GT, Dark Horse, Mach-E GT, GTD |
|
||||||
|
| Accord | 35 | Sedan, Coupe (various years) |
|
||||||
|
| Corvette | 31 | Z06, ZR1, Stingray, Grand Sport |
|
||||||
|
| Camaro | 29 | ZL1, Convertible, Coupe |
|
||||||
|
| F-150 | 19 | Lightning, Raptor, Tremor |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `MIN_YEAR` | 1990 | Minimum year to include |
|
||||||
|
| `MAX_YEAR` | 2026 | Maximum year to include |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### NHTSA API Rate Limiting
|
||||||
|
The script includes 100ms delays between requests. If you encounter rate limiting:
|
||||||
|
- Increase `request_delay` in `nhtsa_fetch.py`
|
||||||
|
- Use cached data in `nhtsa_cache/` directory
|
||||||
|
|
||||||
|
### Missing Models
|
||||||
|
If NHTSA returns fewer models than expected:
|
||||||
|
- Check if the make name matches exactly
|
||||||
|
- Some brands (BMW, GMC) need uppercase handling
|
||||||
|
- Verify the year range is supported (NHTSA has data back to ~1995)
|
||||||
|
|
||||||
|
### Cache Refresh
|
||||||
|
To force refresh NHTSA data:
|
||||||
|
```bash
|
||||||
|
rm nhtsa_vehicles.json
|
||||||
|
rm -rf nhtsa_cache/
|
||||||
|
python3 nhtsa_fetch.py
|
||||||
|
```
|
||||||
@@ -264,6 +264,14 @@ Acceptance:
|
|||||||
4) Flush Redis dropdown caches (if needed) and re-test dropdowns.
|
4) Flush Redis dropdown caches (if needed) and re-test dropdowns.
|
||||||
5) Run QA harness and capture summary output in a `stats.txt` (or similar).
|
5) Run QA harness and capture summary output in a `stats.txt` (or similar).
|
||||||
|
|
||||||
|
## Status Update (completed)
|
||||||
|
- ETL rewritten to use makes-filter as baseline (year/make/model + trims/engines) and overlay evidence only to prune impossible year/trim combos and enrich engines/transmissions.
|
||||||
|
- Engines/transmissions now deduped by display name; vehicle_options deduped on full key.
|
||||||
|
- Uniqueness constraints added to prevent duplicates on import.
|
||||||
|
- Import script made rerunnable (truncate + restart identity) and prints year range.
|
||||||
|
- QA script added and validated (duplicates=0, year range 2000–2026).
|
||||||
|
- Example issue (GMC Sierra 1500 AT4X 6.2L V8) now present via baseline engines for that trim/year and Automatic/Manual fallback when transmissions are absent.
|
||||||
|
|
||||||
## Acceptance Criteria (End-to-End)
|
## Acceptance Criteria (End-to-End)
|
||||||
- Years available in dropdown are exactly those loaded (default 2000–2026).
|
- Years available in dropdown are exactly those loaded (default 2000–2026).
|
||||||
- Makes for a year only include makes with models in that year.
|
- Makes for a year only include makes with models in that year.
|
||||||
@@ -272,4 +280,3 @@ Acceptance:
|
|||||||
- Engines show detailed specs when available; otherwise show one of `Gas/Diesel/Electric/Hybrid`.
|
- Engines show detailed specs when available; otherwise show one of `Gas/Diesel/Electric/Hybrid`.
|
||||||
- Transmissions show derived options when available; otherwise show both `Manual` and `Automatic`.
|
- Transmissions show derived options when available; otherwise show both `Manual` and `Automatic`.
|
||||||
- No duplicate dimension rows; no duplicate fact rows.
|
- No duplicate dimension rows; no duplicate fact rows.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
1048
data/make-model-import/etl_generate_sql.py
Executable file → Normal file
1048
data/make-model-import/etl_generate_sql.py
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,20 +31,30 @@ docker exec -i mvp-postgres psql -U postgres -d motovaultpro < migrations/001_cr
|
|||||||
echo "✓ Schema migration completed"
|
echo "✓ Schema migration completed"
|
||||||
echo ""
|
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;
|
||||||
|
TRUNCATE TABLE engines RESTART IDENTITY CASCADE;
|
||||||
|
TRUNCATE TABLE transmissions RESTART IDENTITY CASCADE;
|
||||||
|
EOF
|
||||||
|
echo "✓ Tables truncated"
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Import engines
|
# Import engines
|
||||||
echo "📥 Step 2: Importing engines (34MB)..."
|
echo "📥 Step 3: Importing engines..."
|
||||||
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/01_engines.sql
|
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/01_engines.sql
|
||||||
echo "✓ Engines imported"
|
echo "✓ Engines imported"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Import transmissions
|
# Import transmissions
|
||||||
echo "📥 Step 3: Importing transmissions..."
|
echo "📥 Step 4: Importing transmissions..."
|
||||||
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/02_transmissions.sql
|
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/02_transmissions.sql
|
||||||
echo "✓ Transmissions imported"
|
echo "✓ Transmissions imported"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Import vehicle options
|
# Import vehicle options
|
||||||
echo "📥 Step 4: Importing vehicle options (56MB - this may take a minute)..."
|
echo "📥 Step 5: Importing vehicle options (this may take a minute)..."
|
||||||
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/03_vehicle_options.sql
|
docker exec -i mvp-postgres psql -U postgres -d motovaultpro < output/03_vehicle_options.sql
|
||||||
echo "✓ Vehicle options imported"
|
echo "✓ Vehicle options imported"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -58,6 +68,7 @@ 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 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 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 vehicle_count 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;"
|
||||||
echo ""
|
echo ""
|
||||||
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT * FROM available_years;"
|
docker exec mvp-postgres psql -U postgres -d motovaultpro -c "SELECT * FROM available_years;"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ CREATE TABLE engines (
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
-- Prevent duplicate engine display names (case-insensitive)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_engines_name_lower ON engines (LOWER(name));
|
||||||
|
|
||||||
CREATE INDEX idx_engines_displacement ON engines(displacement);
|
CREATE INDEX idx_engines_displacement ON engines(displacement);
|
||||||
CREATE INDEX idx_engines_config ON engines(configuration);
|
CREATE INDEX idx_engines_config ON engines(configuration);
|
||||||
@@ -40,6 +42,8 @@ CREATE TABLE transmissions (
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
-- Prevent duplicate transmission display names (case-insensitive)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_transmissions_type_lower ON transmissions (LOWER(type));
|
||||||
|
|
||||||
CREATE INDEX idx_transmissions_type ON transmissions(type);
|
CREATE INDEX idx_transmissions_type ON transmissions(type);
|
||||||
|
|
||||||
@@ -55,6 +59,10 @@ CREATE TABLE vehicle_options (
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
-- Prevent duplicate vehicle option rows
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_vehicle_options_full ON vehicle_options (
|
||||||
|
year, make, model, trim, engine_id, transmission_id
|
||||||
|
);
|
||||||
|
|
||||||
-- Indexes for cascading dropdown performance
|
-- Indexes for cascading dropdown performance
|
||||||
CREATE INDEX idx_vehicle_year ON vehicle_options(year);
|
CREATE INDEX idx_vehicle_year ON vehicle_options(year);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,834 +5,25 @@ BEGIN;
|
|||||||
|
|
||||||
INSERT INTO transmissions (id, type) VALUES
|
INSERT INTO transmissions (id, type) VALUES
|
||||||
(1,'1-Speed Automatic'),
|
(1,'1-Speed Automatic'),
|
||||||
(2,'1-Speed Automatic'),
|
(2,'10-Speed Automatic'),
|
||||||
(3,'1-Speed Automatic'),
|
(3,'2-Speed Automatic'),
|
||||||
(4,'1-Speed Automatic'),
|
(4,'3-Speed Automatic'),
|
||||||
(5,'1-Speed Automatic'),
|
(5,'4-Speed Automatic'),
|
||||||
(6,'1-Speed Automatic'),
|
(6,'4-Speed Manual'),
|
||||||
(7,'10-Speed Automatic'),
|
(7,'5-Speed Automatic'),
|
||||||
(8,'10-Speed Automatic'),
|
(8,'5-Speed Manual'),
|
||||||
(9,'10-Speed Automatic'),
|
(9,'6-Speed Automatic'),
|
||||||
(10,'10-Speed Automatic'),
|
(10,'6-Speed Dual-Clutch'),
|
||||||
(11,'10-Speed Automatic'),
|
(11,'6-Speed Manual'),
|
||||||
(12,'10-Speed Automatic'),
|
(12,'7-Speed Automatic'),
|
||||||
(13,'10-Speed Automatic'),
|
(13,'7-Speed Dual-Clutch'),
|
||||||
(14,'10-Speed Automatic'),
|
(14,'7-Speed Manual'),
|
||||||
(15,'10-Speed Automatic'),
|
(15,'8-Speed Automatic'),
|
||||||
(16,'10-Speed Automatic'),
|
(16,'9-Speed Automatic'),
|
||||||
(17,'10-Speed Automatic'),
|
(17,'Automatic'),
|
||||||
(18,'10-Speed Automatic'),
|
(18,'CVT'),
|
||||||
(19,'2-Speed Automatic'),
|
(19,'Manual');
|
||||||
(20,'2-Speed Automatic'),
|
|
||||||
(21,'2-Speed Automatic'),
|
|
||||||
(22,'2-Speed Automatic'),
|
|
||||||
(23,'2-Speed Automatic'),
|
|
||||||
(24,'2-Speed Automatic'),
|
|
||||||
(25,'3-Speed Automatic'),
|
|
||||||
(26,'3-Speed Automatic'),
|
|
||||||
(27,'3-Speed Manual'),
|
|
||||||
(28,'3-Speed Automatic'),
|
|
||||||
(29,'3-Speed Automatic'),
|
|
||||||
(30,'3-Speed Automatic'),
|
|
||||||
(31,'3-Speed Automatic'),
|
|
||||||
(32,'3-Speed Manual'),
|
|
||||||
(33,'3-Speed Automatic'),
|
|
||||||
(34,'3-Speed Automatic'),
|
|
||||||
(35,'3-Speed Automatic'),
|
|
||||||
(36,'3-Speed Manual'),
|
|
||||||
(37,'3-Speed Manual'),
|
|
||||||
(38,'3-Speed Manual'),
|
|
||||||
(39,'4-Speed Manual'),
|
|
||||||
(40,'4-Speed Automatic'),
|
|
||||||
(41,'4-Speed Automatic'),
|
|
||||||
(42,'4-Speed Automatic'),
|
|
||||||
(43,'4-Speed Manual'),
|
|
||||||
(44,'4-Speed Manual'),
|
|
||||||
(45,'4-Speed Automatic'),
|
|
||||||
(46,'4-Speed Automatic'),
|
|
||||||
(47,'4-Speed Automatic'),
|
|
||||||
(48,'4-Speed Automatic'),
|
|
||||||
(49,'4-Speed Automatic'),
|
|
||||||
(50,'4-Speed Manual'),
|
|
||||||
(51,'4-Speed Automatic'),
|
|
||||||
(52,'4-Speed Automatic'),
|
|
||||||
(53,'4-Speed Automatic'),
|
|
||||||
(54,'4-Speed Automatic'),
|
|
||||||
(55,'4-Speed Automatic'),
|
|
||||||
(56,'4-Speed Automatic'),
|
|
||||||
(57,'4-Speed Automatic'),
|
|
||||||
(58,'4-Speed Automatic'),
|
|
||||||
(59,'4-Speed Automatic'),
|
|
||||||
(60,'4-Speed Manual'),
|
|
||||||
(61,'4-Speed Automatic'),
|
|
||||||
(62,'4-Speed Automatic'),
|
|
||||||
(63,'4-Speed Automatic'),
|
|
||||||
(64,'4-Speed Automatic'),
|
|
||||||
(65,'4-Speed Automatic'),
|
|
||||||
(66,'4-Speed Manual'),
|
|
||||||
(67,'4-Speed Manual'),
|
|
||||||
(68,'4-Speed Manual'),
|
|
||||||
(69,'4-Speed Automatic'),
|
|
||||||
(70,'4-Speed Automatic'),
|
|
||||||
(71,'4-Speed Automatic'),
|
|
||||||
(72,'5-Speed Manual'),
|
|
||||||
(73,'5-Speed Manual'),
|
|
||||||
(74,'5-Speed Automatic'),
|
|
||||||
(75,'5-Speed Automatic'),
|
|
||||||
(76,'5-Speed Automatic'),
|
|
||||||
(77,'5-Speed Automatic'),
|
|
||||||
(78,'5-Speed Automatic'),
|
|
||||||
(79,'5-Speed Automatic'),
|
|
||||||
(80,'5-Speed Manual'),
|
|
||||||
(81,'5-Speed Manual'),
|
|
||||||
(82,'5-Speed Manual'),
|
|
||||||
(83,'5-Speed Manual'),
|
|
||||||
(84,'5-Speed Manual'),
|
|
||||||
(85,'5-Speed Automatic'),
|
|
||||||
(86,'5-Speed Automatic'),
|
|
||||||
(87,'5-Speed Manual'),
|
|
||||||
(88,'5-Speed Manual'),
|
|
||||||
(89,'5-Speed Manual'),
|
|
||||||
(90,'5-Speed Automatic'),
|
|
||||||
(91,'5-Speed Automatic'),
|
|
||||||
(92,'5-Speed Automatic'),
|
|
||||||
(93,'5-Speed Automatic'),
|
|
||||||
(94,'5-Speed Automatic'),
|
|
||||||
(95,'5-Speed Automatic'),
|
|
||||||
(96,'5-Speed Automatic'),
|
|
||||||
(97,'5-Speed Automatic'),
|
|
||||||
(98,'5-Speed Automatic'),
|
|
||||||
(99,'5-Speed Automatic'),
|
|
||||||
(100,'5-Speed Automatic'),
|
|
||||||
(101,'5-Speed Automatic'),
|
|
||||||
(102,'5-Speed Automatic'),
|
|
||||||
(103,'5-Speed Automatic'),
|
|
||||||
(104,'5-Speed Automatic'),
|
|
||||||
(105,'5-Speed Automatic'),
|
|
||||||
(106,'5-Speed Automatic'),
|
|
||||||
(107,'5-Speed Automatic'),
|
|
||||||
(108,'5-Speed Automatic'),
|
|
||||||
(109,'5-Speed Automatic'),
|
|
||||||
(110,'5-Speed Automatic'),
|
|
||||||
(111,'5-Speed Automatic'),
|
|
||||||
(112,'5-Speed Automatic'),
|
|
||||||
(113,'5-Speed Automatic'),
|
|
||||||
(114,'5-Speed Automatic'),
|
|
||||||
(115,'5-Speed Automatic'),
|
|
||||||
(116,'5-Speed Automatic'),
|
|
||||||
(117,'5-Speed Automatic'),
|
|
||||||
(118,'5-Speed Automatic'),
|
|
||||||
(119,'5-Speed Automatic'),
|
|
||||||
(120,'5-Speed Automatic'),
|
|
||||||
(121,'5-Speed Automatic'),
|
|
||||||
(122,'5-Speed Automatic'),
|
|
||||||
(123,'5-Speed Automatic'),
|
|
||||||
(124,'5-Speed Automatic'),
|
|
||||||
(125,'5-Speed Automatic'),
|
|
||||||
(126,'5-Speed Automatic'),
|
|
||||||
(127,'5-Speed Automatic'),
|
|
||||||
(128,'5-Speed Automatic'),
|
|
||||||
(129,'5-Speed Automatic'),
|
|
||||||
(130,'5-Speed Automatic'),
|
|
||||||
(131,'5-Speed Automatic'),
|
|
||||||
(132,'5-Speed Automatic'),
|
|
||||||
(133,'5-Speed Automatic'),
|
|
||||||
(134,'5-Speed Automatic'),
|
|
||||||
(135,'5-Speed Automatic'),
|
|
||||||
(136,'5-Speed Automatic'),
|
|
||||||
(137,'5-Speed Automatic'),
|
|
||||||
(138,'5-Speed Automatic'),
|
|
||||||
(139,'5-Speed Automatic'),
|
|
||||||
(140,'5-Speed Manual'),
|
|
||||||
(141,'5-Speed Manual'),
|
|
||||||
(142,'5-Speed Manual'),
|
|
||||||
(143,'5-Speed Manual'),
|
|
||||||
(144,'5-Speed Manual'),
|
|
||||||
(145,'5-Speed Manual'),
|
|
||||||
(146,'5-Speed Automatic'),
|
|
||||||
(147,'5-Speed Automatic'),
|
|
||||||
(148,'5-Speed Automatic'),
|
|
||||||
(149,'5-Speed Manual'),
|
|
||||||
(150,'5-Speed Manual'),
|
|
||||||
(151,'5-Speed Manual'),
|
|
||||||
(152,'6-Speed Automatic'),
|
|
||||||
(153,'6-Speed Automatic'),
|
|
||||||
(154,'6-Speed Automatic'),
|
|
||||||
(155,'6-Speed Automatic'),
|
|
||||||
(156,'6-Speed Automatic'),
|
|
||||||
(157,'6-Speed Automatic'),
|
|
||||||
(158,'6-Speed Automatic'),
|
|
||||||
(159,'6-Speed Automatic'),
|
|
||||||
(160,'6-Speed Automatic'),
|
|
||||||
(161,'6-Speed Automatic'),
|
|
||||||
(162,'6-Speed Automatic'),
|
|
||||||
(163,'6-Speed Automatic'),
|
|
||||||
(164,'CVT'),
|
|
||||||
(165,'6-Speed Manual'),
|
|
||||||
(166,'6-Speed Manual'),
|
|
||||||
(167,'6-Speed Manual'),
|
|
||||||
(168,'6-Speed Manual'),
|
|
||||||
(169,'6-Speed Manual'),
|
|
||||||
(170,'6-Speed Automatic'),
|
|
||||||
(171,'6-Speed Automatic'),
|
|
||||||
(172,'6-Speed Automatic'),
|
|
||||||
(173,'6-Speed Automatic'),
|
|
||||||
(174,'6-Speed Automatic'),
|
|
||||||
(175,'6-Speed Manual'),
|
|
||||||
(176,'6-Speed Automatic'),
|
|
||||||
(177,'6-Speed Manual'),
|
|
||||||
(178,'6-Speed Manual'),
|
|
||||||
(179,'6-Speed Automatic'),
|
|
||||||
(180,'6-Speed Automatic'),
|
|
||||||
(181,'6-Speed Automatic'),
|
|
||||||
(182,'6-Speed Automatic'),
|
|
||||||
(183,'6-Speed Automatic'),
|
|
||||||
(184,'6-Speed Automatic'),
|
|
||||||
(185,'6-Speed Automatic'),
|
|
||||||
(186,'6-Speed Automatic'),
|
|
||||||
(187,'6-Speed Automatic'),
|
|
||||||
(188,'6-Speed Automatic'),
|
|
||||||
(189,'6-Speed Automatic'),
|
|
||||||
(190,'6-Speed Automatic'),
|
|
||||||
(191,'6-Speed Automatic'),
|
|
||||||
(192,'6-Speed Automatic'),
|
|
||||||
(193,'6-Speed Automatic'),
|
|
||||||
(194,'6-Speed Manual'),
|
|
||||||
(195,'6-Speed Automatic'),
|
|
||||||
(196,'6-Speed Manual'),
|
|
||||||
(197,'6-Speed Manual'),
|
|
||||||
(198,'6-Speed Manual'),
|
|
||||||
(199,'6-Speed Manual'),
|
|
||||||
(200,'6-Speed Manual'),
|
|
||||||
(201,'6-Speed Manual'),
|
|
||||||
(202,'6-Speed Automatic'),
|
|
||||||
(203,'6-Speed Automatic'),
|
|
||||||
(204,'6-Speed Automatic'),
|
|
||||||
(205,'6-Speed Automatic'),
|
|
||||||
(206,'6-Speed Automatic'),
|
|
||||||
(207,'6-Speed Automatic'),
|
|
||||||
(208,'6-Speed Automatic'),
|
|
||||||
(209,'6-Speed Automatic'),
|
|
||||||
(210,'6-Speed Automatic'),
|
|
||||||
(211,'6-Speed Automatic'),
|
|
||||||
(212,'6-Speed Automatic'),
|
|
||||||
(213,'6-Speed Automatic'),
|
|
||||||
(214,'6-Speed Automatic'),
|
|
||||||
(215,'6-Speed Automatic'),
|
|
||||||
(216,'6-Speed Automatic'),
|
|
||||||
(217,'6-Speed Automatic'),
|
|
||||||
(218,'6-Speed Automatic'),
|
|
||||||
(219,'6-Speed Automatic'),
|
|
||||||
(220,'6-Speed Automatic'),
|
|
||||||
(221,'6-Speed Automatic'),
|
|
||||||
(222,'6-Speed Automatic'),
|
|
||||||
(223,'6-Speed Automatic'),
|
|
||||||
(224,'6-Speed Automatic'),
|
|
||||||
(225,'6-Speed Automatic'),
|
|
||||||
(226,'6-Speed Automatic'),
|
|
||||||
(227,'6-Speed Automatic'),
|
|
||||||
(228,'6-Speed Automatic'),
|
|
||||||
(229,'6-Speed Automatic'),
|
|
||||||
(230,'6-Speed Automatic'),
|
|
||||||
(231,'6-Speed Automatic'),
|
|
||||||
(232,'6-Speed Automatic'),
|
|
||||||
(233,'6-Speed Automatic'),
|
|
||||||
(234,'6-Speed Automatic'),
|
|
||||||
(235,'6-Speed Automatic'),
|
|
||||||
(236,'6-Speed Automatic'),
|
|
||||||
(237,'6-Speed Automatic'),
|
|
||||||
(238,'6-Speed Automatic'),
|
|
||||||
(239,'6-Speed Automatic'),
|
|
||||||
(240,'6-Speed Automatic'),
|
|
||||||
(241,'6-Speed Automatic'),
|
|
||||||
(242,'6-Speed Automatic'),
|
|
||||||
(243,'6-Speed Automatic'),
|
|
||||||
(244,'6-Speed Automatic'),
|
|
||||||
(245,'6-Speed Automatic'),
|
|
||||||
(246,'6-Speed Automatic'),
|
|
||||||
(247,'6-Speed Automatic'),
|
|
||||||
(248,'6-Speed Automatic'),
|
|
||||||
(249,'6-Speed Automatic'),
|
|
||||||
(250,'6-Speed Automatic'),
|
|
||||||
(251,'6-Speed Automatic'),
|
|
||||||
(252,'6-Speed Automatic'),
|
|
||||||
(253,'6-Speed Automatic'),
|
|
||||||
(254,'6-Speed Automatic'),
|
|
||||||
(255,'6-Speed Automatic'),
|
|
||||||
(256,'6-Speed Automatic'),
|
|
||||||
(257,'6-Speed Automatic'),
|
|
||||||
(258,'6-Speed Automatic'),
|
|
||||||
(259,'6-Speed Automatic'),
|
|
||||||
(260,'6-Speed Automatic'),
|
|
||||||
(261,'6-Speed Automatic'),
|
|
||||||
(262,'6-Speed Automatic'),
|
|
||||||
(263,'6-Speed Automatic'),
|
|
||||||
(264,'6-Speed Automatic'),
|
|
||||||
(265,'6-Speed Automatic'),
|
|
||||||
(266,'6-Speed Automatic'),
|
|
||||||
(267,'6-Speed Automatic'),
|
|
||||||
(268,'6-Speed Automatic'),
|
|
||||||
(269,'6-Speed Automatic'),
|
|
||||||
(270,'6-Speed Automatic'),
|
|
||||||
(271,'6-Speed Automatic'),
|
|
||||||
(272,'6-Speed Automatic'),
|
|
||||||
(273,'6-Speed Automatic'),
|
|
||||||
(274,'6-Speed Automatic'),
|
|
||||||
(275,'6-Speed Automatic'),
|
|
||||||
(276,'6-Speed Automatic'),
|
|
||||||
(277,'6-Speed Automatic'),
|
|
||||||
(278,'6-Speed Automatic'),
|
|
||||||
(279,'CVT'),
|
|
||||||
(280,'6-Speed Dual-Clutch'),
|
|
||||||
(281,'6-Speed Automatic'),
|
|
||||||
(282,'6-Speed Automatic'),
|
|
||||||
(283,'6-Speed Automatic'),
|
|
||||||
(284,'6-Speed Automatic'),
|
|
||||||
(285,'6-Speed Automatic'),
|
|
||||||
(286,'6-Speed Automatic'),
|
|
||||||
(287,'6-Speed Automatic'),
|
|
||||||
(288,'6-Speed Automatic'),
|
|
||||||
(289,'6-Speed Manual'),
|
|
||||||
(290,'6-Speed Manual'),
|
|
||||||
(291,'6-Speed Manual'),
|
|
||||||
(292,'6-Speed Manual'),
|
|
||||||
(293,'6-Speed Manual'),
|
|
||||||
(294,'6-Speed Manual'),
|
|
||||||
(295,'6-Speed Manual'),
|
|
||||||
(296,'6-Speed Manual'),
|
|
||||||
(297,'6-Speed Manual'),
|
|
||||||
(298,'6-Speed Manual'),
|
|
||||||
(299,'6-Speed Manual'),
|
|
||||||
(300,'6-Speed Manual'),
|
|
||||||
(301,'6-Speed Manual'),
|
|
||||||
(302,'6-Speed Manual'),
|
|
||||||
(303,'6-Speed Automatic'),
|
|
||||||
(304,'6-Speed Automatic'),
|
|
||||||
(305,'6-Speed Automatic'),
|
|
||||||
(306,'6-Speed Dual-Clutch'),
|
|
||||||
(307,'6-Speed Automatic'),
|
|
||||||
(308,'6-Speed Automatic'),
|
|
||||||
(309,'6-Speed Automatic'),
|
|
||||||
(310,'6-Speed Automatic'),
|
|
||||||
(311,'6-Speed Automatic'),
|
|
||||||
(312,'6-Speed Automatic'),
|
|
||||||
(313,'6-Speed Automatic'),
|
|
||||||
(314,'6-Speed Automatic'),
|
|
||||||
(315,'6-Speed Manual'),
|
|
||||||
(316,'6-Speed Manual'),
|
|
||||||
(317,'6-Speed Manual'),
|
|
||||||
(318,'6-Speed Automatic'),
|
|
||||||
(319,'7-Speed Automatic'),
|
|
||||||
(320,'7-Speed Automatic'),
|
|
||||||
(321,'7-Speed Automatic'),
|
|
||||||
(322,'7-Speed Automatic'),
|
|
||||||
(323,'7-Speed Automatic'),
|
|
||||||
(324,'7-Speed Automatic'),
|
|
||||||
(325,'7-Speed Automatic'),
|
|
||||||
(326,'7-Speed Manual'),
|
|
||||||
(327,'7-Speed Automatic'),
|
|
||||||
(328,'7-Speed Automatic'),
|
|
||||||
(329,'7-Speed Automatic'),
|
|
||||||
(330,'7-Speed Automatic'),
|
|
||||||
(331,'7-Speed Automatic'),
|
|
||||||
(332,'7-Speed Automatic'),
|
|
||||||
(333,'7-Speed Automatic'),
|
|
||||||
(334,'7-Speed Automatic'),
|
|
||||||
(335,'7-Speed Automatic'),
|
|
||||||
(336,'7-Speed Automatic'),
|
|
||||||
(337,'7-Speed Automatic'),
|
|
||||||
(338,'7-Speed Automatic'),
|
|
||||||
(339,'7-Speed Automatic'),
|
|
||||||
(340,'7-Speed Automatic'),
|
|
||||||
(341,'7-Speed Automatic'),
|
|
||||||
(342,'7-Speed Automatic'),
|
|
||||||
(343,'7-Speed Automatic'),
|
|
||||||
(344,'7-Speed Automatic'),
|
|
||||||
(345,'7-Speed Automatic'),
|
|
||||||
(346,'7-Speed Automatic'),
|
|
||||||
(347,'7-Speed Automatic'),
|
|
||||||
(348,'7-Speed Automatic'),
|
|
||||||
(349,'7-Speed Automatic'),
|
|
||||||
(350,'7-Speed Automatic'),
|
|
||||||
(351,'7-Speed Automatic'),
|
|
||||||
(352,'7-Speed Automatic'),
|
|
||||||
(353,'7-Speed Automatic'),
|
|
||||||
(354,'7-Speed Automatic'),
|
|
||||||
(355,'7-Speed Automatic'),
|
|
||||||
(356,'7-Speed Automatic'),
|
|
||||||
(357,'7-Speed Automatic'),
|
|
||||||
(358,'7-Speed Automatic'),
|
|
||||||
(359,'7-Speed Automatic'),
|
|
||||||
(360,'7-Speed Automatic'),
|
|
||||||
(361,'7-Speed Automatic'),
|
|
||||||
(362,'7-Speed Automatic'),
|
|
||||||
(363,'7-Speed Automatic'),
|
|
||||||
(364,'7-Speed Automatic'),
|
|
||||||
(365,'7-Speed Automatic'),
|
|
||||||
(366,'7-Speed Automatic'),
|
|
||||||
(367,'7-Speed Automatic'),
|
|
||||||
(368,'7-Speed Automatic'),
|
|
||||||
(369,'7-Speed Automatic'),
|
|
||||||
(370,'7-Speed Automatic'),
|
|
||||||
(371,'7-Speed Automatic'),
|
|
||||||
(372,'7-Speed Automatic'),
|
|
||||||
(373,'7-Speed Automatic'),
|
|
||||||
(374,'7-Speed Automatic'),
|
|
||||||
(375,'7-Speed Automatic'),
|
|
||||||
(376,'7-Speed Automatic'),
|
|
||||||
(377,'7-Speed Automatic'),
|
|
||||||
(378,'7-Speed Automatic'),
|
|
||||||
(379,'7-Speed Automatic'),
|
|
||||||
(380,'7-Speed Automatic'),
|
|
||||||
(381,'7-Speed Automatic'),
|
|
||||||
(382,'7-Speed Automatic'),
|
|
||||||
(383,'7-Speed Automatic'),
|
|
||||||
(384,'7-Speed Automatic'),
|
|
||||||
(385,'7-Speed Automatic'),
|
|
||||||
(386,'7-Speed Automatic'),
|
|
||||||
(387,'7-Speed Automatic'),
|
|
||||||
(388,'7-Speed Automatic'),
|
|
||||||
(389,'7-Speed Automatic'),
|
|
||||||
(390,'7-Speed Automatic'),
|
|
||||||
(391,'7-Speed Automatic'),
|
|
||||||
(392,'7-Speed Automatic'),
|
|
||||||
(393,'7-Speed Automatic'),
|
|
||||||
(394,'7-Speed Automatic'),
|
|
||||||
(395,'7-Speed Automatic'),
|
|
||||||
(396,'7-Speed Automatic'),
|
|
||||||
(397,'7-Speed Automatic'),
|
|
||||||
(398,'7-Speed Automatic'),
|
|
||||||
(399,'7-Speed Automatic'),
|
|
||||||
(400,'7-Speed Automatic'),
|
|
||||||
(401,'7-Speed Automatic'),
|
|
||||||
(402,'7-Speed Automatic'),
|
|
||||||
(403,'7-Speed Automatic'),
|
|
||||||
(404,'7-Speed Automatic'),
|
|
||||||
(405,'7-Speed Automatic'),
|
|
||||||
(406,'7-Speed Automatic'),
|
|
||||||
(407,'7-Speed Automatic'),
|
|
||||||
(408,'7-Speed Automatic'),
|
|
||||||
(409,'7-Speed Automatic'),
|
|
||||||
(410,'7-Speed Automatic'),
|
|
||||||
(411,'7-Speed Automatic'),
|
|
||||||
(412,'7-Speed Automatic'),
|
|
||||||
(413,'7-Speed Automatic'),
|
|
||||||
(414,'7-Speed Automatic'),
|
|
||||||
(415,'7-Speed Automatic'),
|
|
||||||
(416,'7-Speed Automatic'),
|
|
||||||
(417,'7-Speed Automatic'),
|
|
||||||
(418,'7-Speed Automatic'),
|
|
||||||
(419,'7-Speed Automatic'),
|
|
||||||
(420,'7-Speed Automatic'),
|
|
||||||
(421,'7-Speed Automatic'),
|
|
||||||
(422,'7-Speed Automatic'),
|
|
||||||
(423,'7-Speed Automatic'),
|
|
||||||
(424,'7-Speed Automatic'),
|
|
||||||
(425,'7-Speed Automatic'),
|
|
||||||
(426,'7-Speed Automatic'),
|
|
||||||
(427,'7-Speed Automatic'),
|
|
||||||
(428,'7-Speed Automatic'),
|
|
||||||
(429,'7-Speed Automatic'),
|
|
||||||
(430,'7-Speed Automatic'),
|
|
||||||
(431,'7-Speed Automatic'),
|
|
||||||
(432,'7-Speed Automatic'),
|
|
||||||
(433,'7-Speed Automatic'),
|
|
||||||
(434,'7-Speed Automatic'),
|
|
||||||
(435,'7-Speed Automatic'),
|
|
||||||
(436,'7-Speed Automatic'),
|
|
||||||
(437,'7-Speed Automatic'),
|
|
||||||
(438,'7-Speed Automatic'),
|
|
||||||
(439,'7-Speed Automatic'),
|
|
||||||
(440,'7-Speed Automatic'),
|
|
||||||
(441,'7-Speed Automatic'),
|
|
||||||
(442,'7-Speed Automatic'),
|
|
||||||
(443,'7-Speed Automatic'),
|
|
||||||
(444,'7-Speed Automatic'),
|
|
||||||
(445,'7-Speed Automatic'),
|
|
||||||
(446,'7-Speed Automatic'),
|
|
||||||
(447,'7-Speed Automatic'),
|
|
||||||
(448,'7-Speed Automatic'),
|
|
||||||
(449,'7-Speed Automatic'),
|
|
||||||
(450,'7-Speed Automatic'),
|
|
||||||
(451,'7-Speed Automatic'),
|
|
||||||
(452,'7-Speed Automatic'),
|
|
||||||
(453,'7-Speed Automatic'),
|
|
||||||
(454,'7-Speed Automatic'),
|
|
||||||
(455,'CVT'),
|
|
||||||
(456,'7-Speed Dual-Clutch'),
|
|
||||||
(457,'7-Speed Automatic'),
|
|
||||||
(458,'7-Speed Automatic'),
|
|
||||||
(459,'7-Speed Manual'),
|
|
||||||
(460,'7-Speed Automatic'),
|
|
||||||
(461,'7-Speed Automatic'),
|
|
||||||
(462,'7-Speed Automatic'),
|
|
||||||
(463,'7-Speed Manual'),
|
|
||||||
(464,'7-Speed Manual'),
|
|
||||||
(465,'7-Speed Manual'),
|
|
||||||
(466,'7-Speed Manual'),
|
|
||||||
(467,'7-Speed Manual'),
|
|
||||||
(468,'7-Speed Dual-Clutch'),
|
|
||||||
(469,'7-Speed Automatic'),
|
|
||||||
(470,'7-Speed Automatic'),
|
|
||||||
(471,'7-Speed Automatic'),
|
|
||||||
(472,'7-Speed Manual'),
|
|
||||||
(473,'7-Speed Automatic'),
|
|
||||||
(474,'7-Speed Automatic'),
|
|
||||||
(475,'7-Speed Automatic'),
|
|
||||||
(476,'7-Speed Automatic'),
|
|
||||||
(477,'7-Speed Automatic'),
|
|
||||||
(478,'7-Speed Automatic'),
|
|
||||||
(479,'7-Speed Automatic'),
|
|
||||||
(480,'7-Speed Automatic'),
|
|
||||||
(481,'7-Speed Automatic'),
|
|
||||||
(482,'7-Speed Automatic'),
|
|
||||||
(483,'7-Speed Automatic'),
|
|
||||||
(484,'7-Speed Automatic'),
|
|
||||||
(485,'7-Speed Automatic'),
|
|
||||||
(486,'7-Speed Automatic'),
|
|
||||||
(487,'7-Speed Automatic'),
|
|
||||||
(488,'7-Speed Automatic'),
|
|
||||||
(489,'77-Speed Automatic'),
|
|
||||||
(490,'7-Speed Automatic'),
|
|
||||||
(491,'7-Speed Automatic'),
|
|
||||||
(492,'7-Speed Automatic'),
|
|
||||||
(493,'8-Speed Automatic'),
|
|
||||||
(494,'8-Speed Automatic'),
|
|
||||||
(495,'8-Speed Automatic'),
|
|
||||||
(496,'8-Speed Automatic'),
|
|
||||||
(497,'8-Speed Automatic'),
|
|
||||||
(498,'8-Speed Automatic'),
|
|
||||||
(499,'8-Speed Automatic'),
|
|
||||||
(500,'8-Speed Automatic'),
|
|
||||||
(501,'8-Speed Automatic'),
|
|
||||||
(502,'8-Speed Automatic'),
|
|
||||||
(503,'8-Speed Automatic'),
|
|
||||||
(504,'8-Speed Automatic'),
|
|
||||||
(505,'8-Speed Automatic'),
|
|
||||||
(506,'8-Speed Automatic'),
|
|
||||||
(507,'8-Speed Automatic'),
|
|
||||||
(508,'8-Speed Automatic'),
|
|
||||||
(509,'8-Speed Automatic'),
|
|
||||||
(510,'8-Speed Automatic'),
|
|
||||||
(511,'8-Speed Automatic'),
|
|
||||||
(512,'8-Speed Automatic'),
|
|
||||||
(513,'8-Speed Automatic'),
|
|
||||||
(514,'8-Speed Automatic'),
|
|
||||||
(515,'8-Speed Automatic'),
|
|
||||||
(516,'8-Speed Automatic'),
|
|
||||||
(517,'8-Speed Automatic'),
|
|
||||||
(518,'8-Speed Automatic'),
|
|
||||||
(519,'8-Speed Automatic'),
|
|
||||||
(520,'8-Speed Automatic'),
|
|
||||||
(521,'8-Speed Automatic'),
|
|
||||||
(522,'8-Speed Automatic'),
|
|
||||||
(523,'8-Speed Automatic'),
|
|
||||||
(524,'8-Speed Automatic'),
|
|
||||||
(525,'8-Speed Automatic'),
|
|
||||||
(526,'8-Speed Automatic'),
|
|
||||||
(527,'8-Speed Automatic'),
|
|
||||||
(528,'8-Speed Automatic'),
|
|
||||||
(529,'8-Speed Automatic'),
|
|
||||||
(530,'8-Speed Automatic'),
|
|
||||||
(531,'8-Speed Automatic'),
|
|
||||||
(532,'8-Speed Automatic'),
|
|
||||||
(533,'8-Speed Automatic'),
|
|
||||||
(534,'8-Speed Automatic'),
|
|
||||||
(535,'8-Speed Automatic'),
|
|
||||||
(536,'8-Speed Automatic'),
|
|
||||||
(537,'8-Speed Automatic'),
|
|
||||||
(538,'8-Speed Automatic'),
|
|
||||||
(539,'8-Speed Automatic'),
|
|
||||||
(540,'8-Speed Automatic'),
|
|
||||||
(541,'8-Speed Automatic'),
|
|
||||||
(542,'8-Speed Automatic'),
|
|
||||||
(543,'8-Speed Automatic'),
|
|
||||||
(544,'8-Speed Automatic'),
|
|
||||||
(545,'8-Speed Automatic'),
|
|
||||||
(546,'8-Speed Automatic'),
|
|
||||||
(547,'8-Speed Automatic'),
|
|
||||||
(548,'8-Speed Automatic'),
|
|
||||||
(549,'8-Speed Automatic'),
|
|
||||||
(550,'8-Speed Automatic'),
|
|
||||||
(551,'8-Speed Automatic'),
|
|
||||||
(552,'8-Speed Automatic'),
|
|
||||||
(553,'8-Speed Automatic'),
|
|
||||||
(554,'8-Speed Automatic'),
|
|
||||||
(555,'8-Speed Automatic'),
|
|
||||||
(556,'8-Speed Automatic'),
|
|
||||||
(557,'8-Speed Automatic'),
|
|
||||||
(558,'8-Speed Automatic'),
|
|
||||||
(559,'8-Speed Automatic'),
|
|
||||||
(560,'8-Speed Automatic'),
|
|
||||||
(561,'8-Speed Automatic'),
|
|
||||||
(562,'8-Speed Automatic'),
|
|
||||||
(563,'8-Speed Automatic'),
|
|
||||||
(564,'8-Speed Automatic'),
|
|
||||||
(565,'8-Speed Automatic'),
|
|
||||||
(566,'8-Speed Automatic'),
|
|
||||||
(567,'8-Speed Automatic'),
|
|
||||||
(568,'8-Speed Automatic'),
|
|
||||||
(569,'8-Speed Automatic'),
|
|
||||||
(570,'8-Speed Automatic'),
|
|
||||||
(571,'8-Speed Automatic'),
|
|
||||||
(572,'8-Speed Automatic'),
|
|
||||||
(573,'8-Speed Automatic'),
|
|
||||||
(574,'8-Speed Dual-Clutch'),
|
|
||||||
(575,'8-Speed Automatic'),
|
|
||||||
(576,'8-Speed Automatic'),
|
|
||||||
(577,'8-Speed Automatic'),
|
|
||||||
(578,'8-Speed Automatic'),
|
|
||||||
(579,'8-Speed Automatic'),
|
|
||||||
(580,'8-Speed Automatic'),
|
|
||||||
(581,'8-Speed Automatic'),
|
|
||||||
(582,'8-Speed Automatic'),
|
|
||||||
(583,'8-Speed Automatic'),
|
|
||||||
(584,'8-Speed Automatic'),
|
|
||||||
(585,'8-Speed Automatic'),
|
|
||||||
(586,'8-Speed Automatic'),
|
|
||||||
(587,'9-Speed Automatic'),
|
|
||||||
(588,'9-Speed Automatic'),
|
|
||||||
(589,'9-Speed Automatic'),
|
|
||||||
(590,'9-Speed Automatic'),
|
|
||||||
(591,'9-Speed Automatic'),
|
|
||||||
(592,'9-Speed Automatic'),
|
|
||||||
(593,'9-Speed Automatic'),
|
|
||||||
(594,'9-Speed Automatic'),
|
|
||||||
(595,'9-Speed Automatic'),
|
|
||||||
(596,'9-Speed Automatic'),
|
|
||||||
(597,'9-Speed Automatic'),
|
|
||||||
(598,'9-Speed Automatic'),
|
|
||||||
(599,'9-Speed Automatic'),
|
|
||||||
(600,'9-Speed Automatic'),
|
|
||||||
(601,'9-Speed Automatic'),
|
|
||||||
(602,'9-Speed Automatic'),
|
|
||||||
(603,'9-Speed Automatic'),
|
|
||||||
(604,'9-Speed Automatic'),
|
|
||||||
(605,'9-Speed Automatic'),
|
|
||||||
(606,'9-Speed Automatic'),
|
|
||||||
(607,'9-Speed Automatic'),
|
|
||||||
(608,'9-Speed Automatic'),
|
|
||||||
(609,'9-Speed Automatic'),
|
|
||||||
(610,'9-Speed Automatic'),
|
|
||||||
(611,'9-Speed Automatic'),
|
|
||||||
(612,'9-Speed Automatic'),
|
|
||||||
(613,'9-Speed Automatic'),
|
|
||||||
(614,'9-Speed Automatic'),
|
|
||||||
(615,'9-Speed Automatic'),
|
|
||||||
(616,'9-Speed Automatic'),
|
|
||||||
(617,'9-Speed Automatic'),
|
|
||||||
(618,'9-Speed Automatic'),
|
|
||||||
(619,'9-Speed Automatic'),
|
|
||||||
(620,'9-Speed Automatic'),
|
|
||||||
(621,'9-Speed Automatic'),
|
|
||||||
(622,'6-Speed Automatic'),
|
|
||||||
(623,'7-Speed Automatic'),
|
|
||||||
(624,'7-Speed Automatic'),
|
|
||||||
(625,'7-Speed Automatic'),
|
|
||||||
(626,'7-Speed Automatic'),
|
|
||||||
(627,'9-Speed Automatic'),
|
|
||||||
(628,'5-Speed Manual'),
|
|
||||||
(629,'Automatic'),
|
|
||||||
(630,'7-Speed Manual'),
|
|
||||||
(631,'7-Speed Manual'),
|
|
||||||
(632,'Automatic'),
|
|
||||||
(633,'Automatic'),
|
|
||||||
(634,'Automatic'),
|
|
||||||
(635,'Automatic'),
|
|
||||||
(636,'Automatic'),
|
|
||||||
(637,'Automatic'),
|
|
||||||
(638,'6-Speed Automatic'),
|
|
||||||
(639,'Automatic'),
|
|
||||||
(640,'3-Speed Automatic'),
|
|
||||||
(641,'3-Speed Automatic'),
|
|
||||||
(642,'4-Speed Automatic'),
|
|
||||||
(643,'4-Speed Automatic'),
|
|
||||||
(644,'4-Speed Automatic'),
|
|
||||||
(645,'4-Speed Automatic'),
|
|
||||||
(646,'4-Speed Automatic'),
|
|
||||||
(647,'5-Speed Automatic'),
|
|
||||||
(648,'5-Speed Automatic'),
|
|
||||||
(649,'5-Speed Automatic'),
|
|
||||||
(650,'5-Speed Automatic'),
|
|
||||||
(651,'5-Speed Automatic'),
|
|
||||||
(652,'6-Speed Automatic'),
|
|
||||||
(653,'6-Speed Automatic'),
|
|
||||||
(654,'6-Speed Automatic'),
|
|
||||||
(655,'7-Speed Automatic'),
|
|
||||||
(656,'7-Speed Automatic'),
|
|
||||||
(657,'7-Speed Automatic'),
|
|
||||||
(658,'7-Speed Automatic'),
|
|
||||||
(659,'8-Speed Automatic'),
|
|
||||||
(660,'8-Speed Automatic'),
|
|
||||||
(661,'Automatic'),
|
|
||||||
(662,'Automatic'),
|
|
||||||
(663,'Automatic'),
|
|
||||||
(664,'Automatic'),
|
|
||||||
(665,'Automatic'),
|
|
||||||
(666,'Automatic'),
|
|
||||||
(667,'CVT'),
|
|
||||||
(668,'CVT'),
|
|
||||||
(669,'Automatic'),
|
|
||||||
(670,'Automatic'),
|
|
||||||
(671,'CVT'),
|
|
||||||
(672,'CVT'),
|
|
||||||
(673,'CVT'),
|
|
||||||
(674,'CVT'),
|
|
||||||
(675,'CVT'),
|
|
||||||
(676,'CVT'),
|
|
||||||
(677,'CVT'),
|
|
||||||
(678,'CVT'),
|
|
||||||
(679,'CVT'),
|
|
||||||
(680,'Automatic'),
|
|
||||||
(681,'Automatic'),
|
|
||||||
(682,'Automatic'),
|
|
||||||
(683,'CVT'),
|
|
||||||
(684,'CVT'),
|
|
||||||
(685,'CVT'),
|
|
||||||
(686,'CVT'),
|
|
||||||
(687,'CVT'),
|
|
||||||
(688,'CVT'),
|
|
||||||
(689,'CVT'),
|
|
||||||
(690,'CVT'),
|
|
||||||
(691,'CVT'),
|
|
||||||
(692,'CVT'),
|
|
||||||
(693,'CVT'),
|
|
||||||
(694,'CVT'),
|
|
||||||
(695,'CVT'),
|
|
||||||
(696,'CVT'),
|
|
||||||
(697,'CVT'),
|
|
||||||
(698,'Automatic'),
|
|
||||||
(699,'7-Speed Manual'),
|
|
||||||
(700,'CVT'),
|
|
||||||
(701,'CVT'),
|
|
||||||
(702,'CVT'),
|
|
||||||
(703,'CVT'),
|
|
||||||
(704,'Automatic'),
|
|
||||||
(705,'5-Speed Automatic'),
|
|
||||||
(706,'CVT'),
|
|
||||||
(707,'CVT'),
|
|
||||||
(708,'CVT'),
|
|
||||||
(709,'Automatic'),
|
|
||||||
(710,'15-Speed Automatic'),
|
|
||||||
(711,'CVT'),
|
|
||||||
(712,'CVT'),
|
|
||||||
(713,'CVT'),
|
|
||||||
(714,'CVT'),
|
|
||||||
(715,'CVT'),
|
|
||||||
(716,'Automatic'),
|
|
||||||
(717,'Automatic'),
|
|
||||||
(718,'Automatic'),
|
|
||||||
(719,'Automatic'),
|
|
||||||
(720,'Automatic'),
|
|
||||||
(721,'Automatic'),
|
|
||||||
(722,'Automatic'),
|
|
||||||
(723,'Automatic'),
|
|
||||||
(724,'CVT'),
|
|
||||||
(725,'CVT'),
|
|
||||||
(726,'CVT'),
|
|
||||||
(727,'CVT'),
|
|
||||||
(728,'Manual'),
|
|
||||||
(729,'6-Speed Manual'),
|
|
||||||
(730,'8-Speed Manual'),
|
|
||||||
(731,'Automatic'),
|
|
||||||
(732,'CVT'),
|
|
||||||
(733,'150-Speed Automatic'),
|
|
||||||
(734,'6-Speed Automatic'),
|
|
||||||
(735,'6-Speed Automatic'),
|
|
||||||
(736,'6-Speed Automatic'),
|
|
||||||
(737,'6-Speed Automatic'),
|
|
||||||
(738,'6-Speed Automatic'),
|
|
||||||
(739,'6-Speed Automatic'),
|
|
||||||
(740,'Dual-Clutch'),
|
|
||||||
(741,'Automatic'),
|
|
||||||
(742,'Automatic'),
|
|
||||||
(743,'Automatic'),
|
|
||||||
(744,'Automatic'),
|
|
||||||
(745,'Automatic'),
|
|
||||||
(746,'CVT'),
|
|
||||||
(747,'CVT'),
|
|
||||||
(748,'Automatic'),
|
|
||||||
(749,'6-Speed Manual'),
|
|
||||||
(750,'8-Speed Manual'),
|
|
||||||
(751,'355-Speed Manual'),
|
|
||||||
(752,'Manual'),
|
|
||||||
(753,'4-Speed Manual'),
|
|
||||||
(754,'1-Speed Manual'),
|
|
||||||
(755,'2-Speed Manual'),
|
|
||||||
(756,'3-Speed Manual'),
|
|
||||||
(757,'3-Speed Manual'),
|
|
||||||
(758,'4-Speed Manual'),
|
|
||||||
(759,'4-Speed Manual'),
|
|
||||||
(760,'4-Speed Manual'),
|
|
||||||
(761,'5-Speed Manual'),
|
|
||||||
(762,'5-Speed Manual'),
|
|
||||||
(763,'5-Speed Manual'),
|
|
||||||
(764,'6-Speed Manual'),
|
|
||||||
(765,'6-Speed Manual'),
|
|
||||||
(766,'6-Speed Manual'),
|
|
||||||
(767,'CVT'),
|
|
||||||
(768,'Automatic'),
|
|
||||||
(769,'CVT'),
|
|
||||||
(770,'CVT'),
|
|
||||||
(771,'265-Speed Automatic'),
|
|
||||||
(772,'Automatic'),
|
|
||||||
(773,'Automatic'),
|
|
||||||
(774,'6-Speed Automatic'),
|
|
||||||
(775,'Automatic'),
|
|
||||||
(776,'3-Speed Automatic'),
|
|
||||||
(777,'3-Speed Automatic'),
|
|
||||||
(778,'4-Speed Automatic'),
|
|
||||||
(779,'4-Speed Automatic'),
|
|
||||||
(780,'5-Speed Automatic'),
|
|
||||||
(781,'5-Speed Automatic'),
|
|
||||||
(782,'6-Speed Automatic'),
|
|
||||||
(783,'6-Speed Automatic'),
|
|
||||||
(784,'8-Speed Automatic'),
|
|
||||||
(785,'Automatic'),
|
|
||||||
(786,'Automatic'),
|
|
||||||
(787,'Automatic'),
|
|
||||||
(788,'Automatic'),
|
|
||||||
(789,'Automatic'),
|
|
||||||
(790,'Automatic'),
|
|
||||||
(791,'Automatic'),
|
|
||||||
(792,'Dual-Clutch'),
|
|
||||||
(793,'Automatic'),
|
|
||||||
(794,'Automatic'),
|
|
||||||
(795,'Automatic'),
|
|
||||||
(796,'Automatic'),
|
|
||||||
(797,'Automatic'),
|
|
||||||
(798,'Automatic'),
|
|
||||||
(799,'Automatic'),
|
|
||||||
(800,'Automatic'),
|
|
||||||
(801,'Manual'),
|
|
||||||
(802,'Manual'),
|
|
||||||
(803,'Automatic'),
|
|
||||||
(804,'Automatic'),
|
|
||||||
(805,'2-Speed Automatic'),
|
|
||||||
(806,'Automatic'),
|
|
||||||
(807,'Automatic'),
|
|
||||||
(808,'Automatic'),
|
|
||||||
(809,'Automatic'),
|
|
||||||
(810,'Manual'),
|
|
||||||
(811,'Manual'),
|
|
||||||
(812,'Manual'),
|
|
||||||
(813,'Manual'),
|
|
||||||
(814,'Manual'),
|
|
||||||
(815,'Manual'),
|
|
||||||
(816,'Automatic'),
|
|
||||||
(817,'Automatic'),
|
|
||||||
(818,'2-Speed Automatic'),
|
|
||||||
(819,'Automatic'),
|
|
||||||
(820,'Automatic'),
|
|
||||||
(821,'Automatic'),
|
|
||||||
(822,'5-Speed Automatic'),
|
|
||||||
(823,'Automatic'),
|
|
||||||
(824,'Automatic'),
|
|
||||||
(825,'CVT'),
|
|
||||||
(826,'CVT'),
|
|
||||||
(827,'8-Speed Automatic'),
|
|
||||||
(828,'8-Speed Automatic');
|
|
||||||
|
|
||||||
SELECT setval('transmissions_id_seq', 829);
|
SELECT setval('transmissions_id_seq', 19);
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@
|
|||||||
ETL Statistics
|
ETL Statistics
|
||||||
============================================================
|
============================================================
|
||||||
|
|
||||||
Total Engines: 30,066
|
Min Year: 2,000
|
||||||
Total Transmissions: 828
|
Max Year: 2,026
|
||||||
Total Vehicles: 1,122,644
|
Vehicle Records: 1,675,335
|
||||||
Unique Years: 47
|
Engines: 622
|
||||||
Unique Makes: 53
|
Transmissions: 19
|
||||||
Unique Models: 1,741
|
Makes: 54
|
||||||
Year Range: 1980-2026
|
Models: 1,881
|
||||||
|
|||||||
112
data/make-model-import/qa_validate.py
Executable file
112
data/make-model-import/qa_validate.py
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/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()
|
||||||
@@ -20,7 +20,7 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen
|
|||||||
|
|
||||||
*** PERSONALITY ***
|
*** PERSONALITY ***
|
||||||
Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository. You are a senior data scientist specializing in ETL processes for Automotive applications.
|
Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository. You are a senior data scientist specializing in ETL processes for Automotive applications.
|
||||||
Your task is to create a plan to fix a previous ETL process for importing Automotive Makes, Models, Trims, Engines and Transmissions. The resulting data is not clean and accurate. The folder to start research in is the make-model-import folder. There is documentation in there from the previous implementation.
|
Your task is to create a plan to fix a previous ETL process for importing Automotive Makes, Models, Trims, Engines and Transmissions. The resulting data is not clean and accurate. Read @ETL-FIXES.md as that was the latest attempt to fix this problem. The folder to start research in is the make-model-import folder. There is documentation in there from the previous implementation.
|
||||||
|
|
||||||
*** FEATURE ***
|
*** FEATURE ***
|
||||||
- This is focusing on the backend data in the database for the Vehicles features that populates the drop down menus.
|
- This is focusing on the backend data in the database for the Vehicles features that populates the drop down menus.
|
||||||
@@ -51,4 +51,5 @@ Your task is to create a plan to fix a previous ETL process for importing Automo
|
|||||||
- If no specific transmission data is available default to "Manual" or "Automatic"
|
- If no specific transmission data is available default to "Manual" or "Automatic"
|
||||||
|
|
||||||
*** CRITICAL ***
|
*** CRITICAL ***
|
||||||
- Make no assumptions. Ask for clarification on anything not clear.
|
- Make no assumptions. Ask for clarification on anything not clear.
|
||||||
|
- Ultrathink through this problem.
|
||||||
Reference in New Issue
Block a user