Add POST /decode/vin endpoint using Gemini 2.5 Flash for VIN string decoding. Returns structured vehicle data (year, make, model, trim, body/drive/fuel type, engine, transmission) with confidence score. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
2.0 KiB
Python
68 lines
2.0 KiB
Python
"""VIN decode router - Gemini-powered VIN string decoding."""
|
|
import logging
|
|
import re
|
|
import time
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from app.engines.gemini_engine import (
|
|
GeminiEngine,
|
|
GeminiProcessingError,
|
|
GeminiUnavailableError,
|
|
)
|
|
from app.models import VinDecodeRequest, VinDecodeResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/decode", tags=["decode"])
|
|
|
|
_VIN_REGEX = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$")
|
|
|
|
# Shared engine instance (lazy init on first request)
|
|
_gemini_engine = GeminiEngine()
|
|
|
|
|
|
@router.post("/vin", response_model=VinDecodeResponse)
|
|
async def decode_vin(request: VinDecodeRequest) -> VinDecodeResponse:
|
|
"""Decode a VIN string into structured vehicle data using Gemini.
|
|
|
|
Accepts a 17-character VIN and returns year, make, model, trim, etc.
|
|
"""
|
|
vin = request.vin.upper().strip()
|
|
|
|
if not _VIN_REGEX.match(vin):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid VIN format: must be 17 alphanumeric characters (excluding I, O, Q). Got: {vin}",
|
|
)
|
|
|
|
start_ms = time.monotonic_ns() // 1_000_000
|
|
|
|
try:
|
|
result = _gemini_engine.decode_vin(vin)
|
|
except GeminiUnavailableError as exc:
|
|
logger.error("Gemini unavailable for VIN decode: %s", exc)
|
|
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
|
except GeminiProcessingError as exc:
|
|
logger.error("Gemini processing error for VIN %s: %s", vin, exc)
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
|
|
elapsed_ms = (time.monotonic_ns() // 1_000_000) - start_ms
|
|
|
|
return VinDecodeResponse(
|
|
success=True,
|
|
vin=vin,
|
|
year=result.year,
|
|
make=result.make,
|
|
model=result.model,
|
|
trimLevel=result.trim_level,
|
|
bodyType=result.body_type,
|
|
driveType=result.drive_type,
|
|
fuelType=result.fuel_type,
|
|
engine=result.engine,
|
|
transmission=result.transmission,
|
|
confidence=result.confidence,
|
|
processingTimeMs=elapsed_ms,
|
|
error=None,
|
|
)
|