"""API endpoints registered on the Gradio Server. Every endpoint uses ``@app.api(...)`` so it runs through Gradio's queue (concurrency-managed, ZeroGPU-safe) and is reachable from ``gradio_client`` and the frontend JS client. All endpoints are placeholders: they call the placeholder backend functions and return canned data. No real parsing/embedding/RAG is wired yet. """ from __future__ import annotations from gradio import Server from gradio.data_classes import FileData from ..indexing.store import BACKEND from ..parsing.directive import parse_directive from ..parsing.lonstatistik import parse_lonstatistik from ..rag import answer from ..schemas import ( ChatMessage, Chunk, IndexStatus, ParseResult, Profile, ) def register(app: Server) -> None: """Register all backend endpoints on the given Gradio Server.""" @app.api(name="parse_directive") def parse_directive_endpoint(lang: str = "en") -> list[dict]: """Parse Directive 2023/970 into article-level chunks (placeholder).""" chunks: list[Chunk] = parse_directive(lang=lang) return [c.model_dump() for c in chunks] @app.api(name="parse_lonstatistik") def parse_lonstatistik_endpoint( file: FileData, source: str = "unknown" ) -> dict: """OCR/table-parse an uploaded lønstatistik PDF (placeholder).""" result: ParseResult = parse_lonstatistik( file_path=file["path"] if file else None, source=source ) return result.model_dump() @app.api(name="index_documents") def index_documents_endpoint(source: str = "directive") -> dict: """Report the loaded vector index status. The index is built offline (``scripts/build_index.py``); this endpoint reports how many chunks are loaded, not a live (re)build. """ from ..indexing.store import VectorStore store = VectorStore() persisted = store.load() return IndexStatus( source=source, indexed_chunks=len(store), backend=BACKEND, persisted=persisted, ).model_dump() @app.api(name="warmup") def warmup_endpoint() -> dict: """Pre-load the chat models (embedder + LLM) on GPU. Fired by the frontend when onboarding starts, so the model is ready by the time the user reaches the chat. Best-effort: returns a status dict and never errors the UI. """ from ..rag import warmup return warmup() @app.api(name="chat") def chat_endpoint(messages: list[dict], lang: str = "en", context: str = "") -> dict: """Chat with the RAG assistant: retrieve grounded chunks, cite sources. Returns the full ``{reply, citations, disclaimer}`` in one response. ``context`` is the user's compact salary-dashboard summary, injected so the answer can be specific to their situation. """ msgs = [ChatMessage(**m) for m in messages] return answer(msgs, lang=lang, context=context or None).model_dump() @app.api(name="dashboard") def dashboard_endpoint(profile: dict) -> dict: """Build the profile-driven salary dashboard (structured IDA retrieval). Takes the onboarding answers (a ``Profile``) and returns the fused ``Dashboard`` — per-axis comparison cells, reliability-gated, projected to 2026, with the user's percentile and gap. CPU-only (no embedding). """ from ..fusion import build_dashboard return build_dashboard(Profile(**profile)).model_dump()