> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ntropii.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quality checks

> ntro.capabilities.checks.run_quality_check — AI-driven validation of activity outputs that surfaces in the Tenant UI review screens.

Quality checks let a runbook ask a separate model to *evaluate* whatever the previous step produced — extracted invoice fields, a proposed journal, a calculated NAV — before a human ever sees it. The check result feeds the Tenant UI's review screen alongside the underlying value, so the accountant reviewing the output sees the AI's own assessment of "I'm pretty sure I got this right" or "this looks off, please double-check the GL mapping".

## Install

```bash theme={null}
pip install 'ntro[workflow]'
```

The capability is bundled with the workflow extra.

## The API

```python theme={null}
from ntro.capabilities.checks import run_quality_check

check_result = await run_quality_check(
    payload=...,                     # Whatever you're checking
    check_slug="journal-balance-v1", # Routes to a check definition
    context={...},                   # Optional grounding info
)
```

Returns a structured `CheckResult` with:

| Field                          | Type                          | What's in it                                                 |
| ------------------------------ | ----------------------------- | ------------------------------------------------------------ |
| `result.passed`                | `bool`                        | The headline answer                                          |
| `result.severity`              | `"info" \| "warn" \| "error"` | How urgent the finding is                                    |
| `result.summary`               | `str`                         | One-line human summary for the UI                            |
| `result.findings`              | `list[Finding]`               | Itemised observations, each with field path + explanation    |
| `result.suggested_corrections` | `list[Correction]`            | Optional suggested edits the human can accept with one click |

## Wiring into a runbook

Quality checks are typically run as a Temporal activity right after the thing they're checking. Lifted from `nav-monthly-journals`:

```python theme={null}
from ntro.workflow import activity

from ntro.capabilities.checks import run_quality_check
from ntro.workflow.task import (
    find_committed_document_activity,
    load_previous_task_activity,
)

# Re-export the SDK's check + history activities so the runbook bundle's
# activities discovery picks them up. Without these re-exports, the worker
# only registers locally defined activities and check calls fail with
# "activity not registered".
__all__ = [
    "parse_starting_tb",
    "propose_journal",
    "commit_journal_proposal",
    "run_quality_check",
    "load_previous_task_activity",
    "find_committed_document_activity",
]
```

<Note>
  **The re-export pattern matters.** `run_quality_check` is implemented in the SDK, but the worker only registers activities it can find via the runbook's `activities.py`. Re-exporting `run_quality_check` (and the other shared activities you need) in `__all__` is what wires it into your bundle.

  `load_previous_task_activity` is re-exported above because checks frequently want **prior-period baselines** for MoM deltas. See [Task lookups](/sdk/task) for the full helper.
</Note>

The actual call site is just `await run_quality_check(...)` from inside the workflow:

```python theme={null}
@runbook.defn
class NavMonthlyJournalsWorkflow(NtroWorkflow):

    @runbook.step(name="check_proposal", title="Quality check", icon="ShieldCheck")
    async def _step_check(self, proposal: JournalProposal) -> CheckResult:
        return await runbook.execute_activity(
            run_quality_check,
            args=[
                {"payload": proposal, "check_slug": "journal-balance-v1"},
            ],
            start_to_close_timeout=timedelta(minutes=5),
        )
```

The Tenant UI renders the `CheckResult` next to the underlying `JournalProposal` — the accountant sees both the proposed journal and the model's "I checked the balance, debits = credits, looks balanced" alongside it.

## Designing a good check

Quality checks work best when they're focused. A check that's "review this entire NAV" is hard to interpret. A check that's "verify the journal balances and that all GL codes exist in the COA" is actionable.

Common shapes:

| Check type            | Example slug          | What it does                                                                                  |
| --------------------- | --------------------- | --------------------------------------------------------------------------------------------- |
| Constraint check      | `journal-balance-v1`  | Asserts a structural property (debits = credits, all dates in period, etc.)                   |
| Cross-reference check | `gl-map-coverage-v1`  | Confirms every account in the proposal exists in the COA                                      |
| Sanity check          | `nav-plausibility-v1` | Compares the result to historical norms, flags outliers                                       |
| Tail check            | `audit-anomaly-v1`    | Looks for the unusual — duplicate references, suspicious round numbers, weekend-dated entries |

Each slug routes to a check definition in your provider configuration. The same model and prompt setup used by [`ai.extract`](/sdk/capabilities/private-ai) backs `run_quality_check`.

## Severity drives routing

The `severity` field affects how the Tenant UI surfaces the result:

* **`info`** — green check, accountant sees "all clear" and approves quickly
* **`warn`** — yellow flag, accountant sees the finding inline with the value
* **`error`** — red block, the workflow can `wait_for_action` with `must_resolve=True` to force a correction before approval

Pair `severity == "error"` with `wait_for_action(must_resolve=True)` to guarantee the human can't approve over the top of a critical issue.

## Related

<CardGroup cols={2}>
  <Card title="Workflows overview" icon="diagram-project" href="/workflows/runbooks/overview">
    Where you wire the check into the run via `@runbook.step`.
  </Card>

  <Card title="Private AI" icon="brain" href="/sdk/capabilities/private-ai">
    The same provider plumbing backs both extraction and checks.
  </Card>
</CardGroup>
