> ## 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.

# expenses

> ntro.subledger.types.expenses.ExpenseRow — single expense receipt (vendor, amount, category, VAT). Used by the expense-processor runbook and consumed downstream by nav-monthly's operating-expenses block.

`ExpenseRow` captures one expense receipt with provenance back to the originating `ingest.submitted_records` row. Stored in `ledgers.expenses`. Migration: [`004_expenses.sql`](https://github.com/ntropii-com).

```python theme={null}
from ntro.subledger.types.expenses import ExpenseRow
```

The class is auto-registered as the `"expenses"` subledger type on import.

## Type-specific fields

| Field             | Type                            | Purpose                                                                                                                                                      |
| ----------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event_id`        | `UUID`                          | Provenance — points to the originating `ingest.submitted_records` row. Joined during HITL review to surface the original receipt's raw payload.              |
| `vendor`          | `str \| None`                   | Supplier name. Required outside `NEEDS_ATTENTION`.                                                                                                           |
| `amount_gross`    | `Decimal \| None` (`gt=0`)      | Receipt total. Receipts are positive expenses — a negative is either an extraction bug or a credit note (separate type). Required outside `NEEDS_ATTENTION`. |
| `currency`        | `CurrencyCode \| None`          | ISO-4217. Required outside `NEEDS_ATTENTION`.                                                                                                                |
| `expense_date`    | `date \| None`                  | Date on the receipt.                                                                                                                                         |
| `payment_method`  | `str \| None`                   | `"card"`, `"cash"`, `"bank_transfer"`, etc. Free-form.                                                                                                       |
| `line_items`      | `list[dict] \| None`            | Optional sub-lines for multi-line receipts.                                                                                                                  |
| `vat_amount`      | `Decimal \| None` (`ge=0`)      | VAT portion. Zero allowed (zero-rated items); negative isn't a normal category.                                                                              |
| `notes`           | `str \| None`                   | Free-form.                                                                                                                                                   |
| `category`        | `str \| None`                   | GL-code-shaped classification. Auto-proposed, HITL-correctable.                                                                                              |
| `category_source` | `str \| None`                   | `"vendor_lookup" \| "llm" \| "manual"`.                                                                                                                      |
| `confidence`      | `Decimal \| None` (`0 ≤ x ≤ 1`) | Classifier confidence; drives HITL routing.                                                                                                                  |

## GL-handoff fields

Populated by the post step, never written by runbook code directly.

| Field                | Type               | Purpose                                                       |
| -------------------- | ------------------ | ------------------------------------------------------------- |
| `approved_at`        | `datetime \| None` | Set by the runbook's HITL approval step.                      |
| `posted_to_gl`       | `bool`             | True once the GL post succeeded.                              |
| `posted_journal_ref` | `str \| None`      | Provider-assigned journal id from the successful create call. |

## Loose-on-`NEEDS_ATTENTION` contract

`vendor`, `amount_gross`, `currency` are typed `Optional` so the `insert_needs_attention` path can build a "ghost row" with NULLs in the typed columns. Once the row leaves `NEEDS_ATTENTION` (i.e. HITL fixed the issue), these MUST be populated.

Enforced two ways:

1. `_strict_fields_present_when_not_needs_attention` — Pydantic `model_validator` raises at row construction.
2. DB CHECK constraint mirrors the same rule (defense in depth).

A second cross-field invariant — `vat_amount <= amount_gross` — catches the common extraction bug where the agent confused gross with net somewhere on the receipt. Skipped on `NEEDS_ATTENTION` rows.

## GL handoff — `ExpenseRow.propose_for_gl`

```python theme={null}
from uuid import UUID
from ntro.subledger.types.expenses import ExpenseRow

bills = ExpenseRow.propose_for_gl(rows=approved_rows, task_id=UUID("…"))
```

Converts `APPROVED` expense rows to a list of `BillProposal`s — one bill per row (no merging — each receipt is its own bill in the GL). Defaults to the unified `Bill` primitive (AP / supplier invoice); Xero's deprecated Expense Claims module isn't used.

Field mapping:

| Row field      | → | Bill field                                         |
| -------------- | - | -------------------------------------------------- |
| `vendor`       | → | `supplier` (LinkedSupplierInput by `display_name`) |
| `amount_gross` | → | `total` + a single `BillLineItem.total_amount`     |
| `currency`     | → | `currency`                                         |
| `expense_date` | → | `bill_date`                                        |
| `vat_amount`   | → | `total_tax`                                        |
| `category`     | → | `BillLineItem.ledger_account.nominal_code`         |
| `notes`        | → | `notes`                                            |

Idempotency key follows the canonical convention: `"expenses:{task_id}:{row.id}"`.

Rejects non-`APPROVED` rows — only signed-off bills go to the GL.

## Mutation helpers — `ntro.subledger.types.expenses_mutations`

Pure-domain helpers for HITL row actions. No DB access — transports reuse them consistently.

```python theme={null}
from ntro.subledger.types.expenses_mutations import (
    can_reject,
    coerce_edit_value,
    EditableExpenseField,
)
```

`EditableExpenseField` enumerates which columns the UI can edit per cell: `vendor`, `currency`, `expense_date`, `payment_method`, `notes`, `category`, `category_source`, `amount_gross`, `vat_amount`. Anything else returns `INVALID_FIELD`.

`can_reject` only allows reject from `NEEDS_ATTENTION` (or no-op from `REJECTED`); other statuses return `INVALID_TRANSITION`.

## Canonicalisation — `canonicalize_tabular_expense_row`

Maps loose extraction keys onto the strict `ExpenseRow` field names — handles the common alias variants (`amount`, `total`, `gross_total` → `amount_gross`; `tax`, `vat` → `vat_amount`; etc.). Used by tabular ingest (`expense-processor` parsing a CSV) so runbook authors don't re-implement column-name normalisation.

```python theme={null}
from ntro.subledger.types.expenses import canonicalize_tabular_expense_row

normalised = canonicalize_tabular_expense_row(raw_row)
expense = ExpenseRow(**normalised, entity_id=..., period=..., task_id=...)
```

## Related

<CardGroup cols={2}>
  <Card title="Subledgers overview" icon="table" href="/sdk/subledgers">
    `Row` base + `SubledgerStatus` lifecycle.
  </Card>

  <Card title="journal_proposals" icon="file-spreadsheet" href="/sdk/subledgers/types/journal-proposals">
    The other shipped platform type.
  </Card>

  <Card title="General ledgers" icon="landmark" href="/sdk/capabilities/general-ledgers">
    Posting `BillProposal` to the external GL.
  </Card>

  <Card title="Typing" icon="types" href="/sdk/typing">
    `CurrencyCode`, `ForgivingDate`, `Period` — used in `ExpenseRow` field types.
  </Card>
</CardGroup>
