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

> client.workspaces.startLedgerUnderstanding — measure the ingested journal package

`client.workspaces.startLedgerUnderstanding` measures the workspace's
ingested journal package — table roles, integrity, open questions, and
optional GL parsing — and returns a `Job` whose result is a
`LedgerUnderstandingResult`. `client.workspaces.getLedgerUnderstanding`
reads the latest report without starting a run.

A sibling of journal-entry testing, not a search. JET *tests* a criterion
over the package; this *reads* the package so later tests start from a
settled census. Without `force`, an unchanged package republishes the
stored report instead of scanning again.

```ts theme={"dark"}
import { NdiClient, type models } from "ndi-sdk";

const client = new NdiClient({ api_key: process.env.NDI_API_KEY! });
const workspaceId = "550e8400-e29b-41d4-a716-446655440001";

await client.workspaces.update(workspaceId, {
  ledger_understanding: { trigger: "auto", gl_parsing: true },
});

let job = await client.workspaces.startLedgerUnderstanding(workspaceId);
job = await client.jobs.wait(job.job_id, { timeout: 3600 });

const result = job.result as models.LedgerUnderstandingResult;
console.log(result.report.stale, result.report.blocking, result.report.parsing);

const stored = await client.workspaces.getLedgerUnderstanding(workspaceId);
console.log(stored.package_fingerprint, stored.tables.length);
```

`LedgerUnderstandingResult` is reachable as `models.LedgerUnderstandingResult`,
the same way `models.JetResult` is.

With the `llm` engine, `package_confirmation: "required"` (the default) stops discovery
at source review: a person confirms the package and its columns before GL parsing runs.
If the run later settles different columns, or the selection was confirmed by a model,
parsing is skipped and `report.parsing.confirmation_required` lists the changes;
confirm `report.settled_structure` as a structure review to parse.

With `package_confirmation: "automatic"`, discovery can confirm a package and start its
measured run automatically. When the result has `follow_up_job_id`, wait for that job before
reading the final analysis. `report.proposals.auto_confirmation` explains the
confirmation or why review is needed. `report.auto_decisions` records model choices,
suggestions, and validation refusals; measured evidence still controls parsing.

***

## Method signature

```text theme={"dark"}
startLedgerUnderstanding(
  workspace_id: string,
  opts: {
    force?: boolean;
    path_prefix?: string | null;
    paths?: string[];
    selection_id?: string | null;
    wait_seconds?: number;
    idempotency_key?: string | null;
  },
): Promise<Job>
```

### Parameters

| Parameter              | Type       | Required | Description                                                        |
| ---------------------- | ---------- | -------- | ------------------------------------------------------------------ |
| `workspace_id`         | `string`   | Yes      | Workspace that already holds an ingested ledger package            |
| `opts.force`           | `boolean`  | No       | Re-measure even when a report for this package already exists      |
| `opts.path_prefix`     | `string`   | No       | Package folder, relative to the workspace                          |
| `opts.paths`           | `string[]` | No       | Exact source paths, intersected with the folder                    |
| `opts.selection_id`    | `string`   | No       | Confirmed source selection; cannot be combined with path selectors |
| `opts.wait_seconds`    | `number`   | No       | Hold the submission request open (max 300)                         |
| `opts.idempotency_key` | `string`   | No       | Override the key the SDK normally mints                            |

### Returns

A `Job` whose `result` is a `LedgerUnderstandingResult` (`result_type` is
`ledger_understanding`). The report also lives on `result.report`.

***

## Read the latest report

```text theme={"dark"}
getLedgerUnderstanding(
  workspace_id: string,
  scope?: LedgerPackageScope,
): Promise<LedgerUnderstandingReport>
```

`GET` returns the report itself, not a job. `stale` is true when the
package has moved since this reading. A workspace that has never produced
a report raises `404 ledger_understanding_not_found`. For a confirmed
source selection, pass the same `selection_id` to start and read.

***

## Confirm sources

```text theme={"dark"}
listLedgerSources(
  workspace_id: string,
  opts?: LedgerPackageScope & { review_reading_id?: string | null },
): Promise<SourceCatalog>
```

```text theme={"dark"}
selectLedgerSources(
  workspace_id: string,
  opts: {
    primary_source_id: string;
    expected_catalog_revision: string;
    supporting_source_ids?: string[];
    review?: SourceReviewReference | null;
    previous_selection_id?: string | null;
    confirm_unrecognized_primary?: boolean;
  },
): Promise<SelectionSummary>
```

```text theme={"dark"}
getLedgerSelection(
  workspace_id: string,
  selection_id: string,
): Promise<SelectionDetails>
```

`listLedgerSources` is the review catalog. Pass `review_reading_id` from a
reading that asked for source selection. `selectLedgerSources` freezes a
primary and supporting sources against that catalog revision. Use the
returned `selection_id` on start and get.

***

## Workspace policy

`create` and `update` accept a `ledger_understanding` block. Updating
replaces the block whole.

`package_scope: { path_prefix: "engagement" }` sets the default for automatic
runs, reads and reviews. Empty request selectors use this default. Read a
per-request package with the same selectors used to start it.
See [package scope and parse coverage](/api-reference/ledger-understanding).

| Field                  | Default      | Meaning                                                                                                                                                                                                                                |
| ---------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trigger`              | `"auto"`     | `auto` chains a run after ingestion only when the workspace already holds a journal-line table. `always` chains after every ingestion that landed files. `off` stops chaining; it never refuses an explicit `startLedgerUnderstanding` |
| `gl_parsing`           | `true`       | Map each journal's account / debit-credit groups to balanced sub-journals                                                                                                                                                              |
| `package_confirmation` | `"required"` | With the `llm` engine, `required` parses only a mapping a person confirmed; `automatic` lets the model confirm the package                                                                                                             |

A document-only corpus with `trigger: "auto"` never starts a run: the
predicate is over sidecars ingestion already wrote.

When ingestion does chain a run, `IngestionResult.ledger_understanding_job_id`
names it. Poll that id with `jobs.wait`.

***

## Reading the report

| Field            | What it is                                                        |
| ---------------- | ----------------------------------------------------------------- |
| `stale`          | The package has moved since this reading                          |
| `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. Empty findings is an answer          |
| `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. Null when parsing was off or skipped |

```ts theme={"dark"}
const report = await client.workspaces.getLedgerUnderstanding(workspace_id);
if (report.stale) {
  await client.workspaces.startLedgerUnderstanding(workspace_id, { force: true });
}
if (report.blocking) {
  for (const finding of report.integrity.findings) {
    console.log(finding.check, finding.severity, finding.count, finding.message);
  }
}
for (const question of report.open_questions) {
  console.log(question.id, question.question);
}
```

## Conflicts

A second `startLedgerUnderstanding` while a run is already in flight
returns `409 ledger_understanding_in_progress`. Wait for the in-flight
job, or read the current report with `getLedgerUnderstanding`.

See [Workspaces](/sdks/typescript/workspaces) and the
[ledger-understanding API reference](/api-reference/ledger-understanding).
