Spaces:
Sleeping
Sleeping
| """Append-only audit log of moderator decisions. | |
| Each record is one human decision over a campaign: the AI's recommendation, the human's final call, | |
| who made it, whether it was an override, and the reason. This is the receipt that the **human** — | |
| not the AI — owns the decision. | |
| Two backends behind one interface: when `DATABASE_URL` is set (Neon Postgres on the deployed Space) | |
| the records persist there and survive restarts; otherwise they go to a flat JSONL file (easy to show | |
| locally, trivially greppable). Callers (`api.py`) don't care which is active. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from .config import CONFIG | |
| def utc_now_iso() -> str: | |
| return datetime.now(timezone.utc).isoformat() | |
| def _use_db() -> bool: | |
| """Use Postgres when a database is configured; otherwise the local JSONL file.""" | |
| return bool(CONFIG.database_url) | |
| def append_decision(record: dict, path: str | Path | None = None) -> dict: | |
| """Append one decision record to the log (a `timestamp` is added if absent). | |
| Returns the stored record. Writes to Postgres when configured, else the JSONL file (creating its | |
| parent dir if needed). | |
| """ | |
| stored = {"timestamp": utc_now_iso(), **record} | |
| if _use_db(): | |
| from . import db # lazy: only pulls in psycopg when a database is configured | |
| db.insert_decision(stored) | |
| return stored | |
| p = Path(path or CONFIG.audit_log_path) | |
| p.parent.mkdir(parents=True, exist_ok=True) | |
| with p.open("a", encoding="utf-8") as f: | |
| f.write(json.dumps(stored, ensure_ascii=False) + "\n") | |
| return stored | |
| def load_decisions(path: str | Path | None = None) -> list[dict]: | |
| """Load all decision records in write order (oldest first). Tolerates a missing/partial file.""" | |
| if _use_db(): | |
| from . import db # lazy | |
| return db.fetch_decisions() | |
| p = Path(path or CONFIG.audit_log_path) | |
| if not p.exists(): | |
| return [] | |
| out: list[dict] = [] | |
| for line in p.read_text(encoding="utf-8").splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| out.append(json.loads(line)) | |
| except json.JSONDecodeError: | |
| continue # skip a half-written line rather than crash the UI | |
| return out | |
| def decided_campaign_ids(path: str | Path | None = None) -> dict[str, dict]: | |
| """Map each decided campaign_id to its MOST RECENT decision record.""" | |
| latest: dict[str, dict] = {} | |
| for rec in load_decisions(path): | |
| cid = rec.get("campaign_id") | |
| if cid: | |
| latest[cid] = rec | |
| return latest | |