""" Ukweli — Document Catalog Endpoint GET /documents — Browse and filter the document registry. Public tier (IP rate-limited) per Endpoint Security Matrix. """ from __future__ import annotations import logging from fastapi import APIRouter, Depends, Query from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.api.auth import AuthContext, require_auth from app.db.session import get_db_session from app.models.database import Document from app.models.schemas import DocumentListResponse, DocumentOut logger = logging.getLogger("ukweli.api.documents") router = APIRouter(tags=["Documents"]) @router.get("/documents", response_model=DocumentListResponse) async def list_documents( auditee: str | None = Query(None, description="Filter by auditee name (partial match)"), fy: str | None = Query(None, description="Filter by fiscal year, e.g. 2024/25"), report_type: str | None = Query(None, description="Filter by report type enum value"), status: str | None = Query(None, description="Filter by processing status"), limit: int = Query(20, ge=1, le=100, description="Number of results to return"), offset: int = Query(0, ge=0, description="Pagination offset"), db: AsyncSession = Depends(get_db_session), _auth: AuthContext = Depends(require_auth), # public tier rate limit ) -> DocumentListResponse: """ List documents from the catalog with optional filters. Supports pagination via limit/offset. No authentication required, but public-tier rate limits apply. """ query = select(Document) count_query = select(func.count(Document.id)) # Apply filters if auditee: query = query.where(Document.auditee.ilike(f"%{auditee}%")) count_query = count_query.where(Document.auditee.ilike(f"%{auditee}%")) if fy: query = query.where(Document.fiscal_year == fy) count_query = count_query.where(Document.fiscal_year == fy) if report_type: query = query.where(Document.report_type == report_type) count_query = count_query.where(Document.report_type == report_type) if status: query = query.where(Document.status == status) count_query = count_query.where(Document.status == status) # Get total count total_result = await db.execute(count_query) total = total_result.scalar_one() # Apply pagination and ordering query = query.order_by(Document.published_date.desc().nullslast(), Document.created_at.desc()) query = query.limit(limit).offset(offset) result = await db.execute(query) documents = result.scalars().all() return DocumentListResponse( documents=[ DocumentOut( id=doc.id, title=doc.title, fiscal_year=doc.fiscal_year, auditee=doc.auditee, report_type=doc.report_type, page_count=doc.page_count, download_url=doc.pdf_url, published_date=doc.published_date, status=doc.status, ) for doc in documents ], total=total, limit=limit, offset=offset, ) @router.post("/documents/trigger-scraper", status_code=202) async def trigger_scraper( max_pages: int | None = Query(None, description="Max pages to crawl"), _auth: AuthContext = Depends(require_auth), # public tier rate limit ) -> dict[str, str]: """ Trigger the OAG Kenya spider to discover and crawl new reports. Runs asynchronously in the background. """ from app.scraper.workers.pdf_processor import run_scraper run_scraper.delay(max_pages=max_pages) return {"status": "triggered", "message": "Scraper task has been queued."}