"""FastAPI application entrypoint. This file is intentionally small: - Routes call into a job manager. - The job manager calls the pipeline. Keeping the web layer thin makes the business logic easier to test and maintain. """ from __future__ import annotations import logging from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException from notebook_tools.jobs import JobManager, get_job_manager from notebook_tools.logging_utils import configure_logging from notebook_tools.models import JobStartRequest, JobStatusResponse from notebook_tools.settings import Settings, get_settings app = FastAPI(title="notebook-tools", version="0.1.0") logger = logging.getLogger("notebook_tools.api") @app.on_event("startup") async def _startup() -> None: # Load settings once at startup so we fail fast if env vars are missing. settings = get_settings() configure_logging(level=settings.log_level) logger.info("Service starting up") @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} @app.post("/jobs/paperless/{document_id}", response_model=JobStatusResponse) async def start_job_for_paperless_document( document_id: int, req: JobStartRequest, background: BackgroundTasks, settings: Settings = Depends(get_settings), manager: JobManager = Depends(get_job_manager), ) -> JobStatusResponse: """Start an OCR job for an existing Paperless document id.""" if document_id <= 0: raise HTTPException(status_code=422, detail="document_id must be a positive integer") job = manager.create_job(document_id=document_id, notebook_id=req.notebook_id) logger.info( "Job created job_id=%s paperless_document_id=%s notebook_id=%s", job.job_id, document_id, req.notebook_id, ) background.add_task( manager.run_job, job_id=job.job_id, settings=settings, ocr_prompt_override=req.ocr_prompt, title_prefix=req.title_prefix, ) return job @app.get("/jobs/{job_id}", response_model=JobStatusResponse) async def get_job(job_id: str, manager: JobManager = Depends(get_job_manager)) -> JobStatusResponse: job = manager.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="job not found") return job