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

# Fits and runs

> Every correction has an id and belongs to a run that Brussle keeps for the life of the namespace. Find the fit behind any number, read its formula, compare two fits on the same outcomes, and fit on outcomes you choose.

Every correction Brussle makes has an id you can quote, and Brussle keeps it. Find the fit behind any corrected number, read the formula it went through, compare two fits on the same outcomes, and fit on the outcomes you choose beside the automatic fit.

* A **run** is one calibration of a judgment. Brussle makes one each night, when you ask for [Fit now](/train/promotion#fit-now), when you [pin a fit](/train/promotion#pin-a-fit), when you make a [named run](#fit-on-outcomes-you-choose), and when a composite's confirmed shadow report fits its weights. Each run has an id, `run`. **Brussle keeps every run for the life of the namespace.**
* A **fit** is the correction for one judgment version on one engine epoch, inside a run. Its id is `fit_` and 16 hexadecimal characters, such as `fit_9f3a2c7e1b04d6a8`. A run that makes the same correction again gives it the same id. A fit that did not [beat the raw numbers](/train/correction#only-when-it-beats-the-raw-numbers) has no id, and neither do a composite's weights.

Each corrected answer names its `fit` and its `run`. Which fit answers read, and when a new one replaces it, is on [when a new fit replaces the current one](/train/promotion).

## List the fits

In the dashboard, the judgment's **Calibration** section lists the fits by version and epoch, newest run first. Each row has **Details** (the formula), **Compare**, **Pin** and **Lift pin**, and a challenger's row also has **Promote**. A line at the top shows `auto` or the pins. The judgments list shows each judgment's calibration as a badge: `Raw`, `Early`, `Full`, `Features` or `Pinned`.

`GET /namespaces/{ns}/judgments/{name}/calibration/fits` lists the fits, newest run first, one row for each version and epoch of a run:

<CodeGroup>
  ```ts TypeScript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  const { mode, fits, next_cursor } = await ns.judgments.calibrationFits("material_change", { version: 3 });
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  page = ns.judgments.calibration_fits("material_change", version=3)
  ```
</CodeGroup>

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "mode": "auto",
  "fits": [
    {
      "run": "01JA2J0P2R4T6W8Y0A2C4E6G9B",
      "fitted_at": "2026-10-02T03:00:00Z",
      "cause": "nightly",
      "version": 3,
      "engine": "jev",
      "engine_version": "current+2026-09-24.1",
      "fit": "fit_9f3a2c7e1b04d6a8",
      "applied": true,
      "stage": "full",
      "outcomes": 1432,
      "pinned": false,
      "current": true,
      "outcomes_by_source": {"posted": 390, "rule": 1042, "queue": 0, "implicit": 0},
      "fitted_on": "all",
      "source": "outcomes",
      "held_out": {"raw_log_loss": 0.441, "calibrated_log_loss": 0.351, "raw_ece": 0.152, "calibrated_ece": 0.012, "standard_error": 0.009},
      "features_only": null
    }
  ],
  "next_cursor": null
}
```

* **`run`** and **`cause`**: the run, and why it ran. `nightly` is the scheduled run, or one Brussle brought forward: after your first outcomes, after the engine changed, or after you lifted a pin. `fit_now` is a Fit now. `pin` is the run that a pin writes. `named` is a [named run](#fit-on-outcomes-you-choose). `shadow` is a composite's weights, fitted when you confirmed its shadow report.
* **`fit`**: the fit's id, the same in every run that holds the same correction. `null` when the fit did not beat the raw numbers; then `applied` is `false`, and there is nothing to pin.
* **`current`**: `true` on the fits answers read now. **`pinned`**: `true` when the run holds the fit because it is pinned; `pinned_from` then names the run you pinned it from.
* **`role`**, **`promotion`**, **`held`** and **`versus_current`**: from 2026-10-13, a run can keep the current fit and list its new fit beside it. The current fit's row has `role: current` and, in `promotion`, what the run did: `first`, `promoted`, `withdrawn`, `held` or `pinned`, with `held` saying why it was kept. Its `fit` id stays the same night after night while it is kept. The new fit is on the row under it, with `role: challenger` and `versus_current`. You can compare it and pin it. A pin of a run that holds two fits for a version and epoch must name its `fit`. See [why a new fit was held](/train/promotion#why-a-new-fit-was-held).
* **`held_out`**: the fit's [held-out test](/train/correction#only-when-it-beats-the-raw-numbers), as recorded when the fit was made. On a `bool`, it also gives `ranking`: `auroc` and precision at the top 5% and 10%, raw and with the fit. **`features_only`**: on a judgment with features, the same test of a fit on the features alone.
* **Filters:** `version`, `engine_version` (one epoch), `since` (runs made at or after a time) and `cause`, such as `cause=named`.
* **Pages.** A page has about 100 rows and never divides a run. Send `next_cursor` back as `cursor`, with the same filters, for the next page. It is `null` on the last page.
* **Templates.** On a namespace that inherits the judgment, the list shows the template's fits, which are what its answers read.

`held_out` was measured on the outcomes the fit had when it was made, and two runs had different outcomes. Do not compare the `held_out` numbers of two runs; [compare the fits](#compare-two-fits) instead.

## Read the formula of a fit

`GET .../calibration/fits/{fit}` returns one fit, from the newest run that holds it. It is `ns.judgments.calibrationFit(name, fit)` in TypeScript and `calibration_fit(name, fit)` in Python. It has the row's fields, and also:

* **`model`**: the terms that turn the engine's raw value into the corrected value. `form` says which formula applies. With `logit(p) = ln(p / (1 − p))`, `p` first clamped to 0.005 to 0.995, and `σ(z) = 1 / (1 + e^−z)`:

  | `form` | Terms | Corrected value |
  | - | - | - |
  | `log_odds_linear` | `a`, `b` | A `bool`: `σ(a · logit(p) + b)` |
  | `temperature` | `t` | A `choice` or `score`: `softmax(ln(pᵢ) / t)` over its options or levels, each `pᵢ` clamped as above. A `bool`: `σ(logit(p) / t)` |
  | `points` | `x`, `y` | The raw value, interpolated in a straight line through the points, and flat past the ends. A `choice` or `score` maps the probability of each option or level, then divides each by their sum |
  | `features` | `intercept`, `inputs`, `weights`, `centers`, `scales` | A `bool` with features: `σ(intercept + Σ wᵢ · (xᵢ − cᵢ) / sᵢ)` over `inputs`. The first input, `log_odds`, is `logit(p)`. The others are the features' raw values. A missing value adds 0 |

  `model` is `null` when the fit did not beat the raw numbers.
* **`range`**: the lowest and highest raw value the fit used. Outside it, a corrected number is `extrapolated`.

From the raw value in an answer and the fit it names, you can calculate its corrected number again. Brussle keeps answers' numbers to about seven significant digits. A fit id that no run of the judgment holds is `not_found`.

On a template's tenant that reads a fit of its own (`tenant.applied` is `tenant_shrunk`), the corrected number blends the tenant's fit with the template's fit that `fit` names, and this route does not give the tenant's own formula. See [per-customer corrections](/train/tenants).

## Compare two fits

Each fit's `held_out` comes from the outcomes it had when it was made. To know which of two fits is better on your outcomes now, compare them on the same outcomes:

```text theme={"theme":{"light":"css-variables","dark":"css-variables"}}
GET /namespaces/{ns}/judgments/material_change/calibration/fits/fit_9f3a2c7e1b04d6a8/compare?with=fit_4b7e0d2a9c6f1e83
```

<CodeGroup>
  ```ts TypeScript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  const compared = await ns.judgments.compareCalibrationFits("material_change", "fit_9f3a2c7e1b04d6a8", "fit_4b7e0d2a9c6f1e83");
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  compared = ns.judgments.compare_calibration_fits("material_change", "fit_9f3a2c7e1b04d6a8", "fit_4b7e0d2a9c6f1e83")
  ```
</CodeGroup>

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "version": 3,
  "engine": "jev",
  "engine_version": "current+2026-09-24.1",
  "outcomes": 1469,
  "fitted_on": "all",
  "a": {"fit": "fit_9f3a2c7e1b04d6a8", "run": "01JA2J0P2R4T6W8Y0A2C4E6G9B", "fitted_at": "2026-10-02T03:00:00Z", "held_out": {"raw_log_loss": 0.443, "calibrated_log_loss": 0.352}},
  "b": {"fit": "fit_4b7e0d2a9c6f1e83", "run": "01JA4M1R3T5V7X9Z1B3D5F7H0K", "fitted_at": "2026-10-09T03:00:00Z", "held_out": {"raw_log_loss": 0.443, "calibrated_log_loss": 0.361}},
  "difference": {"log_loss": -0.009, "standard_error": 0.003}
}
```

The example leaves out each side's calibration error, ranking and ten-bin `reliability` curve.

* **The same outcomes.** Brussle reads the epoch's outcomes as they are now, as the next run would fit them, and scores both fits, as answers read them, beside the raw answers. `outcomes` is how many it scored. When the epoch's queue labels alone meet the minimums, `fitted_on` is `queue`, and it scores those labels, weighted to your traffic.
* **The stored fits.** Each fit is scored as it was made, not fitted again. The outcomes a fit was made on are scored too, so a fit made on more of them can look better. Read `difference` with this in mind.
* **`difference`**: the mean log loss of `a` minus that of `b`, per outcome, with its standard error. Positive favours `b`; negative favours `a`. Treat a difference under about two standard errors as not clear. `null` with fewer than two outcomes.
* **Rules.** Both fits must be for the same version and epoch, or the call is `invalid_request`. `with` is required.
* **Limit.** One compare for each judgment each minute; within it, `rate_limited` (429) with `Retry-After`. A compare can take longer than the limit for a request while Brussle reads the outcomes; it is then `unavailable` (503) with `Retry-After`. Retry after that time. The SDKs wait out a short `Retry-After` and retry for you.

When the comparison clearly favours one fit, you can [pin it](/train/promotion#pin-a-fit).

## Fit on outcomes you choose

A **named run** fits only the outcomes you choose, by where they came from and when they were observed: for example, your labelled examples alone, or one quarter's outcomes. Answers never read a named run unless you pin one of its fits. A named run is never billed.

`POST /namespaces/{ns}/judgments/{name}/calibration/fit` with a body makes one:

<CodeGroup>
  ```ts TypeScript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  await ns.judgments.fitCalibration("material_change", {
    label: "labels only, Q3",
    outcomes: { sources: ["posted", "queue"], since: "2026-07-01T00:00:00Z", until: "2026-10-01T00:00:00Z" },
  });
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  ns.judgments.fit_calibration(
      "material_change",
      label="labels only, Q3",
      outcomes={"sources": ["posted", "queue"], "since": "2026-07-01T00:00:00Z", "until": "2026-10-01T00:00:00Z"},
  )
  ```
</CodeGroup>

* **`label`** (required): what you call the run, 1 to 200 characters.
* **`outcomes.sources`**: any of `posted`, `rule` (from [outcome rules](/train/outcomes#record-outcomes-from-your-own-writes)), `queue` ([labelling-queue](/train/thresholds#label-a-random-sample) labels) and `implicit` ([implicit negatives](/train/horizons#turn-on-implicit-negatives)). Absent or empty keeps every source.
* **`outcomes.since`** and **`outcomes.until`**: outcomes observed at or after `since` and before `until`. An implicit negative counts as observed when its window closed. `since` must be before `until`.
* **An outcome you leave out still labels its answer,** so it never turns that answer into an implicit negative.
* **The same rules as any fit:** the [minimums](/train/correction#at-least-15-of-each-kind), the held-out test and the queue weights apply to the outcomes you chose. The queue weights come from the queue labels you kept; without `queue` there are none.
* **The response** (202) has `queued_at`, `next_request_at` and your `label`. The run usually ends within a minute or two.
* **Limit.** Named runs and Fit now share one limit: one request an hour for each judgment. Within it, the call is `rate_limited` (429) with `Retry-After`.
* **Templates.** On a template's prefix path, a named run fits the pooled outcomes of the tenants and makes no tenant's own fit. On a namespace that inherits the judgment, it is `invalid_request`; make it on the template.

The list of fits shows the named run with `cause: named`, its `label` and its `filter`; `?cause=named` lists named runs only. A version and epoch whose chosen outcomes do not meet the minimums has `fit: null` and `not_fitted`, with the reason and a message. To use a named run's fit, [compare](#compare-two-fits) it with the current fit, then [pin](/train/promotion#pin-a-fit) it.


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