"""Sheetly FastAPI application entrypoint.""" from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from app.api import batches, documents, export from app.config import get_settings from app.db import get_session_factory, init_db from app.schemas import HealthResponse from app.services.seed import seed_demo_if_empty logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger("sheetly") @asynccontextmanager async def lifespan(_: FastAPI): """Create folders, database, and demo seed on startup.""" settings = get_settings() Path("data/db").mkdir(parents=True, exist_ok=True) Path(settings.upload_path).mkdir(parents=True, exist_ok=True) Path(settings.export_path).mkdir(parents=True, exist_ok=True) await init_db() factory = get_session_factory() async with factory() as session: seeded = await seed_demo_if_empty(session) if seeded: logger.info("Demo seed: %s files queued", seeded) logger.info( "Sheetly ready (openai=%s)", settings.has_openai, ) yield settings = get_settings() app = FastAPI( title=settings.app_name, version="1.0.0", description=( "Sheetly — senior document extraction to Excel / JSON. " "Batch upload, vision+text routing, schema-safe fields, " "confidence review, and structured exports." ), lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(batches.router) app.include_router(documents.router) app.include_router(export.router) static_dir = Path(__file__).parent / "static" app.mount( "/static", StaticFiles(directory=str(static_dir)), name="static", ) @app.get("/api/health", response_model=HealthResponse) async def health() -> HealthResponse: """Health check.""" current = get_settings() return HealthResponse( status="ok", app=current.app_name, openai=current.has_openai, confidence_threshold=current.confidence_threshold, text_model=current.openai_model, vision_model=current.openai_vision_model, ) @app.get("/") async def home() -> FileResponse: """Landing / upload page.""" return FileResponse(static_dir / "index.html") @app.get("/validate") async def validate_page() -> FileResponse: """Validation review screen.""" return FileResponse(static_dir / "validate.html") @app.get("/dashboard") async def dashboard_page() -> FileResponse: """Operator dashboard.""" return FileResponse(static_dir / "dashboard.html")