feat: add VIN decode endpoint to OCR Python service (refs #224)
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>
This commit is contained in:
67
ocr/app/routers/decode.py
Normal file
67
ocr/app/routers/decode.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user