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

# Maturity Ramp

> Bayesian Confidence-Bound Maturity Ramp (BCB-MR) — how Kaireon decides when an offer has enough evidence to rank at full exposure.

# Maturity Ramp

When a new offer goes live, its propensity posterior is wide — we don't yet know its true acceptance rate. Showing it at full ranking confidence is reckless (we might over-spend on a dud) but suppressing it entirely is also wrong (we'll never learn). The **maturity ramp** is the gating decision that controls cold-start exposure as evidence accumulates.

Kaireon ships **two** maturity ramps. The default — **Bayesian Confidence-Bound Maturity Ramp (BCB-MR)** — is the better one and the rest of this page covers it. A legacy linear-count ramp is kept for back-compat with tenants who tuned around a fixed interaction threshold.

## Why BCB-MR exists

Count-based maturity gates — "treat the model as immature until it has accumulated N responses" — are the conventional choice in next-best-action systems. The approach is intuitive but has two real failure modes:

1. **Low-volume offers are trapped.** A campaign for 100 high-value enterprise customers will never accumulate a 5,000-response gate. The model is forever immature even when its posterior is already tight — useful signal is thrown away.
2. **High-volume volatile offers are prematurely "done".** An offer with thousands of trials at a seasonal volatile rate still has a wide posterior. A pure count gate declares it mature even when it deserves continued exploration.

The signal we actually want is **posterior credible-interval width** — "how much room is left for the true rate to be?". An offer is mature when we can pin down its rate within an acceptable confidence band.

## The algorithm

For each candidate offer at decision time:

1. Aggregate the **offer-scope** `ModelAdaptation` posterior for this offer — its `positives` + `negatives` summed across all active models. The maturity ramp keys off the offer-scope Beta posterior specifically, not the full [scope hierarchy](/ai-ml/model-lifecycle#scope-hierarchy) that propensity scoring walks — maturity is a property of the offer, not of a channel/direction cell. (A `paused` offer-scope row bypasses the ramp entirely.)
2. Compute the **Wilson score interval** for the Bernoulli proportion `p̂ = positives / (positives + negatives)` at the configured confidence level (default 95%, z = 1.96). Wilson 1927 is the canonical closed-form CI; well-behaved at boundaries (no blow-up at p̂ = 0 or 1, unlike the Wald interval).
3. Let `width = upper − lower`. If `width ≤ widthThreshold` (default **0.20**), the offer is **mature** → `exposureProbability = 1.0`.
4. Otherwise the offer is **immature**. Compute a **decaying cold-start floor**:

   ```
   floor(n) = baseFloor / √(1 + n / decayHalfLife)
   ```

   At n=0 the floor is `baseFloor` (default 0.50). At n=100 with default decay it's ≈ 0.15. The decay mirrors UCB1's `√(2 ln t / n)` shrinkage but applied to the *floor* rather than an exploration bonus.
5. Return `exposureProbability = max(floor(n), wilsonLower)`. The `max` ensures we never penalize evidence we already have — an offer with strong early positives (8/10 → Wilson lower ≈ 0.49) gets at least that lower-bound exposure even if the decay-floor shrank below it.

The caller then does a deterministic per-customer/per-day roll: `hash(customerId + offerId + today) ≤ exposureProbability` decides whether THIS impression includes the offer.

## Worked examples

The table below shows real exposure decisions at default config (`widthThreshold = 0.20`, `baseFloor = 0.50`, z = 1.96, `decayHalfLife = 10`).

| Positives | Negatives | Wilson lower | Wilson upper | Width | Source        | Exposure |
| --------: | --------: | -----------: | -----------: | ----: | ------------- | -------: |
|         0 |         0 |         0.00 |         1.00 |  1.00 | `no_evidence` |    0.500 |
|         1 |         9 |        0.018 |        0.404 | 0.386 | `floor`       |    0.354 |
|         3 |         7 |        0.108 |        0.603 | 0.496 | `floor`       |    0.354 |
|         8 |         2 |        0.490 |        0.943 | 0.452 | `ci_gated`    |    0.490 |
|        40 |        60 |        0.309 |        0.497 | 0.188 | `mature`      |    1.000 |
|       200 |       800 |        0.176 |        0.226 | 0.050 | `mature`      |    1.000 |

The 8/10 row is the interesting one: the offer is clearly persuasive (μ̂ = 0.80) but the CI is still wide, so it's `ci_gated` — exposure is the Wilson lower bound 0.49. As soon as evidence tightens the CI below 0.20 width, the offer flips to `mature`.

## Configuration

Per-tenant settings (Settings → Models → Maturity Ramp):

| Setting                      | Default         | Effect                                                                                                                                                    |
| ---------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maturityRampMode`           | `"bayesian_ci"` | Set to `"legacy_count"` to use the count-based ramp.                                                                                                      |
| `maturityWidthThreshold`     | `0.20`          | CI width below which an offer is mature. Lower = stricter.                                                                                                |
| `maturityRampColdStartFloor` | `0.50`          | Maximum cold-start exposure floor (the `baseFloor` in the formula).                                                                                       |
| `modelMaturityThreshold`     | `100`           | Used only when `maturityRampMode = "legacy_count"`. The number of impressions that flip an offer to mature. Set to 0 to disable the legacy ramp entirely. |

## Why this design works

1. **Low-volume offers aren't trapped.** A campaign for 100 enterprise customers can mature once its posterior tightens, regardless of raw response count.
2. **High-volume volatile offers stay in exploration.** An offer with thousands of trials but a still-wide CI keeps getting exploration traffic instead of being declared "done" by a count threshold.
3. **The threshold is principled.** A 0.20 width at 95% means "the true rate is known to within ±0.10" — a tunable that business owners can reason about, with a clear posterior interpretation rather than a chosen convention.
4. **The maturity event is observable.** Operators see a single interpretable number — "this offer's posterior CI width is 0.18, just below the 0.20 threshold" — instead of "evidence count is 4837 of 5000". Combined with [direction-scoped adaptations](/ai-ml/model-lifecycle#scope-hierarchy), this surfaces directly in the Model Health UI.

## References

* Wilson, E.B. (1927). "Probable inference, the law of succession, and statistical inference." *JASA* 22:209–212. The canonical Bernoulli proportion CI.
* Auer, Cesa-Bianchi, Fischer (2002). "Finite-time Analysis of the Multiarmed Bandit Problem." *Machine Learning* 47:235–256. UCB1 — origin of the √(ln t / n) shrinkage form adapted here as a decaying floor.
* Chapelle & Li (2011). "An Empirical Evaluation of Thompson Sampling." NeurIPS. Empirical justification for Bayesian bandit-style exposure.

## Code

* Library: `platform/src/lib/ml/maturity.ts`
* Tests: `platform/src/lib/ml/__tests__/maturity.test.ts`
* Call site: `platform/src/lib/pipeline-runner.ts` (`applyMaturityRamp`)
