Major features: - Pixel-Perfect glassmorphism design (landing + POS + public catalog) - OEM/Local catalog toggle with Nexpart taxonomy (14 groups, 108 subgroups, 558 part types) - Marketplace B2B Phase 1 (bodegas, POs, status machine, WA+email notifications) - Peer-to-peer inventory (multi-instance, LAN discovery) - WhatsApp: photo→Vision AI, voice→Whisper, conversational quotations - Smart unified search (VIN/plate/part_number/keyword auto-detect) - Shop Supplies tab (vehicle-independent parts) - Chatbot AI fallback chain (5 models) + response cache - CSV inventory import tool + setup_instance.sh installer - Tablet-responsive CSS + sidebar toggle - Filters, export CSV, employee edit, business data save - Quotation system (WA→POS) with auto-print on confirmation - Live stats on landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
67 lines
2.3 KiB
Python
67 lines
2.3 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"
|
|
# NHTSA's free API can be slow (5-30s). Retry once on timeout.
|
|
import time
|
|
for attempt in range(2):
|
|
try:
|
|
resp = requests.get(url, timeout=25)
|
|
resp.raise_for_status()
|
|
break
|
|
except requests.exceptions.Timeout:
|
|
if attempt == 0:
|
|
time.sleep(2)
|
|
continue
|
|
return {"error": "El servidor NHTSA no respondio. Intenta de nuevo en unos segundos."}
|
|
except requests.exceptions.RequestException as e:
|
|
return {"error": f"Error de conexion con NHTSA: {str(e)[:100]}"}
|
|
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 "",
|
|
}
|