"""FastAPI app factory. Composes all domain routers and middleware.""" import logging import os import time from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from src.core.exceptions import ( ChunkingError, BenchmarkError, QueryError, chunking_exception_handler, benchmark_exception_handler, ) from src.documents.routes import router as documents_router from src.benchmarking.routes import router as benchmarking_router from src.admin.routes import router as admin_router from src.storage.sqlite import init_db # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) # ── Request Logging Middleware ───────────────────────────────────── class RequestLoggingMiddleware(BaseHTTPMiddleware): """Log request method, path, status code, and duration.""" async def dispatch(self, request: Request, call_next) -> Response: start = time.time() method = request.method path = request.url.path response = await call_next(request) duration = (time.time() - start) * 1000 # ms status = response.status_code # Log at appropriate level if status >= 500: logger.error("%s %s -> %d (%.1fms)", method, path, status, duration) elif status >= 400: logger.warning("%s %s -> %d (%.1fms)", method, path, status, duration) else: logger.info("%s %s -> %d (%.1fms)", method, path, status, duration) return response def create_app() -> FastAPI: """Create and configure the FastAPI application. Mounts domain routers and registers exception handlers. """ # Ensure SQLite tables exist at startup init_db() app = FastAPI( title="RAG Chunking Benchmarker", description="Benchmark five chunking strategies on regulatory documents. " "Compare retrieval quality, answer faithfulness, and cost.", version="0.1.0", ) # CORS — allow all origins for development app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Request logging app.add_middleware(RequestLoggingMiddleware) # Register exception handlers app.add_exception_handler(ChunkingError, chunking_exception_handler) app.add_exception_handler(BenchmarkError, benchmark_exception_handler) app.add_exception_handler(QueryError, benchmark_exception_handler) # Mount domain routers app.include_router(documents_router) app.include_router(benchmarking_router) app.include_router(admin_router) # Mount dashboard at /app static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") app.mount("/app", StaticFiles(directory=static_dir, html=True), name="dashboard") return app app = create_app()