📤 Upload
Drag & drop or click to browse
PDF, PNG, JPG (max 50MB)Processing...
📊 Extraction Results
Upload a document to see extraction results
""" FastAPI Application Factory Creates and configures the FastAPI application. """ from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from typing import TYPE_CHECKING from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse from .config import AppConfig, default_config from backend.web.services import InferenceService from backend.web.services.storage_helpers import get_storage_helper # Public API imports from backend.web.api.v1.public import ( create_inference_router, create_async_router, set_async_service, create_labeling_router, ) # Async processing imports from backend.data.async_request_db import AsyncRequestDB from backend.web.workers.async_queue import AsyncTaskQueue from backend.web.services.async_processing import AsyncProcessingService from backend.web.dependencies import init_dependencies from backend.web.core.rate_limiter import RateLimiter # Admin API imports from backend.web.api.v1.admin import ( create_annotation_router, create_augmentation_router, create_auth_router, create_documents_router, create_locks_router, create_training_router, ) from backend.web.api.v1.admin.dashboard import create_dashboard_router from backend.web.core.scheduler import start_scheduler, stop_scheduler from backend.web.core.autolabel_scheduler import start_autolabel_scheduler, stop_autolabel_scheduler # Batch upload imports from backend.web.api.v1.batch.routes import router as batch_upload_router from backend.web.workers.batch_queue import init_batch_queue, shutdown_batch_queue from backend.web.services.batch_upload import BatchUploadService from backend.data.repositories import ModelVersionRepository if TYPE_CHECKING: from collections.abc import AsyncGenerator logger = logging.getLogger(__name__) def create_app(config: AppConfig | None = None) -> FastAPI: """ Create and configure FastAPI application. Args: config: Application configuration. Uses default if not provided. Returns: Configured FastAPI application """ config = config or default_config # Create model path resolver that reads from database def get_active_model_path(): """Resolve active model path from database.""" try: model_repo = ModelVersionRepository() active_model = model_repo.get_active() if active_model and active_model.model_path: return active_model.model_path except Exception as e: logger.warning(f"Failed to get active model from database: {e}") return None # Create inference service with database model resolver inference_service = InferenceService( model_config=config.model, storage_config=config.storage, model_path_resolver=get_active_model_path, ) # Create async processing components async_db = AsyncRequestDB() rate_limiter = RateLimiter(async_db) task_queue = AsyncTaskQueue( max_size=config.async_processing.queue_max_size, worker_count=config.async_processing.worker_count, ) async_service = AsyncProcessingService( inference_service=inference_service, db=async_db, queue=task_queue, rate_limiter=rate_limiter, async_config=config.async_processing, storage_config=config.storage, ) # Initialize dependencies for FastAPI init_dependencies(async_db, rate_limiter) set_async_service(async_service) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """Application lifespan manager.""" logger.info("Starting Invoice Inference API...") # Initialize async request database tables try: async_db.create_tables() logger.info("Async database tables ready") except Exception as e: logger.error(f"Failed to initialize async database: {e}") # Initialize admin database tables (admin_tokens, admin_documents, training_tasks, etc.) try: from backend.data.database import create_db_and_tables create_db_and_tables() logger.info("Admin database tables ready") except Exception as e: logger.error(f"Failed to initialize admin database: {e}") # Initialize inference service on startup try: inference_service.initialize() logger.info("Inference service ready") except Exception as e: logger.error(f"Failed to initialize inference service: {e}") # Continue anyway - service will retry on first request # Start async processing service try: async_service.start() logger.info("Async processing service started") except Exception as e: logger.error(f"Failed to start async processing: {e}") # Start batch upload queue try: batch_service = BatchUploadService() init_batch_queue(batch_service) logger.info("Batch upload queue started") except Exception as e: logger.error(f"Failed to start batch upload queue: {e}") # Start training scheduler try: start_scheduler() logger.info("Training scheduler started") except Exception as e: logger.error(f"Failed to start training scheduler: {e}") # Start auto-label scheduler try: start_autolabel_scheduler() logger.info("AutoLabel scheduler started") except Exception as e: logger.error(f"Failed to start autolabel scheduler: {e}") yield logger.info("Shutting down Invoice Inference API...") # Stop auto-label scheduler try: stop_autolabel_scheduler() logger.info("AutoLabel scheduler stopped") except Exception as e: logger.error(f"Error stopping autolabel scheduler: {e}") # Stop training scheduler try: stop_scheduler() logger.info("Training scheduler stopped") except Exception as e: logger.error(f"Error stopping training scheduler: {e}") # Stop batch upload queue try: shutdown_batch_queue() logger.info("Batch upload queue stopped") except Exception as e: logger.error(f"Error stopping batch upload queue: {e}") # Stop async processing service try: async_service.stop(timeout=30.0) logger.info("Async processing service stopped") except Exception as e: logger.error(f"Error stopping async service: {e}") # Close database connection try: async_db.close() logger.info("Database connection closed") except Exception as e: logger.error(f"Error closing database: {e}") # Create FastAPI app # Store inference service for access by routes (e.g., model reload) # This will be set after app creation app = FastAPI( title="Invoice Field Extraction API", description=""" REST API for extracting fields from Swedish invoices. ## Features - YOLO-based field detection - OCR text extraction - Field normalization and validation - Visualization of detections ## Supported Fields - InvoiceNumber - InvoiceDate - InvoiceDueDate - OCR (reference number) - Bankgiro - Plusgiro - Amount - supplier_org_number (Swedish organization number) - customer_number - payment_line (machine-readable payment code) """, version="1.0.0", lifespan=lifespan, ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount static files for results using StorageHelper storage = get_storage_helper() results_dir = storage.get_results_base_path() if results_dir: app.mount( "/static/results", StaticFiles(directory=str(results_dir)), name="results", ) else: logger.warning("Could not mount static results directory: local storage not available") # Include public API routes inference_router = create_inference_router(inference_service, config.storage) app.include_router(inference_router) async_router = create_async_router(config.storage.allowed_extensions) app.include_router(async_router, prefix="/api/v1") labeling_router = create_labeling_router(inference_service, config.storage) app.include_router(labeling_router) # Include admin API routes auth_router = create_auth_router() app.include_router(auth_router, prefix="/api/v1") documents_router = create_documents_router(config.storage) app.include_router(documents_router, prefix="/api/v1") locks_router = create_locks_router() app.include_router(locks_router, prefix="/api/v1") annotation_router = create_annotation_router() app.include_router(annotation_router, prefix="/api/v1") training_router = create_training_router() app.include_router(training_router, prefix="/api/v1") augmentation_router = create_augmentation_router() app.include_router(augmentation_router, prefix="/api/v1/admin") # Include dashboard routes dashboard_router = create_dashboard_router() app.include_router(dashboard_router, prefix="/api/v1") # Include batch upload routes app.include_router(batch_upload_router) # Store inference service in app state for access by routes app.state.inference_service = inference_service # Root endpoint - serve HTML UI @app.get("/", response_class=HTMLResponse) async def root() -> str: """Serve the web UI.""" return get_html_ui() return app def get_html_ui() -> str: """Generate HTML UI for the web application.""" return """
Upload a Swedish invoice (PDF or image) to extract fields automatically
Drag & drop or click to browse
PDF, PNG, JPG (max 50MB)Processing...
Upload a document to see extraction results