"""OCR router: upload, get results, list results, apply to vehicle.""" import uuid from fastapi import ( APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Query, UploadFile, status, ) from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database import get_db from app.dependencies import get_current_user from app.models.user import User from app.models.ocr_result import OCRStatus from app.schemas.ocr import ( OCRApplyResponse, OCRResultListResponse, OCRResultResponse, OCRUploadResponse, ) from app.services import ocr_service from app.tasks.ocr_processing import run_ocr_processing router = APIRouter(prefix="/ocr", tags=["ocr"]) @router.post( "/upload", response_model=OCRUploadResponse, status_code=status.HTTP_202_ACCEPTED, ) async def upload_scan( background_tasks: BackgroundTasks, file: UploadFile = File(..., description="Image file to OCR"), vehicle_id: str | None = Form(None, description="Optional vehicle ID to link"), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Upload an image file for OCR processing. Returns 202 with ocr_result_id. Processing happens asynchronously. """ if not file or not file.filename: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={"error": {"code": "NO_FILE", "message": "No file provided"}}, ) mime_type = file.content_type or "" if not ocr_service.validate_mime_type(mime_type): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": { "code": "INVALID_MIME_TYPE", "message": f"Invalid MIME type: {mime_type}. Only image/* types are allowed.", } }, ) # Read file content file_bytes = await file.read() if not ocr_service.validate_file_size(len(file_bytes)): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": { "code": "FILE_TOO_LARGE", "message": f"File size exceeds limit of {settings.MAX_FILE_SIZE_MB} MB", } }, ) # Parse optional vehicle_id parsed_vehicle_id: uuid.UUID | None = None if vehicle_id: try: parsed_vehicle_id = uuid.UUID(vehicle_id) except (ValueError, TypeError): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": { "code": "INVALID_VEHICLE_ID", "message": "Invalid vehicle UUID", } }, ) try: ocr_result = await ocr_service.upload_file( db=db, file_bytes=file_bytes, file_name=file.filename or "upload.png", mime_type=mime_type, vehicle_id=parsed_vehicle_id, ) except ValueError as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={"error": {"code": "UPLOAD_FAILED", "message": str(exc)}}, ) # Queue background processing background_tasks.add_task(run_ocr_processing, ocr_result.id) return OCRUploadResponse( message="OCR processing queued", ocr_result_id=ocr_result.id, status=ocr_result.status.value if isinstance(ocr_result.status, OCRStatus) else str(ocr_result.status), ) @router.get( "/results/{result_id}", response_model=OCRResultResponse, status_code=status.HTTP_200_OK, ) async def get_ocr_result( result_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get a single OCR result by ID.""" ocr_result = await ocr_service.get_result(db, result_id) if ocr_result is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "OCR_NOT_FOUND", "message": "OCR result not found"} }, ) return OCRResultResponse.model_validate(ocr_result) @router.get( "/results", response_model=OCRResultListResponse, status_code=status.HTTP_200_OK, ) async def list_ocr_results( db: AsyncSession = Depends(get_db), vehicle_id: uuid.UUID | None = Query(None, description="Filter by vehicle ID"), page: int = Query(1, ge=1, description="Page number"), page_size: int = Query(20, ge=1, le=100, description="Items per page"), current_user: User = Depends(get_current_user), ): """List OCR results, optionally filtered by vehicle_id.""" items, total = await ocr_service.list_results( db=db, vehicle_id=vehicle_id, page=page, page_size=page_size, ) return OCRResultListResponse( items=[OCRResultResponse.model_validate(item) for item in items], total=total, page=page, page_size=page_size, ) @router.post( "/results/{result_id}/apply", response_model=OCRApplyResponse, status_code=status.HTTP_200_OK, ) async def apply_ocr_to_vehicle( result_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Apply OCR structured data to the linked vehicle.""" try: ocr_result, vehicle, updated_fields = await ocr_service.apply_to_vehicle( db, result_id ) except ValueError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"error": {"code": "APPLY_FAILED", "message": str(exc)}}, ) return OCRApplyResponse( message="OCR data applied to vehicle", ocr_result_id=ocr_result.id, vehicle_id=vehicle.id, updated_fields=updated_fields, )