Skip to main content

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.

ntro.accounting is the domain layer — typed models for the things fund accountants care about (trial balance, 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. Three submodules ship today:
SubmodulePurpose
ntro.accounting.modelsPydantic types — TrialBalance, ChartOfAccounts, JournalLine, JournalProposal
ntro.accounting.proposalReference algorithms — propose_allocation_journal()
ntro.accounting.validationPure validation — validate_journal_balance(), validate_required_accounts()

Install

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.
from ntro.accounting.models import (
    ChartOfAccounts,
    JournalLine,
    JournalProposal,
    TrialBalance,
)

TrialBalance

A balanced TB at a point in time. Lifted from nav-monthly-journals:
from ntro.accounting.models import TrialBalance

# Most runbooks build a TrialBalance from an AI extraction result
tb = TrialBalance.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)
TrialBalance carries a list of TBLine rows (one per account) plus the period and any metadata. from_extraction() builds it from the structured ExtractionResult returned by ai.extract() — saves you a manual mapping pass.

ChartOfAccounts

The list of accounts the entity uses, with their hierarchy and types:
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.
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:
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.
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.

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

Private AI

ai.extract() is what produces the ExtractionResult TrialBalance.from_extraction() consumes.

Quality checks

Pair validate_journal_balance with a quality check for stronger HITL routing.