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

# When other judgments read this one

> What a roll-up can show of another judgment's answers, which judgments it can read, and what a backfill, a new version, a threshold change, a pin or a new fit costs the judgments that read it, before you confirm.

Change a judgment that other judgments read, and see first what the change costs them. Before a backfill, a new version, a threshold change, a pin or a promote, Brussle returns each reader's re-judges and their price. It refuses a change that would break a reader, and keeps readers current by itself when a new [fit](/train/promotion) changes the numbers they read.

A judgment whose answers a relation reads is a **child**. The judgments that read it are its **readers**. The examples continue the [roll-up guide](/guides/roll-ups): `contract_terms` reads the `one_sided` answers of each contract's clauses. Relations, chains and bands are on [relations](/concepts/relations).

## What a reader can show of an answer

A relation reads the current answer of a judgment at `answers.<judgment>.<field>`, the same paths a [query](/concepts/answers) filters on:

| Path | Judgment type |
| - | - |
| `.p` | `bool` |
| `.value` | `choice` |
| `.score` | `score` |
| `.dist.<option>` | `choice` and `score` |
| `.thresholds.<name>` | all types |

A path that the judgment's type does not answer is `invalid_request`.

A probability moves a little each time Brussle judges a clause again. If the contract showed the raw number, Brussle would judge the contract again each time. What a relation shows of an answer therefore changes only when the answer crosses a line that you chose (see [bands](/concepts/relations#bands-keep-answers-stable)):

| Where | What it can read |
| - | - |
| `fields` | `.value` and `.thresholds.<name>` as they are; `.p`, `.score` and `.dist.<option>` only with `bands` |
| `aggregate.count_where` | documents whose answer agrees: `.value` or `.thresholds.<name>` by value or by `{"not": <value>}`, or `.p` or `.score` by a cut such as `{"gte": 0.8}` (one of `gte`, `gt`, `lte` and `lt`) |
| `aggregate.min`, `aggregate.max` | `.p`, `.score` or `.dist.<option>` with `bands`: the band of the lowest or highest one |
| `aggregate.latest` | `.value` and `.thresholds.<name>` of the newest document that has one |
| `aggregate.sum`, `aggregate.avg` | `.p`, `.score` or `.dist.<option>`, as a [feature](/concepts/relations#the-aggregates-a-feature-can-name) only, never shown to the engine |
| `match` | `.value` or `.thresholds.<name>` by value, or a cut on `.p` or `.score`, beside attributes |

A sum changes on every evaluation, and even a sum in bands can cross a band when no single clause's answer did, so the engine never reads one. Brussle refuses a `sum` or `avg` of a path that is not a number, such as `.value` or `.thresholds.<name>`.

A [named threshold](/train/thresholds) means what you decided from your outcomes. Use an inline cut when you have not set a threshold. It is equally stable.

Over many documents, a `max` in bands almost always reads the top band and tells the judgment nothing. Use `max` and `min` only on a short relation, such as the last ten documents, and `count_where` for many.

**Select by answer.** A `match` on an answer, such as only the flagged clauses, keeps the newest `last_n` documents whose answer agrees. A clause enters or leaves the selection only when it crosses the cut. Brussle checks the newest 1,000 documents that agree with the attributes of `match`, then keeps the newest `last_n` whose answer agrees. If more than 1,000 documents agree with the attributes and fewer than `last_n` of the newest 1,000 have an answer that agrees, the relation shows `capped: true`: an older document may agree too. You cannot order by an answer.

## What a judgment can read

The judgment whose answers a relation reads must meet the rules on [relations](/concepts/relations#at-most-three-levels-never-a-loop): active and `on_change`, in the same namespace or template, answering each document the relation selects (its `applies_to` covers the relation's `match`), not a composite with parts, and within three levels. A [matcher](#count-the-matches-of-a-matcher) can be read only through a [link](/guides/entity-matching#read-the-matches-from-the-invoice) or a `count_where` on `none_of_the_above`, because its `value` is only a document id.

A relation reads the answers or outcomes of 4 judgments at most. Answer paths in `count_where`, `sum`, `avg`, `min`, `max` and `latest` count toward the relation's 8 aggregate paths; answer paths in `fields` and `match` do not.

Brussle refuses a create that breaks a rule with `invalid_request`, and `details.path` names the path. When a chain would be four deep or loop, `details.chain` names it, such as `["region_terms", "portfolio_terms", "counterparty_terms", "contract_terms", "one_sided"]`.

A judgment that reads a [referenced document](/guides/referenced-document) or a [block](/guides/entity-matching) can be read only when its `freshness.fanout.scope` is `created_within: null`, with no `where`. Otherwise a document outside its scope keeps an old answer that nothing judges again, and its readers read that old answer for all time. Limit such a judgment with `applies_to`, not with the scope.

A relation that reads its [referenced document](/guides/referenced-document) can read that document's answers in the same way. A change to what it shows is a referenced change, and [fans out](/guides/fan-out) to the documents that point at it under the same limits.

A judgment's `GET` lists its readers, and Brussle refuses a change that would break one with `conflict`, naming them in `details.readers`. These changes are refused:

* delete the judgment, deactivate it, or change its policy from `on_change`
* give it a fan-out scope while it reads a referenced document or a block
* remove a threshold that a reader names
* activate a version that:
  * is a composite with parts, or chooses among candidates, unless the reader only counts whether the value is `none_of_the_above`
  * does not answer a field that a reader reads, or whose `applies_to` does not cover a reader's `match`
  * makes a reader, at any level, read more than three deep. `details.chain` is then the chain from that reader. A [group](/guides/groups) that counts the judgment is a reader for this rule only, named as `group <name>`.
  * for a judgment that links read, does not choose among candidates, or chooses among candidates that share a different key attribute

A judgment whose [outcomes](/concepts/relations#what-happened-to-related-documents) another judgment reads is kept readable in the same way: a change to its answer type, or its delete, is refused with `conflict`. A read of outcomes does not add a level to the chain.

On a [template](/guides/templates), Brussle runs the same checks in each namespace that inherits the judgment, and names it in `details.namespace`. An override of an inherited judgment's settings is checked in the namespace that has it.

## Count the matches of a matcher

A [matcher](/guides/entity-matching) is a judgment that chooses among candidates (`options.from`). Its answer is a document `id`, or `none_of_the_above`. To know how many related documents matched any candidate, count the answers that are not `none_of_the_above`. You need no link, and no judgment on each matched document.

For example, `settles` matches each payment to an invoice. This judgment on accounts counts the account's payments that settled any invoice:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "name": "account_reconciled",
  "type": "bool",
  "question": "Are this account's payments reconciled?",
  "applies_to": {"attributes.kind": "account"},
  "context": {"fields": ["state.name"], "related": {"payments": {
    "match": {"attributes.kind": "payment", "attributes.status": "unmatched"},
    "join": {"theirs": "attributes.account_id", "mine": "id"},
    "last_n": 50,
    "aggregate": {
      "count": true,
      "count_where": {"answers.settles.value": {"not": "none_of_the_above"}},
      "render": false
    }
  }}},
  "features": ["payments.count", "payments.count_where"],
  "freshness": {"policy": "on_change"},
  "confirm": true
}
```

* `payments.count_where` is the number of the account's payments that settled any invoice. `{"answers.settles.value": "none_of_the_above"}` counts the payments that settled none.
* The count follows each payment's answer. A payment that changes from one invoice to another does not change the count, and Brussle does not judge the account again. A payment that changes to or from `none_of_the_above` changes the count, and Brussle judges the account again after its debounce.
* The count is one level, so the matcher can be at most two levels deep.
* Brussle refuses every other read of the value with `invalid_request`: a field, `latest`, a specific `id`, a list of values, a threshold of the matcher, and a `match` on the value. To read the matched documents, use a [link](/guides/entity-matching#read-the-matches-from-the-invoice).

A [group](/guides/groups) can count the same way, such as the payments that settled any invoice, by month.

## What it costs

A change to the child's answers can cost each reader more. These requests return that cost as `downstream`, one line for each reader, before you confirm: a backfill estimate of `one_sided` (the contracts judged again in the month it runs), the shadow report of a new version of `one_sided`, a [threshold change](#changing-a-threshold-a-reader-reads) that a reader names, and a [pin or a promote](#when-a-child-is-calibrated-differently) on a child with features.

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"downstream": [
  {"judgment": "contract_terms", "judgments_per_month": 18400, "cost_usd": 4.6, "level": 1, "basis": "history"},
  {"judgment": "counterparty_terms", "judgments_per_month": 2100, "cost_usd": 0.53, "level": 2, "via": "contract_terms", "basis": "history"}
]}
```

A level-1 line prices the reader's re-judges at your tiers. Where they come from depends on the request:

* **A threshold change, a pin or a promote** counts how often the clauses' band or cut would have changed in the last 30 days of answers under the new setting, plus the contracts that the change judges again now. It is exact, with one exception: the history does not record a clause that moved from one contract to another in that time.
* **A backfill** applies the history's rate of changes to the clauses that the backfill judges.
* **A shadow report** counts the changes in its sample and scales them to every document, with `basis` `replay`.

The lines continue down the chain, up to three levels, because a reader judged again can change what its own readers read:

* `level` 2 and 3 name the reader above them in `via`.
* Below the first level, a line is an estimate, not a bound. It applies the reader's own past rate of changes, from its history, to the re-judges above it.
* `basis` is `history`, or `replay` where there is none: a reader not judged in the last 30 days counts each of its re-judges as one re-judge of its own reader.
* The estimate reads a limited amount of answer history. If it stops before the end, the line has `lower_bound: true`, and `omitted` names the judgments whose history it did not read to the end.

If you already use `one_sided` for its own purpose, a read of it adds no cost on the clause side: its answers exist anyway, and the contract's re-judges are ones the clause writes would cause anyway. If you add a judgment only to give its answers to another judgment, you pay for each evaluation of the child too.

## Changing a threshold a reader reads

A threshold is a setting: a change applies to all answers at once, with no evaluation. When a reader's relation names the threshold, such as `answers.one_sided.thresholds.flag`, the change moves what the reader shows with no new answer to start a judging. So the `PATCH` returns the `downstream` estimate and changes nothing, including the rest of the request:

<CodeGroup>
  ```ts TypeScript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  const result = await ns.judgments.update("one_sided", { thresholds: { flag: 0.7 } });
  if ("downstream" in result && !("judgment" in result)) {
    // Nothing changed. Show each reader's cost, then confirm.
    console.log(result.downstream);
    await ns.judgments.update("one_sided", { thresholds: { flag: 0.7 }, confirm: true });
  }
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  result = ns.judgments.update("one_sided", thresholds={"flag": 0.7})
  if "downstream" in result and "judgment" not in result:
      # Nothing changed. Show each reader's cost, then confirm.
      print(result["downstream"])
      ns.judgments.update("one_sided", thresholds={"flag": 0.7}, confirm=True)
  ```
</CodeGroup>

With `confirm: true`, the thresholds apply to all reads at once, as always, and a `resync` job starts:

* The job reads the answers of `one_sided` once, and judges again each contract whose clauses now show a different result.
* Until the job reaches a contract, the contract's answer stays `fresh` under the old threshold.
* The response has `job_id` and the same `downstream` lines.

When you apply a [threshold recommendation](/train/thresholds), this response is often where you learn that another judgment reads the threshold.

On the prefix path of a [template](/guides/templates), the readers are those of each tenant that uses the template's thresholds: the template's own judgments that read the threshold, and each tenant's own judgment that reads the inherited one. A tenant that overrides the thresholds is left out, because its readers see no change.

The `downstream` lines add up the tenants, one line for each reader: Brussle counts up to 100 tenants and scales the result to all of them, as for a template's backfill estimate. One `resync` job, whose `namespace` is the prefix, reads the tenants one at a time.

The `when` of a [link](/guides/entity-matching#read-the-matches-from-the-invoice) also names a threshold. You confirm a change to it in the same way, and the `resync` job judges again each document whose links start or end.

A threshold that no reader names changes at once, as always. An inline cut is part of the reader's own definition, so a change to it is a new version of the reader, with its own backfill estimate.

The re-judges that a `resync` causes reach [subscriptions](/concepts/subscriptions#bulk-changes) as a bulk change, like those of a backfill. By default they send no events, and `subscription.synced` announces them.

## When a child is calibrated differently

A child with [features](/train/features) answers through its fit: readers read its corrected `p` on a `bool`, its `dist`, `escape_p` and `value` on a `choice`, and its `dist` and `score` on a `score`. A fit that changes a choice's pick therefore changes what the parent reads.

The fit a child's answers read changes only under the [rule for a new fit](/train/promotion#the-rule): its first fit, a new fit that is clearly better on the outcomes since, a withdrawal, and the other cases the rule lists. A night that keeps the current fit changes nothing and judges nothing again. You change it yourself when you [pin a fit](/train/promotion#pin-a-fit) or [promote](/train/promotion#promote-the-newest-fit) the newest one. After you [lift a pin](/train/promotion#lift-a-pin), the next run replaces the formerly pinned fit only with a clearly better one.

Each change moves what readers see of the child, with no write, so Brussle runs the same pass as for a threshold change:

* **Automatic for a new fit.** When a run changes the fit, a `resync` job (`cause: fit`) reads the child's answers once and judges again each reader's document whose child now shows a different result, with no confirm and no estimate first: readers that waited for a confirm would read numbers that the child no longer shows.
* **Billed as any re-judge,** under each reader's [rolling limit](/guides/fan-out#the-rolling-limit). Past it, the other documents read `stale` (`limit_reached`).
* **Then the bill.** When the pass is done, `judgment.fit_applied` or `judgment.fit_withdrawn` reports the documents that crossed each threshold and each reader's re-judges with their cost (see [events when the fit changes](/train/promotion#events-when-the-fit-changes)). It lists only the child's direct readers, the first level of [`downstream`](#what-it-costs). Their own readers are judged again as their answers change, each under its own rolling limit, and billed as they happen.
* **A pin and a promote are confirmed first,** as a [threshold change](#changing-a-threshold-a-reader-reads) is. `POST .../calibration/fit` with `{"promote": true}` on a child with readers returns the `downstream` estimate and queues nothing; with `confirm: true`, it queues the run that starts the pass. A `PATCH` that pins a fit of a child with readers returns the estimate and changes nothing; with `confirm: true`, it starts the same `resync` job and returns `job_id`, and the event has `pinned: true`. While the pin holds, a new fit of the child does not change what readers read for that version and epoch, so the nightly run starts no pass.
* **In the audit.** A reader's evaluation names in `related_documents` the fit under which it read the child, keyed by the child: `"fits": {"contract_terms": "fit_…"}`. With the child's engine `p`, its features and that fit, you can work out the number the reader saw, whatever the fit is now.

**On a [template](/guides/templates),** the pass runs in each tenant that inherits the child. When only one tenant's own fit changes ([each tenant grows its own correction](/train/tenants#each-tenant-grows-its-own-correction-team-and-scale)), it judges again, and bills, that tenant's readers only. When the pooled fit changes, it judges again the readers in each tenant that reads the pool.

A child with features can be at any level of a [chain](#three-levels).

When a reader must not change with the child's fit, read the child's features, or a feature threshold, instead of its `p`. A reader that shows the child's `p` as a bare number is judged again on any change of the child's fit, however small.

## Three levels

"Clause, contract, counterparty" is one chain: `one_sided`, then `contract_terms` reading the clauses' answers, then `counterparty_terms` reading the contracts'. You copy no key: the clause holds only its contract's id, and the contract its counterparty's. The rule, at most three levels and never a loop, is on [relations](/concepts/relations#at-most-three-levels-never-a-loop).

**Each level adds a debounce.** A clause's new answer judges its contract again after the contract's `debounce_ms`, then the counterparty after the counterparty's. `GET` on a judgment lists each chain it reads through as `upstream`, with the sum of the debounces: about how long a change at the bottom takes to reach the judgment, before its own debounce.

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"upstream": [{"path": ["contract_terms", "one_sided"], "debounce_ms": 660000}]}
```

**Each level is billed.** A change at the bottom that changes what the level above shows judges that level again, and so on up, each under its own rolling limit.

When you create a judgment in a chain without `confirm`, the estimate has `graph_ceiling` beside `replay` and `fanout`: the cost in 30 days if this judgment, and each judgment that reads it at any level, spent all of its rolling limit. That is the bound. The replay and the `downstream` lines below the first level are estimates. When one of the judgments has `rolling_limit: null`, there is no bound: `graph_ceiling` is `null`, and `graph_ceiling_unlimited` names that judgment.

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "replay": {"replayed_days": 30, "entities": 40000, "judgments_per_month": 9000, "judgment_units_per_month": 9000, "cost_usd_per_month": 2.25, "bulk_pool_share": 0.001, "lower_bound": false},
  "fanout": {"rolling_limit": 300000, "cost_usd_at_limit": 75.0},
  "graph_ceiling": {"cost_usd": 150.0, "judgments": [
    {"judgment": "contract_terms", "rolling_limit": 300000, "cost_usd": 75.0},
    {"judgment": "counterparty_terms", "rolling_limit": 300000, "cost_usd": 75.0}
  ]}
}
```

The evaluation of each level records under `upstream` whether the level below was behind when Brussle judged it, as for any roll-up.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.