Files
Autoparts-DB/pos/services/vin_decoder.py
consultoria-as f9589f4a4e feat(pos): add inventory-aware chat context (#31) and VIN decoder (#17)
Chat now fetches tenant inventory summary (brands, counts, low-stock)
and injects it into the AI system prompt so responses prioritize local
stock. VIN decoder uses free NHTSA vPIC API to decode 17-char VINs and
auto-fills the vehicle selector dropdowns when a catalog match is found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:14:45 +00:00

55 lines
1.8 KiB
Python

"""VIN Decoder using NHTSA vPIC API (free, no key needed)."""
import re
import requests
def decode_vin(vin):
"""Decode a VIN number using NHTSA API.
Returns: {make, model, year, engine, body_type, plant_country, fuel_type, error}
"""
vin = (vin or "").strip().upper()
# Validate: 17 alphanumeric chars, no I/O/Q
if not re.match(r'^[A-HJ-NPR-Z0-9]{17}$', vin):
return {"error": "VIN debe tener exactamente 17 caracteres alfanumericos (sin I, O, Q)."}
url = f"https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValues/{vin}?format=json"
resp = requests.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()["Results"][0]
error_text = data.get("ErrorText", "") or ""
# NHTSA returns error codes like "0 - ..." for no error
has_real_error = error_text and not error_text.startswith("0")
displacement = data.get("DisplacementL", "") or ""
cylinders = data.get("EngineCylinders", "") or ""
engine_parts = []
if displacement:
engine_parts.append(f"{displacement}L")
if cylinders:
engine_parts.append(f"{cylinders}cyl")
engine_str = " ".join(engine_parts)
model_year = data.get("ModelYear", "")
year_int = None
if model_year:
try:
year_int = int(model_year)
except (ValueError, TypeError):
pass
return {
"vin": vin,
"make": data.get("Make", "") or "",
"model": data.get("Model", "") or "",
"year": year_int,
"engine": engine_str,
"body_type": data.get("BodyClass", "") or "",
"plant_country": data.get("PlantCountry", "") or "",
"fuel_type": data.get("FuelTypePrimary", "") or "",
"drive_type": data.get("DriveType", "") or "",
"trim": data.get("Trim", "") or "",
"error": error_text if has_real_error else "",
}