Spaces:
Running
Running
| """``/processing-log`` (admin) β filterable Operations log view. | |
| Path is kebab-case per CONTRACT.md Β§9. Filters are camelCase queries: | |
| - ``bookId`` (not ``book_id``) | |
| - ``stage`` | |
| - ``status`` β one of ``success | failed | in_flight`` | |
| - ``since`` β ISO timestamp lower bound on ``started_at`` | |
| - ``limit`` (default 100, max 500) | |
| Admin-only because the log surfaces error messages from every book the | |
| user has ingested β sensitive enough to keep behind ``require_admin``. | |
| """ | |
| from __future__ import annotations | |
| from fastapi import APIRouter, Depends, Query | |
| from src.api.deps import require_admin | |
| from src.api.dto.processing_log import ProcessingLogEntryDTO, ProcessingOutcomeEnum | |
| from src.lib.auth.models import User | |
| from src.lib.processing.models import StageRun | |
| from src.lib.processing.repo import query | |
| router = APIRouter(tags=["processing-log"]) | |
| _STATUS_TO_OUTCOME: dict[ProcessingOutcomeEnum, str] = { | |
| ProcessingOutcomeEnum.success: "success", | |
| ProcessingOutcomeEnum.failed: "failed", | |
| ProcessingOutcomeEnum.in_flight: "in-flight", | |
| } | |
| def _to_dto(run: StageRun) -> ProcessingLogEntryDTO: | |
| if run.success == 1: | |
| outcome = ProcessingOutcomeEnum.success | |
| elif run.success == 0: | |
| outcome = ProcessingOutcomeEnum.failed | |
| else: | |
| outcome = ProcessingOutcomeEnum.in_flight | |
| return ProcessingLogEntryDTO( | |
| id=run.id, | |
| book_id=run.book_id, | |
| stage=run.stage, | |
| started_at=run.started_at, | |
| finished_at=run.finished_at, | |
| outcome=outcome, | |
| pages_done=run.pages_done, | |
| engine=run.engine, | |
| provider=run.provider, | |
| backend=run.backend, | |
| where=run.where, | |
| error=run.error, | |
| duration_ms=run.duration_ms, | |
| ) | |
| def list_processing_log( | |
| _: User = Depends(require_admin), | |
| book_id: str | None = Query(default=None, alias="bookId"), | |
| stage: str | None = Query(default=None), | |
| status: ProcessingOutcomeEnum | None = Query(default=None), | |
| since: str | None = Query( | |
| default=None, | |
| description="ISO timestamp lower bound on started_at; applied client-side", | |
| ), | |
| limit: int = Query(default=100, ge=1, le=500), | |
| ) -> list[ProcessingLogEntryDTO]: | |
| """Filterable list β newest first. | |
| The repo's :func:`query` does book/stage/outcome filtering in SQL; | |
| the ``since`` filter applies in Python since the repo signature | |
| doesn't expose it (a Stage 3 router shouldn't grow the lib's | |
| signature for one filter; if the FE wants this pushed down later we | |
| revisit). | |
| """ | |
| outcome_str = _STATUS_TO_OUTCOME[status] if status is not None else None | |
| runs = query( | |
| book_id=book_id, | |
| stage=stage, | |
| outcome=outcome_str, | |
| limit=limit if since is None else max(limit * 2, 500), | |
| ) | |
| if since: | |
| runs = [r for r in runs if r.started_at >= since] | |
| return [_to_dto(r) for r in runs[:limit]] | |