"""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, )