Files
AR-VINchecker/src/api/nhtsa.py
T
alro65 df09627ccb feat: 7-module VINchecker v2 — NICB, auction history, Florida DMV, theft stats, odometer fraud, safety ratings, risk score expansion
New modules: nicb.py, auction_history.py, dmv_florida.py, theft_stats.py, odometer_validator.py
Expanded risk.py with 8 new factors (NICB alert, safety rating, auction flags, odo validation, theft level, odo fraud, DMV lien, open VIN recalls)
PDF now has 11 sections including NICB check, theft stats, odometer fraud, odometer validation, auction history, Florida HSMV
AI prompt enriched with all new data fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 13:54:16 -04:00

154 lines
5.9 KiB
Python

import httpx
_VPIC_BASE = "https://vpic.nhtsa.dot.gov/api/vehicles"
_NHTSA_BASE = "https://api.nhtsa.gov"
_FIELD_MAP = {
"Make": "Make",
"Model": "Model",
"Model Year": "ModelYear",
"Trim": "Trim",
"Body Class": "BodyClass",
"Doors": "Doors",
"Displacement (L)": "DisplacementL",
"Engine Number of Cylinders": "Cylinders",
"Engine Horsepower": "Horsepower",
"Engine Model": "EngineModel",
"Fuel Type - Primary": "FuelType",
"Drive Type": "DriveType",
"Transmission Style": "Transmission",
"Brake System Type": "BrakeSystem",
"Gross Vehicle Weight Rating": "GVWR",
"Plant Country": "PlantCountry",
"Plant City": "PlantCity",
"Electrification Level": "EVLevel",
"Series": "Series",
}
_SKIP_VALS = {"Not Applicable", "null", "None", "0", ""}
async def decode_vin(vin: str) -> dict:
url = f"{_VPIC_BASE}/decodevin/{vin}?format=json"
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url)
resp.raise_for_status()
data = resp.json()
except Exception:
return {}
result: dict = {}
for item in data.get("Results", []):
var = item.get("Variable", "")
val = (item.get("Value") or "").strip()
if var in _FIELD_MAP and val and val not in _SKIP_VALS:
result[_FIELD_MAP[var]] = val
return result
async def fetch_vin_recalls(vin: str) -> list:
"""Returns recalls that apply to this specific VIN (not just make/model/year)."""
url = f"{_NHTSA_BASE}/recalls/recallsByVehicleId"
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params={"vin": vin})
resp.raise_for_status()
return resp.json().get("results", [])
except Exception:
return []
async def fetch_safety_ratings(year: str, make: str, model: str) -> dict:
"""Returns NHTSA 5-star crash test ratings for the vehicle."""
if not (year and make and model):
return {}
try:
async with httpx.AsyncClient(timeout=15) as client:
# Step 1: get list of vehicle variants
r1 = await client.get(
f"{_NHTSA_BASE}/SafetyRatings/modelyear/{year}/make/{make}/model/{model}"
)
r1.raise_for_status()
variants = r1.json().get("Results", [])
if not variants:
return {}
vehicle_id = variants[0].get("VehicleId")
if not vehicle_id:
return {}
# Step 2: get ratings for that variant
r2 = await client.get(f"{_NHTSA_BASE}/SafetyRatings/VehicleId/{vehicle_id}")
r2.raise_for_status()
results = r2.json().get("Results", [])
if not results:
return {}
r = results[0]
return {
"vehicle_desc": r.get("VehicleDescription", ""),
"overall": r.get("OverallRating", "NR"),
"frontal_driver": r.get("FrontCrashDriversideRating", "NR"),
"frontal_passenger": r.get("FrontCrashPassengersideRating", "NR"),
"side_driver": r.get("SideCrashDriversideRating", "NR"),
"side_passenger": r.get("SideCrashPassengersideRating", "NR"),
"rollover": r.get("RolloverRating", "NR"),
"rollover_risk_pct": r.get("RolloverPossibility", ""),
"pole_crash": r.get("SidePoleCrashRating", "NR"),
"esc": r.get("NHTSAElectronicStabilityControl", ""),
"fcw": r.get("NHTSAForwardCollisionWarning", ""),
"ldw": r.get("NHTSALaneDepartureWarning", ""),
}
except Exception:
return {}
async def fetch_recalls(make: str, model: str, year: str) -> list:
url = f"{_NHTSA_BASE}/recalls/recallsByVehicle"
params = {"make": make, "model": model, "modelYear": year}
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params=params)
resp.raise_for_status()
return resp.json().get("results", [])
except Exception:
return []
async def fetch_complaints(make: str, model: str, year: str) -> list:
url = f"{_NHTSA_BASE}/complaints/complaintsByVehicle"
params = {"make": make, "model": model, "modelYear": year}
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params=params)
resp.raise_for_status()
return resp.json().get("results", [])
except Exception:
return []
def check_odometer_complaints(complaints: list) -> dict:
"""Filter already-fetched complaints for odometer/speedometer fraud mentions."""
_ODO_KW = ("odometer", "speedometer", "mileage", "rollback", "rolled back", "fraud")
hits = []
for c in complaints:
comp = (c.get("Component") or c.get("components") or c.get("component") or "").lower()
desc = (c.get("summary") or c.get("Summary") or c.get("description") or "").lower()
if any(kw in comp or kw in desc for kw in _ODO_KW):
hits.append(c)
return {"count": len(hits), "samples": hits[:3]}
async def fetch_investigations(make: str, model: str, year: str) -> list:
url = f"{_NHTSA_BASE}/investigations/investigationsByVehicle"
params = {"make": make, "model": model, "modelYear": year}
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params=params)
resp.raise_for_status()
return resp.json().get("results", [])
except Exception:
return []