Download src/payloads.py from anne-voigt/pdac-knowledge-agent-dev: direct link, hf CLI and curl.
- Browser
- Download file 5.11 kB
-
https://huggingface.co/spaces/anne-voigt/pdac-knowledge-agent-dev/resolve/main/src/payloads.py
- Command line
-
hf download hf://spaces/anne-voigt/pdac-knowledge-agent-dev/src/payloads.py
-
curl -L -o payloads.py https://huggingface.co/spaces/anne-voigt/pdac-knowledge-agent-dev/resolve/main/src/payloads.py
5.11 kB
| """Payload assembly — every answer this Space returns goes through here. | |
| The contract (ADR-0001 §5, accepted 2026-10-20), on top of the family invariants: | |
| - Every payload is a dict with ``status`` in {"ok", "refused", "error", "denied"} plus a | |
| human-readable ``reason`` on every non-ok status. Refusals are answers, never exceptions. | |
| - Every payload except a denial carries a ``run_id`` (the gate mints none for a caller it | |
| turned away). | |
| - Every ``ok`` payload carries ``kind: "annotation"`` and **no** ``hierarchy`` or | |
| ``n_for_inference``. This is the deliberate break from the data specialists: a row count here | |
| is not a sample size, and nothing downstream may render it as one. | |
| - Every row carries a complete ``provenance`` block (:mod:`src.provenance`); :func:`ok` refuses to | |
| build a payload around a row that does not. | |
| - ``coverage_notes`` and ``attribution`` come from config for every source the payload used, and | |
| the deterministic ``narrative`` says "according to <source> (queried <date>)". | |
| """ | |
| from __future__ import annotations | |
| import datetime | |
| import json | |
| import secrets | |
| from collections.abc import Iterable | |
| from typing import Any | |
| from src import config | |
| from src.provenance import missing_fields | |
| STATUSES = ("ok", "refused", "error", "denied") | |
| SOURCE_ERROR_REASONS = ("source_unavailable", "rate_limited", "upstream_schema_drift") | |
| KIND = "annotation" | |
| DISCLAIMER = ( | |
| "External annotation, not analysis: these rows are what the named public sources say, " | |
| "as of the query date on each row. Nothing here was computed from a dataset, and no " | |
| "statistic was run." | |
| ) | |
| def mint_run_id() -> str: | |
| """A sortable, collision-resistant run id (sibling-agent format).""" | |
| stamp = datetime.datetime.now(datetime.UTC).strftime("%Y%m%d_%H%M%S") | |
| return f"{stamp}_{secrets.randbelow(10**6):06d}" | |
| def source_error(source: str, reason: str, detail: str) -> dict[str, str]: | |
| if reason not in SOURCE_ERROR_REASONS: | |
| raise ValueError(f"source error reason must be one of {SOURCE_ERROR_REASONS}") | |
| return {"source": source, "reason": reason, "detail": detail} | |
| def _attribution(source_ids: Iterable[str]) -> list[dict[str, str]]: | |
| out = [] | |
| for sid in sorted(set(source_ids)): | |
| src = config.source(sid) | |
| out.append( | |
| { | |
| "source": sid, | |
| "name": src["name"], | |
| "citation": src["citation"], | |
| "licence": src["licence"], | |
| "licence_url": src["licence_url"], | |
| } | |
| ) | |
| return out | |
| def _coverage_notes(source_ids: Iterable[str]) -> list[dict[str, str]]: | |
| return [ | |
| {"source": sid, "note": note} | |
| for sid in sorted(set(source_ids)) | |
| for note in config.source(sid)["coverage_notes"] | |
| ] | |
| def ok( | |
| endpoint: str, | |
| *, | |
| query: dict[str, Any], | |
| rows: list[dict[str, Any]], | |
| sources_used: Iterable[str], | |
| narrative: str, | |
| unresolved: Iterable[dict[str, str]] = (), | |
| source_errors: Iterable[dict[str, str]] = (), | |
| run_id: str | None = None, | |
| **extra: Any, | |
| ) -> dict[str, Any]: | |
| """An ok payload. Raises ValueError (a programming error) on an unstamped row.""" | |
| for i, row in enumerate(rows): | |
| gaps = missing_fields(row) | |
| if gaps: | |
| raise ValueError(f"row {i} is missing provenance fields {gaps}") | |
| if not narrative.strip(): | |
| raise ValueError("an ok payload needs its deterministic narrative") | |
| used = list(sources_used) | |
| forbidden = {"hierarchy", "n_for_inference"} & set(extra) | |
| if forbidden: | |
| raise ValueError(f"annotation payloads never carry {sorted(forbidden)}") | |
| payload: dict[str, Any] = { | |
| "status": "ok", | |
| "run_id": run_id or mint_run_id(), | |
| "kind": KIND, | |
| "endpoint": endpoint, | |
| "query": query, | |
| "rows": rows, | |
| "unresolved": list(unresolved), | |
| "source_errors": list(source_errors), | |
| "coverage_notes": _coverage_notes(used), | |
| "attribution": _attribution(used), | |
| "narrative": narrative, | |
| "disclaimer": DISCLAIMER, | |
| } | |
| payload.update(extra) | |
| return payload | |
| def refused(endpoint: str, reason: str, message: str, *, run_id: str | None = None) -> dict: | |
| return { | |
| "status": "refused", | |
| "run_id": run_id or mint_run_id(), | |
| "endpoint": endpoint, | |
| "reason": reason, | |
| "message": message, | |
| } | |
| def error( | |
| endpoint: str, | |
| reason: str, | |
| message: str, | |
| *, | |
| source_errors: Iterable[dict[str, str]] = (), | |
| run_id: str | None = None, | |
| ) -> dict: | |
| return { | |
| "status": "error", | |
| "run_id": run_id or mint_run_id(), | |
| "endpoint": endpoint, | |
| "reason": reason, | |
| "message": message, | |
| "source_errors": list(source_errors), | |
| } | |
| def denied(message: str) -> dict: | |
| return {"status": "denied", "reason": "access_denied", "message": message} | |
| def to_json(payload: dict[str, Any]) -> str: | |
| if payload.get("status") not in STATUSES: | |
| raise ValueError(f"unknown status {payload.get('status')!r}") | |
| return json.dumps(payload, ensure_ascii=False) | |