Audit cuts
How the narrative pages under /audit are built.
Audit pages answer "why is this number what it is?" They sit one click below the cockpit and break a topic — revenue, clients, journeys — into a sequence of small charts (cuts) with prose between them.
File layout
Each cut is a Python module under api/audit/cuts/<cut_id>.py
exporting two things:
META = {
"page": "clients", # /audit/<page>
"title": "...",
"window_default": "30d",
}
def compute(conn, *, window: str) -> dict:
# Returns the JSON payload the renderer consumes.
...The module is the source of truth. api.scripts.refresh_audit
auto-syncs heartbeat.audit_cut_registry from disk — no per-file SQL,
no DB-level write guard. heartbeat.audit_cut_snapshot caches the
payload per (cut_id, window_key).
Rendering
Each leaf page (/audit/<page>/page.tsx) is ~30 lines:
export default function Page() {
return <AuditPage page="clients" />;
}The page-level scaffolding lives in
dashboard/app/audit/_shared/AuditPage.tsx. A cut_id → React component
registry in _shared/cuts/index.ts maps each cut to its renderer.
Adding a chart
- Drop a backend module:
api/audit/cuts/<cut_id>.pywithMETA+compute. - Drop a renderer:
dashboard/app/audit/_shared/cuts/<cut_id>.tsx. - Register the renderer in
_shared/cuts/index.ts. - Run
./bin/deploy.sh(or at least re-runpython -m api.scripts.refresh_audit).
Without refresh_audit, new cuts surface as 404 on the audit page —
the registry is what the page reads from, and the registry only knows
what disk-sync has told it.
Verification
Cuts carry the same verification_state and owner columns as
metrics, PATCHed via /api/audit/cuts/{id}/verification. Auto-saves
on owner blur; both fields are independent.