Skip to main content
Your numbers do not change night to night for no reason. Brussle fits every night, and a new fit replaces the one your answers read only when it is clearly better on the outcomes that arrived since. When the numbers change, something changed in your data. To take a new fit at once, promote it. To hold one you have audited, pin it. The fit that answers read is the current fit. Each run makes a new fit, the challenger.

When fits run

  • The first run starts within a few minutes of the first outcome you post, or of the judgment’s first outcome rules. A run makes a fit only for an epoch that meets the minimums, so the first run usually makes none.
  • Until there is a fit, Brussle runs again every hour, for the current epoch and for a new epoch with no fit yet. The first fit comes within the hour after your outcomes meet the minimums.
  • Once there is a fit, Brussle makes a new fit each night, on all the outcomes so far, rule outcomes included. It replaces the current fit only under the rule below.
  • When Brussle re-judges answers to correct them, the next run starts within a few minutes after the correction ends. See when answers are corrected.

Fit now

To fit at once, for example right after you post the outcomes that meet the minimums, call POST /namespaces/{ns}/judgments/{name}/calibration/fit (fitCalibration, fit_calibration), or select Fit now in the judgment’s Calibration section. The run usually ends within a minute or two. Then read the report.
  • You can ask once an hour for each judgment. Within that hour, the call returns rate_limited (429), and Retry-After gives the seconds until the next call is accepted. Named runs share this limit.
  • If a run is already waiting or running, that run serves the call.
  • It runs on every plan, and it is never billed.
Fit now follows the rule below like a nightly run. To make its fit current regardless, promote.

The rule

The challenger replaces the current fit only when both are true:
  1. It is clearly better on the outcomes that arrived since the current fit was made. The current fit never saw them, so they are a fair test of both. The challenger is scored on each of them without that outcome, and must beat the current fit by more than two standard errors of the mean difference in log loss.
  2. It moves the number by at least a point: 0.01 on average over all the judgment’s outcomes. For a choice or score, this is the probability that moves between the options.
The test runs only when the outcomes since hold at least 20 of each kind. Otherwise the current fit stays, with the same fit id, and nothing is judged again.
  • An outcome arrives when you post it, not when it was observed. An implicit negative arrives when its window closes. After the engine changes, a replayed outcome arrives when it is replayed. An outcome whose answer was judged again with changed inputs counts as new too.
  • The current fit is withdrawn when the engine’s raw numbers beat it by the same margin on the outcomes since. Answers then read the night’s fit, or the raw numbers if that did not beat them.
  • A new start takes the night’s fit at once: the first fit, a new engine epoch, a new version, and a change from fitting on all outcomes to fitting on labelling-queue labels alone, or back.
  • Below 100 outcomes, while the current fit or the night’s rests on fewer than 100, each night’s fit replaces the current one, under the margin below 100 rather than this test.
  • When a run cannot fit again, because outcomes were deleted and the epoch fell below the minimums, the current fit stays.
  • It applies to every judgment: to calibrated on a bool, a choice and a score, to p of a judgment with features, and to a template tenant’s own fit and its own weights, on that tenant’s own outcomes. The exception is a choice among candidates with features: each run’s fit replaces the current one, as a first fit does.
  • On a steady process, the rule changes the fit by chance about once in three to five years for each version and epoch. A real change in your outcomes, such as a new base rate, shows in the outcomes since, and the new fit replaces the current one once it is clearly better.

Read the current fit and the challenger

Each epoch of the report shows both:
  • current: the fit answers read now. fit is its id, or null when it did not beat the raw numbers. run holds it. pinned is true for a pinned fit, and pin_suspended is older_preprocessing when the pin is suspended. fitted_at is when the run that made it ran; a fit kept since an earlier run keeps that date, so it can be weeks older than the newest run. since measures it against the raw numbers on the outcomes that arrived after it was made: outcomes, fallback_log_loss (the raw numbers’), log_loss (the current fit’s) and the standard_error of the difference. since is null while those outcomes hold fewer than 20 of each kind, and on the night the fit was made. current is null while the epoch has no fit of its own.
  • challenger: the newest run’s fit when it did not replace the current one; null when it did, and when there was no current fit. It has its own fit, run, fitted_at, outcomes and held_out, and versus_current: the challenger against the current fit on the outcomes since. gain is the current fit’s log loss minus the challenger’s, so a positive gain favours the challenger. shift is how far the challenger would move the number on average over every outcome. gain and standard_error are null with fewer than 20 of each kind since. On a choice or score with features, moved_picks is the share of outcomes whose most probable option would change.
On a template’s tenant with a fit of its own, tenant.current and tenant.challenger say the same of the tenant’s own fit, against the pool (fallback: pooled). See per-customer corrections.

Why a new fit was held

challenger.held says why the current fit stays:
  • too_few_outcomes_since: fewer than 20 of each kind arrived since the current fit was made. Post more outcomes, or wait for them.
  • not_clearly_better: on the outcomes since, the new fit did not beat the current one by more than two standard errors (versus_current.gain and standard_error). The difference is within noise.
  • too_small_a_move: the new fit is better, but would move the number by less than 0.01 on average (versus_current.shift). On a judgment with features, each move re-judges the judgments that read p, billed, so a move this small is not worth it.
  • pinned: a pin holds the fit.
The dashboard’s list of fits shows the same in its Held column, with the new fit on the row under the current one. The lift chart draws what the new fits would score, dotted, beside what answers read. current.since tells whether the current fit still beats the raw numbers on outcomes it never saw; when it stops doing so by the same margin, the next run withdraws it.

Promote the newest fit

