Skip to main content
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 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: contract_terms reads the one_sided answers of each contract’s clauses. Relations, chains and bands are on 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 filters on: 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): 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 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: 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 can be read only through a link 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 or a block 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 can read that document’s answers in the same way. A change to what it shows is a referenced change, and fans 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 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 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, 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 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:
  • 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.
A group 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 that a reader names, and a pin or a promote on a child with features.
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:
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, this response is often where you learn that another judgment reads the threshold. On the prefix path of a template, 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 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 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 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: 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 or promote the newest one. After you 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. 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). It lists only the child’s direct readers, the first level of downstream. 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 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, the pass runs in each tenant that inherits the child. When only one tenant’s own fit changes (each tenant grows its own correction), 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. 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. 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.
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.
The evaluation of each level records under upstream whether the level below was behind when Brussle judged it, as for any roll-up.