Gifted-oNe's picture
chore: Update OCR engine and related services
6a9e389
Raw
History Blame Contribute Delete
5.95 kB
"""
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"])
@router.post("/upload", response_model=ExtractionResponse)
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)
@router.get("/tier-status")
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