# Audit cuts (/docs/audit-cuts)



Audit pages answer &#x2A;*"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 [#file-layout]

Each cut is a Python module under `api/audit/cuts/<cut_id>.py`
exporting two things:

```python
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 [#rendering]

Each leaf page (`/audit/<page>/page.tsx`) is \~30 lines:

```tsx
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 [#adding-a-chart]

1. Drop a backend module: `api/audit/cuts/<cut_id>.py` with `META` + `compute`.
2. Drop a renderer: `dashboard/app/audit/_shared/cuts/<cut_id>.tsx`.
3. Register the renderer in `_shared/cuts/index.ts`.
4. Run `./bin/deploy.sh` (or at least re-run `python -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 [#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.