To make the newest fit current for every version and epoch now, whether or not it is clearly better, call Fit now with promote:
  • The run makes its own new fit and applies it. It fits all the outcomes so far, as a nightly run does, and that fit becomes current for each version and epoch that is not pinned, tenants included. A promote never moves a pin.
  • With readers, confirm first. On a bool with features that other judgments read, a new current fit judges their documents again, billed. Without confirm, the call returns the downstream estimate (200), made against last night’s fits, and queues nothing. With confirm: true, the run is queued, and the answer carries the estimate too.
  • A confirmed promote is not held by the hourly limit. You accepted its cost, so it is queued even within an hour of Fit now. Without readers, a promote is queued at once and counts as Fit now’s request for the hour.
  • Templates. On a namespace that inherits the judgment, a promote is invalid_request; promote on the template. Fit now without a body still queues the template’s run.
In the dashboard, the challenger’s row has Promote, Pin and Compare. With readers, Promote first shows the estimate and a confirm checkbox, and the row reads promotion queued until the run lands. Promote or pin? Promote is for “I trust the rule, but I want tonight’s fit now”. To hold one fit you have audited, pin it: the challenger has an id, and a pin by that id makes it current and holds it.

Pin a fit

By default, each answer reads the current fit for its version and epoch (auto). A pin holds one fit you choose for one version and epoch: to hold a fit you checked, or to go back to an earlier one. Name the fit as the list of fits shows it: its run, version, engine and engine_version. fit is optional; when you give it, Brussle checks it is that run’s fit.
  • Send calibration alone in its PATCH /namespaces/{ns}/judgments/{name}, without other settings, or the request is invalid_request.
  • The list replaces all the pins. Include the pins you want to keep. A pin sent again from the same run stays as it is. Two pins for one version and epoch is invalid_request.
  • The pin writes a run (cause: pin) that holds the pinned fit, and every later run holds it too. The response has the judgment, with calibration: {"mode": "pinned", "pins": [...]}, and each pin has pinned_at. mode is auto when no pin applies.
  • Answers of that version and epoch read the pinned fit, also when a later fit would do better. Every other version and epoch stays auto. Each run still makes a fit and lists it as the challenger; when it clearly beats the pinned fit on the outcomes since, the report warns pinned_fit_beaten.
  • A pin is a setting, like thresholds. It makes no version, and a new version starts in auto.
  • On any judgment without features, a pin changes only calibrated. Readers use the raw numbers, so nothing is judged again or billed, and there is nothing to confirm.
On a bool with features that other judgments read, a pin also stops the nightly re-judging of readers for that version and epoch. A pin of a fit other than the one p reads now changes p once, so Brussle judges again the readers whose view of it changes, billed as any re-judge. Confirm it first, as for a threshold change a reader reads:
Without confirm, the response is only the downstream estimate, and nothing changes. With confirm: true, the pin applies and a resync job starts; the response has the judgment, job_id and downstream. The dashboard shows the cost first and asks you to confirm. Brussle refuses a pin with invalid_request when the run is not in the judgment’s list of fits; the run has no fit for that version and epoch, for example because it did not beat the raw numbers; fit is not that run’s fit; the fit uses features and was made before Brussle last changed how it keeps features; or the namespace inherits the judgment from a template (set the pin on the template; see per-customer corrections). If the pins changed between your read and your write, the request is conflict: read the judgment and send the list again.

A pin and a new epoch

A pin never reaches another epoch. After the engine changes by itself, the new epoch starts in auto. Until it has its own fit, its answers read the previous epoch’s fit as usual, which here is the pinned fit, with "from_previous_epoch": true and "pinned": true. The 30-day limit for calibrated applies as for any fit. p of a bool with features keeps the pinned fit until the new epoch has its own.

A suspended pin

Sometimes Brussle changes how it keeps features, and a fit with features made before the change cannot read the features after it. At the next run its version and epoch read auto: the pin shows "suspended": "older_preprocessing", the answer shows "pinned": false, and the report warns pin_suspended. Pin a fit made since, or lift the pin. A pin of a fit without features is never suspended.

Lift a pin

Send the list of pins without it. {"calibration": {"pins": []}} lifts them all:
  • The next run comes at once. Brussle brings it forward, usually within a few minutes. It does not count toward Fit now’s limit, and the list of fits shows it as cause: nightly.
  • Until that run ends, answers read the pinned fit and still show "pinned": true. GET on the judgment already shows mode: auto.
  • A lift keeps the number until a clearly better fit arrives. The formerly pinned fit stays current, now in auto, and that run’s fit replaces it only when it is clearly better on the outcomes since the pinned fit was made, usually many by then. To take tonight’s fit anyway, promote. On a bool with features, when the fit p reads changes, readers are re-judged and billed, with no confirm.
After the engine changes, lift a pin only when the replay is done. While the replay runs, the new epoch’s answers read the pinned fit. Lift it first, and they read the previous epoch’s new fit, then the new epoch’s own fit when the replay ends: they change twice, and on a bool with features each change re-judges the readers. The report’s replay.status is done when it has ended.

Events when the fit changes

On a judgment with features, p is the corrected number and readers act on it. When the fit that p reads changes (its first fit, a promotion, a withdrawal, a pin or a lift), Brussle brings up to date the judgments that read it: it judges again the documents whose view of it changed, billed like any re-judge. A night that keeps the current fit re-judges nothing. Then:
  • judgment.fit_applied reports how many documents crossed each of the judgment’s thresholds. When p goes back to the engine’s, the event is judgment.fit_withdrawn. Both carry fit, run and pinned.
  • The event gives each reader’s re-judges: engine_judgments, which are billed, and reuses, re-judges where only a feature the reader keeps moved, which use their engine answer again at no charge.
  • The counts come from those re-judges, so the event arrives after they have started.
See roll-ups and the event types. On a judgment without features, nothing reads calibrated, so a new fit judges nothing again.