Homepage Redesign
This commit is contained in:
116
archive/platform-services/vehicles/api/routes/vehicles.py
Normal file
116
archive/platform-services/vehicles/api/routes/vehicles.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
import asyncpg
|
||||
from ..dependencies import get_db, get_cache
|
||||
# DropdownService deprecated; using normalized schema service
|
||||
from ..services.vehicles_service import VehiclesService
|
||||
from ..repositories.vehicles_repository import VehiclesRepository
|
||||
from ..services.cache_service import CacheService
|
||||
from ..models.responses import (
|
||||
MakesResponse, ModelsResponse, TrimsResponse,
|
||||
EnginesResponse,
|
||||
MakeItem, ModelItem, TrimItem, EngineItem
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/vehicles", tags=["Vehicles"])
|
||||
|
||||
@router.get("/years", response_model=list[int])
|
||||
async def get_years(
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache),
|
||||
):
|
||||
"""Get available model years (distinct, desc)"""
|
||||
service = VehiclesService(cache, VehiclesRepository())
|
||||
return await service.get_years(db)
|
||||
|
||||
@router.get("/makes", response_model=MakesResponse)
|
||||
async def get_makes(
|
||||
year: int = Query(..., description="Model year", ge=1980, le=2050),
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache)
|
||||
):
|
||||
"""Get makes for a specific year
|
||||
|
||||
Hierarchical API: First level - requires year parameter only
|
||||
"""
|
||||
try:
|
||||
service = VehiclesService(cache, VehiclesRepository())
|
||||
makes = await service.get_makes(db, year)
|
||||
return MakesResponse(makes=[MakeItem(**m) for m in makes])
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get makes for year {year}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to retrieve makes for year {year}"
|
||||
)
|
||||
|
||||
@router.get("/models", response_model=ModelsResponse)
|
||||
async def get_models(
|
||||
year: int = Query(..., description="Model year", ge=1980, le=2050),
|
||||
make_id: int = Query(..., description="Make ID", ge=1),
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache)
|
||||
):
|
||||
"""Get models for year and make
|
||||
|
||||
Hierarchical API: Second level - requires year and make_id parameters
|
||||
"""
|
||||
try:
|
||||
service = VehiclesService(cache, VehiclesRepository())
|
||||
models = await service.get_models(db, year, make_id)
|
||||
return ModelsResponse(models=[ModelItem(**m) for m in models])
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get models for year {year}, make {make_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to retrieve models for year {year}, make {make_id}"
|
||||
)
|
||||
|
||||
@router.get("/trims", response_model=TrimsResponse)
|
||||
async def get_trims(
|
||||
year: int = Query(..., description="Model year", ge=1980, le=2050),
|
||||
make_id: int = Query(..., description="Make ID", ge=1),
|
||||
model_id: int = Query(..., description="Model ID", ge=1),
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache)
|
||||
):
|
||||
"""Get trims for year, make, and model
|
||||
|
||||
Hierarchical API: Third level - requires year, make_id, and model_id parameters
|
||||
"""
|
||||
try:
|
||||
service = VehiclesService(cache, VehiclesRepository())
|
||||
trims = await service.get_trims(db, year, model_id)
|
||||
return TrimsResponse(trims=[TrimItem(**t) for t in trims])
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get trims for year {year}, make {make_id}, model {model_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to retrieve trims for year {year}, make {make_id}, model {model_id}"
|
||||
)
|
||||
|
||||
@router.get("/engines", response_model=EnginesResponse)
|
||||
async def get_engines(
|
||||
year: int = Query(..., description="Model year", ge=1980, le=2050),
|
||||
make_id: int = Query(..., description="Make ID", ge=1),
|
||||
model_id: int = Query(..., description="Model ID", ge=1),
|
||||
trim_id: int = Query(..., description="Trim ID", ge=1),
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache)
|
||||
):
|
||||
"""Get engines for year, make, model, and trim"""
|
||||
try:
|
||||
service = VehiclesService(cache, VehiclesRepository())
|
||||
engines = await service.get_engines(db, year, model_id, trim_id)
|
||||
return EnginesResponse(engines=[EngineItem(**e) for e in engines])
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to get engines for year {year}, make {make_id}, model {model_id}, trim {trim_id}: {e}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=(
|
||||
f"Failed to retrieve engines for year {year}, make {make_id}, model {model_id}, trim {trim_id}"
|
||||
)
|
||||
)
|
||||
110
archive/platform-services/vehicles/api/routes/vin.py
Normal file
110
archive/platform-services/vehicles/api/routes/vin.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
import asyncpg
|
||||
from ..dependencies import get_db, get_cache
|
||||
from ..services.cache_service import CacheService
|
||||
from ..models.responses import VINDecodeRequest, VINDecodeResponse, VINDecodeResult
|
||||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/vehicles", tags=["VIN Decoding"])
|
||||
|
||||
def validate_vin(vin: str) -> bool:
|
||||
"""Validate VIN format"""
|
||||
if len(vin) != 17:
|
||||
return False
|
||||
|
||||
# VIN cannot contain I, O, Q
|
||||
if any(char in vin.upper() for char in ['I', 'O', 'Q']):
|
||||
return False
|
||||
|
||||
# Must be alphanumeric
|
||||
if not re.match(r'^[A-HJ-NPR-Z0-9]{17}$', vin.upper()):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@router.post("/vindecode", response_model=VINDecodeResponse)
|
||||
async def decode_vin(
|
||||
request: VINDecodeRequest,
|
||||
db: asyncpg.Connection = Depends(get_db),
|
||||
cache: CacheService = Depends(get_cache)
|
||||
):
|
||||
"""Decode VIN using PostgreSQL function with MSSQL parity
|
||||
|
||||
Uses the vehicles.f_decode_vin() function to decode VIN with confidence scoring
|
||||
"""
|
||||
vin = request.vin.upper().strip()
|
||||
|
||||
# Validate VIN format
|
||||
if not validate_vin(vin):
|
||||
return VINDecodeResponse(
|
||||
vin=vin,
|
||||
result=None,
|
||||
success=False,
|
||||
error="Invalid VIN format"
|
||||
)
|
||||
|
||||
# Check cache first
|
||||
cache_key = f"vin:decode:{vin}"
|
||||
cached_result = await cache.get(cache_key)
|
||||
if cached_result:
|
||||
logger.debug(f"VIN decode result for {vin} retrieved from cache")
|
||||
return VINDecodeResponse(**cached_result)
|
||||
|
||||
try:
|
||||
# Call PostgreSQL VIN decode function
|
||||
query = """
|
||||
SELECT * FROM vehicles.f_decode_vin($1)
|
||||
"""
|
||||
|
||||
row = await db.fetchrow(query, vin)
|
||||
|
||||
if row:
|
||||
result = VINDecodeResult(
|
||||
make=row['make'],
|
||||
model=row['model'],
|
||||
year=row['year'],
|
||||
trim_name=row['trim_name'],
|
||||
engine_description=row['engine_description'],
|
||||
transmission_description=row['transmission_description'],
|
||||
horsepower=row.get('horsepower'),
|
||||
torque=row.get('torque'),
|
||||
top_speed=row.get('top_speed'),
|
||||
fuel=row.get('fuel'),
|
||||
confidence_score=float(row['confidence_score']) if row['confidence_score'] else 0.0,
|
||||
vehicle_type=row.get('vehicle_type')
|
||||
)
|
||||
|
||||
response = VINDecodeResponse(
|
||||
vin=vin,
|
||||
result=result,
|
||||
success=True
|
||||
)
|
||||
|
||||
# Cache successful decode for 30 days
|
||||
await cache.set(cache_key, response.dict(), ttl=30*24*3600)
|
||||
|
||||
logger.info(f"Successfully decoded VIN {vin}: {result.make} {result.model} {result.year}")
|
||||
return response
|
||||
else:
|
||||
# No result found
|
||||
response = VINDecodeResponse(
|
||||
vin=vin,
|
||||
result=None,
|
||||
success=False,
|
||||
error="VIN not found in database"
|
||||
)
|
||||
|
||||
# Cache negative result for 1 hour
|
||||
await cache.set(cache_key, response.dict(), ttl=3600)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to decode VIN {vin}: {e}")
|
||||
return VINDecodeResponse(
|
||||
vin=vin,
|
||||
result=None,
|
||||
success=False,
|
||||
error="Internal server error during VIN decoding"
|
||||
)
|
||||
Reference in New Issue
Block a user