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

# Ledger understanding

> Measure the ingested journal package and read the latest report

`POST /v1/workspaces/{workspace_id}/ledger-understanding` starts a job whose
result is a `ledger_understanding`. `GET` on the same path returns the
latest report.

Python: `client.workspaces.start_ledger_understanding` /
`client.workspaces.get_ledger_understanding`. TypeScript:
`client.workspaces.startLedgerUnderstanding` /
`client.workspaces.getLedgerUnderstanding`. MCP:
`start_ledger_understanding` / `get_ledger_understanding`. CLI:
`ndi ledger-understanding start|get`.

A sibling of [journal-entry testing](/api-reference/je-testing), not a
search. JET *tests* a criterion over the package; this *reads* the package
so later tests start from a settled census. The report names table roles,
integrity findings, open questions, and optional GL-parse counts. It does
not carry the SQL behind those figures — re-run a figure with
[run-sql](/guides/query-tables).

Without `force`, a request over an unchanged package republishes the
stored report instead of scanning again.

## Package scope

Start the first run without source selection. An empty request discovers all sources
the caller can access; optional path selectors bound discovery. Catalog recognition
does not block this first attempt, and saved workspace scope settings are not implicit
request selectors.

If the persisted report has `assessment.status: needs_source_selection`, review
its measured candidates. Request `GET /v1/workspaces/{workspace_id}/ledger-understanding/sources`
with `review_reading_id` and the report's scope query parameters for a reading-bound
catalog. Only `review_eligible` primaries have accepted complete-population key evidence.
Submit `review: {reading_id, scope}` with the selection body below. A stale reading,
an unmeasured source or a primary without key evidence is refused. A `native_contract`
primary states its own journal structure: select it without `review`, and neither an
identity choice nor a structural nomination applies. Confirmation starts
a new understanding run using the returned selection; it never overrides technical checks.

With the LLM discovery engine, generic source nominations can use an ordered composite
account identity, such as `["company", "account_code"]`. The source editor preserves these
components and lets reviewers add or remove columns. A structural selection accepts
`structure_proposal.account` as either one column name or an ordered list. Shared journal/account
qualifiers are allowed; account components must be distinct, scalar columns available on every
primary member. Confirmation records intent; the following measured run still validates the
package. Parsing preserves the whole account tuple, including its order. Scalar reference
accounts alone cannot establish CoA or TB matches for a composite identity.

For an existing explicit-selection integration, list `GET /v1/workspaces/{workspace_id}/ledger-understanding/sources`,
then post `primary_source_id`, explicit `supporting_source_ids`, and the returned revision as
`expected_catalog_revision` to `POST /v1/workspaces/{workspace_id}/ledger-understanding/selections`.
Use the returned `selection_id` when starting understanding and reading its report or when
starting either JET engine. Do not combine it with path selectors.

`recommended_as_primary` is a structural recommendation, not proof of ownership or fitness.
`primary_eligible` retains the same recommendation for older clients. An unrecognized primary
requires `confirm_unrecognized_primary: true`; the saved selection reports
`primary_recommendation_overridden: true`. The run measures that exact primary and publishes
missing key, amount or grain evidence for review. Confirmation does not waive technical checks.
Recommended alternate posting populations cannot be support, and changing or revoking selected
files still prevents a new run. The selection's members and evidence remain immutable.

Send `path_prefix` (a workspace-relative folder), `paths` (exact source files),
or both in the start body. Both selectors intersect; folder boundaries are
respected. Include supporting headers, accounts and calendars in the package.
Selecting only some parts of a native partitioned dataset is refused.

Pass the same selectors as query parameters to read the report, reviews,
decisions and derived tables; repeat `paths` for each file. Report fallback and
human decisions never cross package scopes. Empty selectors use the workspace's
`ledger_understanding.package_scope`; its empty default means the whole workspace.
An unscoped census with multiple journal populations asks for package selection
instead of choosing one. Changing the default scope does not migrate old answers.

## Policy

`create` and `update` accept a `ledger_understanding` block
(`trigger`, `gl_parsing`, `package_confirmation`, `package_scope`, `prepared_relationships`).

`package_confirmation` applies to the `llm` engine. `required` (default) stops discovery at
source review and parses only a mapping a person confirmed. A run that settles other columns
than the confirmed ones, or runs over a model-confirmed selection, skips parsing and reports
`parsing.confirmation_required`. To parse, post a structure review of that reading with
`previous_selection_id` and the report's `settled_structure` (edited as needed). The correction
keeps the previous selection's other human decisions. `automatic` lets the model confirm the
package and chain its measured run.
Updating replaces the block whole. Automatic ingestion runs use `package_scope`;
the console's Properties tab can configure its folder.

`trigger` is `auto` (default), `always`, or `off`. `auto` chains a run
after ingestion only when the workspace already holds a journal-line
table — a document corpus costs nothing. `always` chains after every
ingestion that landed files. `off` stops chaining; it never refuses an
explicit `POST`.

When ingestion does chain a run, `IngestionResult.ledger_understanding_job_id`
names it.

## The report

`GET` returns the report itself. A succeeded job's `result.report` is the
same object.

| Field            | What it is                                                        |
| ---------------- | ----------------------------------------------------------------- |
| `stale`          | The package has moved since this reading                          |
| `scope`          | Canonical folder and exact source selection                       |
| `blocking`       | Integrity found something that makes the population unfit to test |
| `tables`         | One card per table: role, row count, journal key, balance share   |
| `conventions`    | What the numbers mean — journal key, amount bases, date roles     |
| `integrity`      | Findings and skipped checks                                       |
| `join_graph`     | Expected joins, measured in both directions                       |
| `open_questions` | Things the data could not settle, asked once each                 |
| `parsing`        | GL-parse counts, never rows                                       |

`404 ledger_understanding_not_found` means the stage has never produced a
report for the requested scope. `409 ledger_understanding_in_progress` means a run is already in
flight — wait for that job rather than starting another.

`parsing.attempted_candidates` counts candidates actually evaluated.
`candidate_coverage` and `population_capped_share` are fractions of eligible
candidates (null when there are none). `cap_reason_counts` counts distinct
journals per stop reason, including `population_cap`; reasons may overlap.
Persisted sub-journal rows carry `attempted` and `caps_hit`.
Successful orchestration does not imply complete parsing. Limits are unchanged.

`parsing.cap_profiles` contains journal counts and median, p95 and maximum group
and posting counts for each stop reason. These aggregate diagnostics contain no
business values. Readiness reasons distinguish unattempted candidates from
attempted journals retained whole; both limitations can appear together.

For composite references, `join_graph[].left_columns` and `right_columns` state
the ordered key tuples, and `left_tables` lists the measured population members.
Missing-key and duplicate-reference counts are reported separately from membership
coverage. Complete membership does not make a duplicate reference safe to join.
`conventions.auxiliary_columns` retains non-exclusive SAP field references without
assigning generic users a preparer or approver role.

See [Workspaces](/guides/workspace) and [Jobs](/api-reference/jobs).
