Spaces:
Sleeping
Sleeping
| """ | |
| Upload Router — Handles file uploads, extraction, and JSON response. | |
| POST /api/upload | |
| - Accepts multipart/form-data with a single file | |
| - Validates file type, size, and user tier | |
| - Routes to PDF Parser or OCR Engine based on file analysis | |
| - Returns structured JSON with extracted fields | |
| """ | |
| import time | |
| from pathlib import Path | |
| from fastapi import APIRouter, UploadFile, File, Form, Header, HTTPException | |
| from fastapi.responses import JSONResponse | |
| from models.schemas import ( | |
| ExtractionResponse, | |
| ExtractionMetadata, | |
| ExtractedField, | |
| FileType, | |
| ProcessingLane, | |
| DocumentType, | |
| ErrorResponse, | |
| ) | |
| from services.file_router import detect_processing_lane, get_pdf_page_count | |
| from services.pdf_parser import extract_from_pdf, extract_tables_as_fields | |
| from services.ocr_engine import extract_from_image, extract_from_scanned_pdf | |
| from services.json_mapper import map_text_to_fields | |
| from services.summarizer import generate_summary | |
| from services.tier_manager import check_upload_allowed, record_usage | |
| from utils.helpers import ( | |
| validate_file_type, | |
| generate_temp_filename, | |
| cleanup_temp_file, | |
| UPLOAD_DIR, | |
| MAX_FILE_SIZE_UNREGISTERED, | |
| ) | |
| router = APIRouter(prefix="/api", tags=["upload"]) | |
| async def upload_file( | |
| file: UploadFile = File(...), | |
| document_type: str = Form(default="form"), | |
| x_session_token: str = Header(default="anonymous"), | |
| x_user_registered: str = Header(default="false"), | |
| ): | |
| """ | |
| Upload a file for extraction. Returns structured JSON with field-value pairs. | |
| Headers: | |
| - X-Session-Token: Session identifier from frontend localStorage | |
| - X-User-Registered: "true" if user is authenticated via Supabase | |
| """ | |
| start_time = time.time() | |
| is_registered = x_user_registered.lower() == "true" | |
| doc_type_enum = DocumentType(document_type) if document_type in [e.value for e in DocumentType] else DocumentType.FORM | |
| # Validate file type | |
| file_type = validate_file_type(file.filename or "unknown", file.content_type) | |
| if not file_type: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Unsupported file type. Please upload PDF, JPG, or PNG files only.", | |
| ) | |
| # Read file content to check size | |
| content = await file.read() | |
| file_size = len(content) | |
| # Check tier limits | |
| tier_check = check_upload_allowed(x_session_token, file_size, is_registered) | |
| if not tier_check.allowed: | |
| raise HTTPException(status_code=429, detail=tier_check.message) | |
| # Save to temp file | |
| temp_filename = generate_temp_filename(file.filename or "upload") | |
| temp_path = UPLOAD_DIR / temp_filename | |
| try: | |
| with open(temp_path, "wb") as f: | |
| f.write(content) | |
| # Detect processing lane | |
| lane = detect_processing_lane(temp_path, file_type) | |
| # Process the file | |
| fields: list[ExtractedField] = [] | |
| page_count = 1 | |
| raw_text = "" | |
| if lane == ProcessingLane.PDF_PARSER: | |
| pdf_result = extract_from_pdf(temp_path) | |
| page_count = pdf_result["page_count"] | |
| raw_text = pdf_result["raw_text"] | |
| if doc_type_enum == DocumentType.FORM: | |
| table_fields = extract_tables_as_fields(pdf_result.get("tables", [])) | |
| fields = map_text_to_fields(raw_text=raw_text, tables=table_fields) | |
| else: | |
| fields = [ExtractedField(name="Extracted Text", value=raw_text, field_type="text", confidence=0.95)] | |
| elif lane == ProcessingLane.OCR_ENGINE: | |
| if file_type == "pdf": | |
| ocr_result = extract_from_scanned_pdf(temp_path) | |
| page_count = ocr_result["page_count"] | |
| else: | |
| ocr_result = extract_from_image(temp_path, preprocess=True) | |
| page_count = 1 | |
| raw_text = ocr_result["raw_text"] | |
| if doc_type_enum == DocumentType.FORM: | |
| fields = map_text_to_fields(raw_text=raw_text, ocr_blocks=ocr_result.get("blocks", [])) | |
| else: | |
| fields = [ExtractedField(name="Extracted Text", value=raw_text, field_type="text", confidence=0.95)] | |
| # Generate AI Summary | |
| summary = generate_summary(raw_text, is_registered=is_registered) | |
| # Record the usage | |
| record_usage(x_session_token) | |
| processing_time = int((time.time() - start_time) * 1000) | |
| # Calculate actual average confidence from extracted fields | |
| if fields: | |
| avg_confidence = sum(f.confidence for f in fields) / len(fields) | |
| else: | |
| avg_confidence = 0.0 | |
| return ExtractionResponse( | |
| success=True, | |
| fields=fields, | |
| summary=summary, | |
| metadata=ExtractionMetadata( | |
| filename=file.filename or "unknown", | |
| file_type=FileType(file_type), | |
| processing_lane=lane, | |
| document_type=doc_type_enum, | |
| page_count=page_count, | |
| processing_time_ms=processing_time, | |
| confidence_score=round(avg_confidence * 100, 1), | |
| ), | |
| message=f"Extracted {len(fields)} fields from {page_count} page(s) in {processing_time}ms", | |
| ) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Processing error: {str(e)}", | |
| ) | |
| finally: | |
| cleanup_temp_file(temp_path) | |
| async def get_tier( | |
| x_session_token: str = Header(default="anonymous"), | |
| x_user_registered: str = Header(default="false"), | |
| ): | |
| """Get the current usage status for the session.""" | |
| from services.tier_manager import get_tier_status | |
| is_registered = x_user_registered.lower() == "true" | |
| tier = get_tier_status(x_session_token, is_registered) | |
| return tier | |