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

# Accounting

> ntro.accounting — domain models (ChartOfAccounts, JournalLine, JournalProposal), proposal helpers, and validation. (Trial balance has moved — see ntro.capabilities.gl.reports.)

`ntro.accounting` is the domain layer — typed models for the things fund accountants care about (chart of accounts, journal lines, proposals) plus validation helpers and one reference proposal algorithm. It's deliberately thin: most fund-ops calculation logic is tenant-specific and lives in the runbook itself.

> **Heads up:** the trial-balance type **moved** to [`ntro.capabilities.gl.reports.TrialBalanceReport`](/sdk/capabilities/general-ledgers#reports--typed-read-only-views-of-gl-state) in N-70. It's a typed read-only view of external GL state — fits the GL capability, not the legacy accounting kitchen-sink. The old import (`from ntro.accounting.models import TrialBalance`) still works for one release as a deprecated alias.

Three submodules ship today:

| Submodule                    | Purpose                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------ |
| `ntro.accounting.models`     | Pydantic types — `ChartOfAccounts`, `JournalLine`, `JournalProposal`           |
| `ntro.accounting.proposal`   | Reference algorithms — `propose_allocation_journal()`                          |
| `ntro.accounting.validation` | Pure validation — `validate_journal_balance()`, `validate_required_accounts()` |

## Install

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

The accounting module is bundled with the workflow extra.

## Models — `ntro.accounting.models`

Strict Pydantic models for the fund-ops domain. They give your runbook type safety, JSON-serialisable shapes, and a `model_validate` round-trip you can rely on at activity boundaries.

```python theme={null}
from ntro.accounting.models import (
    ChartOfAccounts,
    JournalLine,
    JournalProposal,
)
from ntro.capabilities.gl.reports import TrialBalanceReport  # was ntro.accounting.models.TrialBalance pre-N-70
```

### `TrialBalanceReport` (moved to `ntro.capabilities.gl.reports`)

A balanced TB at a point in time. Lifted from `nav-monthly-journals`:

```python theme={null}
from ntro.capabilities.gl.reports import TrialBalanceReport

# Pattern B fast path — hydrate from the cached extraction
# document-ingest already wrote at upload time:
tb = TrialBalanceReport.from_extracted_payload(cached_payload, period=ctx.period)

# Or live path (only when re-extracting from raw bytes — ~30s):
# tb = TrialBalanceReport.from_extraction(result, period=ctx.period)

# Built-in invariants — raise if the TB doesn't balance or is the wrong period
tb.assert_balanced()
tb.assert_period_match(ctx.period)
```

See **[General ledgers → Reports](/sdk/capabilities/general-ledgers#reports--typed-read-only-views-of-gl-state)** for the full Pattern B explanation.

### `ChartOfAccounts`

The list of accounts the entity uses, with their hierarchy and types:

```python theme={null}
from ntro.accounting.models import ChartOfAccounts

coa = ChartOfAccounts.load(entity_slug=ctx.entity_slug)  # populated upstream
income_accounts = coa.filter(account_type="income")
```

### `JournalLine` and `JournalProposal`

A `JournalProposal` is a list of `JournalLine` objects assembled into a single journal entry — the artifact of `nav-monthly-journals` that an accountant approves before it posts back to Xero / SAP / whatever the authoritative GL is.

```python theme={null}
from ntro.accounting.models import JournalLine, JournalProposal

# Constructing a line manually (rare — usually proposal.py builds these)
line = JournalLine(
    account_code="200",
    debit=Decimal("1500.00"),
    credit=Decimal("0.00"),
    narrative="March rent — 12 High Street",
    period="2026-03",
)
```

## Proposal — `ntro.accounting.proposal`

One reference algorithm ships today: `propose_allocation_journal`. It takes a parsed TB plus the ingested period documents and produces a balanced journal that allocates the activity to the right GL codes.

Lifted from `nav-monthly-journals`:

```python theme={null}
from ntro.accounting.proposal import propose_allocation_journal


@activity.defn(name="nav_monthly_journals.propose_journal")
async def propose_journal(input: ProposalInput) -> JournalProposal:
    """Apply the pure proposal algorithm and validate balance."""
    proposal = propose_allocation_journal(
        tb=input.tb,
        ingested=input.ingested,
        gl_map=input.gl_map,
        period=input.period,
        entity_slug=input.entity_slug,
        mortgage_agreement=input.mortgage_agreement,
    )

    validate_journal_balance(proposal.lines).raise_if_failed()
    return proposal
```

The algorithm is pure (no I/O) so it can be unit-tested without the harness. Wrapping it in a Temporal activity is what gives the step durability, retries, and a `LOADING` UI surface — but the maths itself is just a function call.

<Note>
  `propose_allocation_journal` is the reference implementation for real-estate SPV monthly NAV. The proposal logic for other domains (private credit, infrastructure, capital calls) is tenant-specific and lives in the runbook itself, not the SDK.
</Note>

## Validation — `ntro.accounting.validation`

Pure validation helpers that return a `ValidationResult` with `passed: bool` and `findings: list[str]`. You either short-circuit on failure (`raise_if_failed()`) or surface the findings to a human.

```python theme={null}
from ntro.accounting.validation import (
    validate_journal_balance,
    validate_required_accounts,
)

# Balance check — debits == credits per period
result = validate_journal_balance(proposal.lines)
result.raise_if_failed()

# COA coverage — every required account exists
result = validate_required_accounts(coa, required=["200", "1100"])
if not result.passed:
    for finding in result.findings:
        log.warn(finding)
```

Lifted from `nav-monthly`:

```python theme={null}
from ntro.accounting.validation import validate_required_accounts

@activity.defn(name="nav_monthly.open_period")
async def open_period(ctx: NavMonthlyContext) -> PeriodOpened:
    coa = await load_coa(ctx.entity_slug)
    validate_required_accounts(
        coa, required=ctx.coa_required_accounts
    ).raise_if_failed()
    ...
```

## What's not in the SDK

The Notion structure plan calls out a roadmap of accounting calculations — NAV calculation, fee / waterfall / equalisation, FX translation, valuation adjustments. **None of those are in `ntro.accounting` today.** If your runbook needs them, you write them in the runbook itself (or in your own private accounting library that the runbook imports).

The reason is twofold: (1) most of these calculations are tenant-specific in their detail, and (2) they're commercially valuable enough that we'd rather not ship reference implementations that might lock customers into ours. The SDK gives you the primitives (typed TB, balanced journal validation, COA shape) and stays out of the calculations.

## Related

<CardGroup cols={2}>
  <Card title="General ledgers" icon="landmark" href="/sdk/capabilities/general-ledgers">
    Post proposals to the customer GL via `ntro.capabilities.gl`.
  </Card>

  <Card title="Private AI" icon="brain" href="/sdk/capabilities/private-ai">
    `ai.extract()` is what produces the `ExtractionResult` `TrialBalanceReport.from_extraction()` consumes.
  </Card>

  <Card title="Quality checks" icon="circle-check" href="/sdk/capabilities/quality-checks">
    Pair `validate_journal_balance` with a quality check for stronger HITL routing.
  </Card>
</CardGroup>
