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

# Algorithms & Models

> ML models that score offers for customers — from manual scorecards to neural collaborative filtering, with built-in experimentation.

The Algorithms module is the machine learning layer that predicts *how likely a customer is to engage with each offer*. Every scoring model takes a customer-offer pair and produces a propensity score between 0 and 1. That score becomes the **P** (Propensity) factor in the [PRIE ranking formula](/decisioning/decision-flows#prie-ranking-formula), which combines it with Relevance, Impact, and Emphasis to determine the final ranking.

KaireonAI ships with **9 scoring engines** — from a transparent scorecard you can configure in minutes (no training data needed) to gradient-boosted trees (LightGBM) and neural collaborative filtering that learns latent user-item embeddings from interaction data. You can start simple and upgrade later without changing your Decision Flows; the engine is a configuration detail, not a structural one.

The module also includes a full experimentation framework with champion/challenger testing, holdout groups, and uplift measurement so you can measure real-world impact before rolling out changes.

***

## When to Use Which Engine

| Engine                                        | Data Required         | Best For                                                            | Setup Time |
| --------------------------------------------- | --------------------- | ------------------------------------------------------------------- | ---------- |
| [Scorecard](#scorecard)                       | None                  | Regulated industries, simple rules, full audit trail                | Minutes    |
| [Bayesian](#bayesian-naive-bayes)             | Some historical data  | Cold-start, adaptive scoring, interpretable contributions           | Minutes    |
| [Logistic Regression](#logistic-regression)   | 1,000+ interactions   | Fast linear classifier with interpretable coefficients              | Minutes    |
| [Gradient Boosted](#gradient-boosted)         | 5,000+ interactions   | Maximum accuracy on non-linear structured data (requires ML Worker) | Hours      |
| [Thompson Bandit](#thompson-bandit)           | Outcome signals       | Auto-discovering best offers, content selection                     | Minutes    |
| [Epsilon-Greedy](#epsilon-greedy)             | Outcome signals       | Deterministic exploit-phase scoring, easy debugging                 | Minutes    |
| [Neural CF](#neural-collaborative-filtering)  | Rich interaction data | Latent preference discovery, sparse matrices                        | Hours      |
| [Online Learner](#online-learner)             | Streaming outcomes    | Real-time adaptation, fast-moving environments                      | Minutes    |
| [External Endpoint](#external-model-endpoint) | External model        | BYO model (SageMaker, Vertex AI, Azure ML)                          | Minutes    |

<Tip>
  Start with a **Scorecard** during initial setup. Once you have 100+ interactions, add a **Bayesian** model as a challenger. At 1,000+ interactions, test **Logistic Regression** via champion/challenger experiments to see if accuracy improves. At 5,000+ interactions — and with the [ML Worker](/self-host/deploy/ml-worker) deployed — try **Gradient Boosted** for the best accuracy on non-linear patterns.
</Tip>

***

## Scoring Engines

### Scorecard

A weighted point system where you define rules that match customer or offer fields against conditions and award points. The raw total is normalized to 0--1 using sigmoid or linear normalization.

**Example (retail rewards):** Award 20 points if `reward_tier = "gold"`, 15 points if `visit_frequency >= 3`, 10 points if `age >= 25`.

| Field           | Type   | Default     | Description                                       |
| --------------- | ------ | ----------- | ------------------------------------------------- |
| `baseScore`     | number | 50          | Starting point total before rules fire            |
| `rules`         | array  | `[]`        | `{ field, operator, value, points, description }` |
| `normalization` | enum   | `"sigmoid"` | `"sigmoid"` or `"linear"`                         |
| `maxScore`      | number | 100         | Upper bound of point range                        |
| `minScore`      | number | 0           | Lower bound of point range                        |

**Supported operators:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `contains`, `starts_with`.

Every rule evaluation is returned in the `explanations` array — fully auditable.

**Pros:** Transparent, instant setup, no training data, easy to audit.
**Cons:** Cannot capture non-linear feature interactions, manual maintenance.

***

### Bayesian (Naive Bayes)

An adaptive probability model that starts with a uniform prior and updates as it observes real outcomes. Each predictor contributes a log-likelihood ratio, and the posterior probability becomes the score. Laplace smoothing prevents zero-probability issues.

| Field                   | Type   | Default | Description                                |
| ----------------------- | ------ | ------- | ------------------------------------------ |
| `laplaceSmoothingAlpha` | number | 1       | Smoothing parameter                        |
| `aucThreshold`          | number | 0.5     | Minimum AUC to consider model useful       |
| `maxPredictors`         | number | 20      | Cap on predictor count                     |
| `binCount`              | number | 10      | Bins for continuous feature discretization |
| `priorPositiveRate`     | number | 0.5     | Initial assumed conversion rate            |

**Key features:**

* **Cold-start handling:** Untrained model returns 0.5 for all customers (uniform prior). Score spread increases as data arrives.
* **Incremental learning:** With `autoLearn: true` and `learnMode: "per_outcome"`, each recorded outcome updates the model. A `RETRAIN_EVERY_N` threshold (default 100) controls full recomputation frequency.
* **Training enrichment:** Training enriches customer attributes from schema tables (`ds_*` tables) so the model learns from real features like age, income, tenure — not empty context objects.

<Note>
  Predictor field names are resolved against your schema-table columns in **two** accepted forms: the bare column name (`credit_score`) **or** the schema-qualified name (`banking_customers.credit_score`). Both resolve during training — use whichever your schema editor produced. What still must match is the **column name itself**: if your `ds_customers` table has `household_income`, reference `household_income` (or `customers.household_income`), not a generic `income`. A predictor whose column doesn't exist contributes nothing and shows `importance: 0`.
</Note>

**Pros:** Learns from data, handles cold-start, interpretable per-field contributions.
**Cons:** Assumes feature independence, less accurate than tree-based models on complex data.

***

### Logistic Regression

A linear classifier with sigmoid activation. Trained on customer features, offer features, and interaction history via full-batch gradient descent with optional L2 regularization. Fast, interpretable per-feature coefficients, and works well with 1,000+ samples.

| Field                    | Type   | Default | Description                                   |
| ------------------------ | ------ | ------- | --------------------------------------------- |
| `learningRate`           | number | 0.01    | Step size for gradient-descent weight updates |
| `maxIterations`          | number | 100     | Full-batch passes over the training data      |
| `regularization`         | enum   | `"l2"`  | Penalty family: `none`, `l1`, or `l2`         |
| `regularizationStrength` | number | 1.0     | Penalty magnitude (λ)                         |

Real weights are fit only with **≥ 20 usable labeled rows spanning both classes**; below that, training falls back to a metrics-only pass (weights unchanged). See the [Logistic Regression algorithm page](/ai-ml/algorithms/logistic-regression) for the fit details and a caveat on how the L2 config is wired.

**Pros:** Fast training, fully interpretable coefficients, small model footprint, in-process scoring.
**Cons:** Can only capture linear relationships — combine with feature engineering for non-linear effects.

***

### Gradient Boosted

A LightGBM tree ensemble — the highest-accuracy engine in the platform. Training runs in the Python [ML Worker](/self-host/deploy/ml-worker) using LightGBM; the trained ensemble is serialized as portable tree JSON and **scored in-process in Node**, so the `/recommend` hot path never calls the Python service.

| Field             | Type    | Default | Description                                                   |
| ----------------- | ------- | ------- | ------------------------------------------------------------- |
| `numLeaves`       | integer | 31      | Tree complexity — higher = more capacity and overfitting risk |
| `maxDepth`        | integer | -1      | Depth cap; `-1` lets LightGBM decide based on `numLeaves`     |
| `learningRate`    | number  | 0.05    | Shrinkage per tree; lower values need more estimators         |
| `nEstimators`     | integer | 100     | Number of boosting trees                                      |
| `minChildSamples` | integer | 20      | Minimum data points per leaf — higher = more regularization   |
| `regAlpha`        | number  | 0       | L1 regularization on leaf weights                             |
| `regLambda`       | number  | 0       | L2 regularization on leaf weights                             |
| `subsample`       | number  | 1.0     | Row sampling ratio per tree                                   |
| `colsampleBytree` | number  | 1.0     | Feature sampling ratio per tree                               |

**Architecture:**

* **Training:** Python ml-worker receives a compact JSON payload (`feature_names`, `training_data`, `hyperparams`) and fits a LightGBM booster. The booster is dumped as portable tree JSON with `split_feature`, `threshold`, `default_left`, and `leaf_value` at each node.
* **Scoring:** The Node scorer walks every tree per record, summing leaf values into a raw margin, then applies sigmoid. Typical latency is 5--50µs for a 100-tree ensemble. Missing values are routed via LightGBM's native `default_left` convention.
* **Zero hops in decision path:** The `/recommend` API never calls the ML Worker. Training is the only phase that does.

**Requirements:**

* **5,000+ labeled interactions** for meaningful accuracy (the engine will cold-start to 0.5 for everything until enough trees exist).
* The ML Worker must be reachable at training time. See [ML Worker Setup](/self-host/deploy/ml-worker) to deploy it. If `ML_WORKER_URL` is unset, GBM training fails with an "ML worker unavailable" error and the model stays in its previous state.

**Troubleshooting:**

* **ML Worker unreachable:** Verify `ML_WORKER_URL` points to a running worker and that `curl $ML_WORKER_URL/health` returns `status: ok`. The platform exposes `GET /api/v1/ml-worker/health` as a probe.
* **All scores are 0.5:** The model has no trained trees yet. Run Train to kick off LightGBM training.
* **Low AUC on training:** Increase `nEstimators`, decrease `learningRate`, or collect more labeled interactions.

**Pros:** Best accuracy on non-linear structured data, captures feature interactions automatically, handles missing values natively, in-process scoring.
**Cons:** Requires the ML Worker for training, needs 5,000+ interactions, per-tree path contributions are a lightweight SHAP-inspired approximation rather than true SHAP.

**Optional preprocessing (WoE binning + target encoding).** Set `config.preprocessing.enabled = true` and the platform fits Weight-of-Evidence bins and categorical target encodings on the labeled training set (`fitPreprocessing`), ships them to the ML Worker alongside the training payload, and the Python trainer applies the same encoders before LightGBM sees the features (echoed back in the response's `preprocessing_used` list). It is **off by default** so existing trains are unchanged — opt in per model when categorical features or monotonic binning help.

***

### Thompson Bandit

A Thompson Sampling multi-armed bandit using Beta-distributed arms. Each offer is an arm with `alpha` (successes + 1) and `beta` (failures + 1). At scoring time, the engine draws from each arm's Beta distribution — arms with higher expected reward win more often, but uncertain arms still get explored.

| Field        | Type                           | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------ | ------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `priorAlpha` | number                         | 1         | Initial alpha for all arms                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `priorBeta`  | number                         | 1         | Initial beta for all arms                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `minSamples` | number                         | 10        | Minimum observations before exploitation                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `armScope`   | `"offer"` \| `"offer_channel"` | `"offer"` | Arm granularity. `"offer"` keys one arm per offer (stats shared across channels). `"offer_channel"` keys arms `<offerId>:<channelId>` so the bandit learns channel-specific conversion stats — offer A on email accumulates separately from offer A on web. Applies to scoring, per-outcome `/respond` updates, and batch retrain replay. When no channel is available for an observation, it falls back to the bare offer arm (signal is never dropped). |

**Pros:** Automatic explore/exploit balance, no feature engineering, Bayesian uncertainty.
**Cons:** Stochastic scores (different each request), does not use customer features directly.

***

### Epsilon-Greedy

A simpler bandit that exploits the best-known arm with probability `1 - epsilon` and explores randomly with probability `epsilon`. Epsilon decays over time.

| Field        | Type                           | Default   | Description                                                                                                                                              |
| ------------ | ------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `epsilon`    | number                         | 0.1       | Base exploration probability                                                                                                                             |
| `decayRate`  | number                         | 0.01      | Decay: `epsilon_t = epsilon / (1 + decayRate * totalPulls)`                                                                                              |
| `minEpsilon` | number                         | 0.01      | Floor for exploration                                                                                                                                    |
| `armScope`   | `"offer"` \| `"offer_channel"` | `"offer"` | Arm granularity — same semantics as Thompson Bandit's `armScope`: `"offer_channel"` keys arms `<offerId>:<channelId>` for channel-specific reward stats. |

Unpulled arms receive an optimistic score of 1.0 to encourage initial exploration.

**Pros:** Simple, deterministic during exploitation, easy to tune.
**Cons:** Less sample-efficient than Thompson, no uncertainty modeling.

***

### Neural Collaborative Filtering

A two-tower embedding model with an MLP head. Customer and offer each get a learned embedding vector. At scoring time, embeddings are concatenated and passed through a hidden layer (ReLU) and output layer (sigmoid).

**Architecture:** `user_embedding + item_embedding -> hidden (ReLU) -> output (sigmoid)`

| Field                          | Description                       |
| ------------------------------ | --------------------------------- |
| `userEmbeddings`               | Learned vector per customer       |
| `itemEmbeddings`               | Learned vector per offer          |
| `hiddenWeights` / `hiddenBias` | Hidden layer parameters           |
| `outputWeights` / `outputBias` | Output layer parameters           |
| `embeddingDim`                 | Embedding vector size (default 8) |
| `hiddenDim`                    | Hidden layer size (default 16)    |

Training uses mini-batch SGD with binary cross-entropy loss and Xavier/Glorot initialization.

**Pros:** Captures latent factors, handles sparse interaction matrices.
**Cons:** Requires significant interaction data, cold-start for new users/items (falls back to zero embedding).

***

### Online Learner

A streaming SGD logistic regression model that learns from one example at a time. Each outcome is fed back to the online-learning routine after delivery — no batch training required.

| Field          | Description                           |
| -------------- | ------------------------------------- |
| `weights`      | Feature weights (initialized to zero) |
| `bias`         | Bias term                             |
| `learningRate` | Base learning rate (default 0.01)     |
| `decayRate`    | Learning rate decay (default 0.001)   |
| `step`         | Total updates applied                 |

Effective learning rate: `lr_t = learningRate / (1 + decayRate * step)`.

**Pros:** True real-time learning, no batch jobs, low memory.
**Cons:** Linear model only, sensitive to learning rate, can be noisy.

***

### External Model Endpoint

Call external HTTP prediction endpoints — SageMaker, Vertex AI, Azure ML, MLflow, BentoML, or any HTTP endpoint that returns scores.

| Setting              | Options                                                                 | Description                                 |
| -------------------- | ----------------------------------------------------------------------- | ------------------------------------------- |
| **Scoring mode**     | `batch` (all candidates in one request) or `single` (one call per pair) | Batch is more efficient when supported      |
| **Auth type**        | `api_key`, `bearer`, `aws_sigv4`                                        | AWS SigV4 for SageMaker                     |
| **Response mapping** | Dot-path extraction (e.g., `predictions[0].score`)                      | Pull scores from nested response structures |
| **Timeout**          | Default 200ms                                                           | Falls back to `fallbackScore` on timeout    |

**Example SageMaker config:**

```json theme={null}
{
  "endpointUrl": "https://runtime.sagemaker.us-east-1.amazonaws.com/endpoints/churn-model/invocations",
  "authType": "aws_sigv4",
  "authConfig": { "awsRegion": "us-east-1", "awsService": "sagemaker" },
  "scoringMode": "batch",
  "responseMapping": { "batchScoresPath": "scores", "fallbackScore": 0.5 },
  "timeoutMs": 200
}
```

<Warning>
  External calls add 50--200ms of network latency per recommendation. For latency-critical use cases (under 50ms), use built-in models. Enable response caching to reduce repeated calls for the same customer.
</Warning>

***

### Imported ONNX Model (BYO)

Beyond the nine configurable engines, you can **bring your own model** by uploading an ONNX file to `POST /api/v1/models/import` (admin-only, multipart). The importer persists the model with `modelType: "onnx_imported"` and its ordered `featureNames`, then scores it **in-process** at decision time via a lazily-loaded `onnxruntime-node` session. This type is **injected at import time** — it is not one of the nine configurable `modelType` enum values and cannot be created through the model wizard.

* **Single ONNX file**, 100 MB cap. Larger models offload to a blob store when one is configured; otherwise they store inline in `modelState`.
* **Fail-soft:** if `onnxruntime-node` isn't installed, or a scoring call throws, the engine returns `0.5` with a `degraded` explanation rather than breaking `/recommend`, and the decision sets [`degradedScoring = true`](/api-reference/decision-traces) on its trace (plus a `scoring_model_failures` metric increment) so the fallback is visible per-decision, not just in aggregate. (A malformed model state is the exception — it surfaces loudly.)
* **Not trained in-platform** — the imported bytes *are* the model. Re-import to update it.

***

### Engine Comparison

|                      | Scorecard | Bayesian    | Logistic Regression | Gradient Boosted      | Thompson    | Epsilon-Greedy | Neural CF         | Online Learner | External |
| -------------------- | --------- | ----------- | ------------------- | --------------------- | ----------- | -------------- | ----------------- | -------------- | -------- |
| **Complexity**       | Low       | Medium      | Medium              | High                  | Medium      | Low            | High              | Low            | Varies   |
| **Data needed**      | None      | Historical  | 1,000+              | 5,000+                | Outcomes    | Outcomes       | Rich interactions | Streaming      | External |
| **Interpretability** | High      | Medium      | High                | Medium                | Low         | Medium         | Low               | Medium         | Varies   |
| **Accuracy**         | Good      | Good        | Good                | Best                  | Good        | Good           | Best (latent)     | Good           | Varies   |
| **Learning mode**    | Manual    | Incremental | Scheduled           | Scheduled (ML Worker) | Per-outcome | Per-outcome    | Batch SGD         | Per-outcome    | External |

***

## Explanation Details by Engine

When `explain=true` is passed to the [Recommend API](/api-reference/recommend), each decision includes a `modelExplanation` object with engine-specific details. The structure of the `details` array varies by engine type:

| Engine                  | `modelExplanation.details`                                                           | `confidence` |
| ----------------------- | ------------------------------------------------------------------------------------ | ------------ |
| **Scorecard**           | Per-rule breakdown: `field`, `operator`, `expected` vs `actual`, `matched`, `points` | No           |
| **Bayesian**            | Per-field log-odds contribution: `field`, `contribution`                             | Yes (0--1)   |
| **Logistic Regression** | Per-feature weight contribution: `field`, `contribution`                             | No           |
| **Gradient Boosted**    | Per-feature path contribution (SHAP-inspired approximation): `field`, `contribution` | No           |
| **Thompson Bandit**     | Per-offer Thompson score                                                             | No           |
| **Epsilon-Greedy**      | Per-offer epsilon-greedy score                                                       | No           |
| **Neural CF**           | Not available (embedding-based)                                                      | No           |
| **Online Learner**      | Not available (incremental)                                                          | No           |
| **External Endpoint**   | Depends on external model                                                            | No           |

<Tip>
  Scorecard explanations are the most detailed — every rule evaluation is returned with matched/unmatched status and point contribution. This makes scorecards ideal for regulated industries that require a full audit trail of scoring decisions.
</Tip>

***

## Cold Start and Propensity Smoothing

New offers have no interaction data, so ML scores are unreliable. **Propensity smoothing** blends the model score with a prior estimate until sufficient evidence accumulates:

```
smoothedScore = (modelScore * evidence + startingPropensity * weight) / (evidence + weight)
```

| Interactions | Model Score | Smoothed Score | What Happens                        |
| ------------ | ----------- | -------------- | ----------------------------------- |
| 0            | --          | 0.700          | Pure prior (`offer.priority / 100`) |
| 10           | 0.45        | 0.629          | Prior still dominates               |
| 25           | 0.45        | 0.575          | Equal blend (evidence = weight)     |
| 100          | 0.45        | 0.500          | Model nearly converged              |
| 500          | 0.45        | 0.462          | Effectively only model score        |

*(Example with `priority = 70`, `propensitySmoothingWeight = 25`)*

| Setting                     | Type    | Default | Description                                    |
| --------------------------- | ------- | ------- | ---------------------------------------------- |
| `propensitySmoothingWeight` | integer | 25      | Higher = slower transition from prior to model |

<Tip>
  Set higher (50--100) for extended exploration of new offers. Set lower (5--10) if you have fast feedback loops and trust the model quickly.
</Tip>

***

## Model Maturity Ramp

While propensity smoothing adjusts the *score*, the maturity ramp adjusts the *exposure*. New offers start at just 2% exposure and ramp linearly to 100% as interactions accumulate:

```
exposureProbability = max(0.02, min(1.0, interactions / maturityThreshold))
```

| Interactions | Exposure | Effect                            |
| ------------ | -------- | --------------------------------- |
| 0            | 2%       | Minimal exposure — model is blind |
| 10           | 10%      | Growing confidence                |
| 50           | 50%      | Half of eligible customers        |
| 100+         | 100%     | Full exposure — enough data       |

The ramp uses a deterministic hash of `customerId + offerId + date` so the same customer sees consistent results within a day.

| Setting                  | Type    | Default | Description                             |
| ------------------------ | ------- | ------- | --------------------------------------- |
| `modelMaturityThreshold` | integer | 100     | Interactions required for full exposure |

<Info>
  Smoothing and the maturity ramp work together. Smoothing ensures a new offer's score is reasonable; the ramp ensures it is not shown to everyone until the model has evidence. Together they provide fair but cautious treatment for new offers.
</Info>

***

## Model Resolution Hierarchy

When a Score node executes, the engine resolves which model to use for each candidate via an override priority chain:

```
offer-level override -> category override -> channel override -> default model
```

First match wins. Override resolution happens independently per candidate — two offers in the same request can be scored by different models.

```json theme={null}
{
  "defaultModel": "model_bayesian_v3",
  "overrides": [
    { "scope": "offer",    "key": "offer_premium_cc",  "modelKey": "model_premium_scorecard" },
    { "scope": "category", "key": "cat_loans",         "modelKey": "model_loan_gb" },
    { "scope": "channel",  "key": "chan_email",         "modelKey": "model_email_bayesian" }
  ]
}
```

If the resolved model is unavailable or the circuit breaker is open, the engine falls back to pre-computed propensity scores, then to priority-based scoring as a last resort.

***

## Champion/Challenger Testing

Run a new model against your production model using live traffic with deterministic customer assignment.

### Model Registry

Every model carries an indexed `registryStatus` column that pins it to one
of four lifecycle states:

| Status       | Meaning                                                              |
| ------------ | -------------------------------------------------------------------- |
| `draft`      | training or untested; not reachable from `/recommend`                |
| `challenger` | shadow-scoring only — measured against the active champion           |
| `champion`   | active in `/recommend`; at most one per `(tenantId, registryFamily)` |
| `archived`   | retired; preserved for audit but never scored                        |

Promotions run through the model-registry promotion workflow, which
enforces legal transitions (`draft → challenger → champion → archived → draft`)
and the "one champion per family" invariant in a single transaction.
Every promotion writes an audit-log row keyed by
`entityType=algorithm_model`, `action=registry_promote`, recoverable via
DSAR or compliance review.

<Note>
  **Self-hosters upgrading from a 2026-04-22-or-earlier deployment** must
  add the new columns and the DB-level CHECK constraint:

  ```bash theme={null}
  # 1. Add the columns + indexes (non-destructive)
  npx prisma db push

  # 2. Apply the CHECK constraint (idempotent)
  psql "$DATABASE_URL" -f platform/prisma/manual-sql/01_registry_status_check.sql

  # 3. Backfill values from legacy config.registry JSON
  #    (call backfillRegistryColumns(prisma) once)
  ```

  Existing rows continue to work without the backfill — the read path falls
  back to `config.registry` JSON when the columns are null. Backfill is a
  one-time cleanup, not a hard prerequisite.
</Note>

### How It Works

1. **Configure the split** -- Set weights (e.g., 80/20 champion/challenger)
2. **Deterministic assignment** -- `FNV-1a(customerId + ":cc")` produces a stable hash. Same customer always gets the same model.
3. **Override bypass** -- When enabled, champion/challenger takes precedence over the normal override hierarchy.

```json theme={null}
{
  "championChallenger": {
    "enabled": true,
    "champion": { "modelKey": "model_bayesian_v3", "weight": 80 },
    "challengers": [{ "modelKey": "model_gb_v1", "weight": 20 }]
  }
}
```

### Example: After 14 Days

| Metric          | Champion (Bayesian) | Challenger (Gradient Boosted) |
| --------------- | ------------------- | ----------------------------- |
| Customers       | 800                 | 200                           |
| Conversions     | 96                  | 32                            |
| Conversion rate | 12.0%               | 16.0%                         |
| Uplift          | --                  | +4pp (33.3% relative)         |
| p-value         | --                  | 0.150                         |
| Significant?    | --                  | **No** (need more data)       |

The challenger shows promise but has not reached statistical significance at 95% confidence. The experiment needs more traffic.

***

## Experiments

Experiments wrap champion/challenger testing with holdout groups, traffic management, and statistical analysis.

### Creating an Experiment

```json theme={null}
{
  "key": "q1-rewards-model-test",
  "name": "Q1 Rewards Model Test",
  "championModelId": "model_bayesian_v3",
  "trafficSplit": { "championPct": 80 },
  "challengers": [{ "modelId": "model_gb_v1", "trafficPct": 20 }],
  "autoPromote": false,
  "promoteThreshold": 0.02,
  "promoteAfterDays": 14
}
```

<Warning>
  Traffic split must sum to exactly 100%. The API validates `championPct + sum(challenger trafficPct) = 100` and rejects the request otherwise.
</Warning>

### Uplift Calculation

KaireonAI uses a **two-proportion z-test**:

| Result Field              | Description                               |
| ------------------------- | ----------------------------------------- |
| `treatmentConversionRate` | Conversions / total in treatment group    |
| `holdoutConversionRate`   | Conversions / total in holdout group      |
| `uplift`                  | Absolute difference (treatment - holdout) |
| `relativeUplift`          | Percentage improvement over holdout       |
| `zScore`                  | Test statistic                            |
| `pValue`                  | Two-tailed p-value                        |
| `significant`             | `true` if p-value \< alpha (default 0.05) |

### Power Calculator

Before launching, estimate required sample size and duration given your baseline conversion rate, minimum detectable effect, and daily traffic volume. Returns required sample size per variant (80% power, 95% confidence) and estimated duration in days.

### Auto-Promotion

<Note>
  Auto-promote is **disabled by default**. The system provides uplift magnitude, p-value, and confidence intervals, but the final promotion decision is left to the operator. Enable `autoPromote: true` only when you have guardrail checks and are comfortable with automated rollouts.
</Note>

When enabled, auto-promotion triggers when: (1) experiment has run for at least `promoteAfterDays`, (2) challenger exceeds champion by at least `promoteThreshold`, and (3) result is statistically significant at 95% confidence.

***

## Model Lifecycle

<Steps>
  <Step title="Create">
    Define model with key, name, engine type, and engine-specific config. Starts in `draft` status.
  </Step>

  <Step title="Configure">
    Set up predictors (feature fields), target field, and engine settings.
  </Step>

  <Step title="Train">
    Kick off training (requires 50+ interaction records for data-driven engines).
  </Step>

  <Step title="Evaluate">
    Review accuracy, precision, recall, F1, AUC. Compare against previous versions.
  </Step>

  <Step title="Promote">
    Set to `active` to make available for Decision Flows.
  </Step>

  <Step title="Monitor">
    Track in [Model Health Dashboard](/operations-reporting/dashboards#model-health-dashboard). Scheduled drift checks auto-enqueue retraining when performance degrades.
  </Step>
</Steps>

### Auto-Learning Modes

| Mode            | Engines                                  | How It Works                                                                        |
| --------------- | ---------------------------------------- | ----------------------------------------------------------------------------------- |
| **Incremental** | Bayesian                                 | Outcomes buffered in Redis (50-event threshold), batch-updated without full retrain |
| **Scheduled**   | Logistic Regression, Gradient Boosted    | Cron schedule (e.g., `24h`, `7d`) triggers periodic retraining                      |
| **Per-outcome** | Thompson, Epsilon-Greedy, Online Learner | Model updates immediately after each outcome                                        |

### Auto-Upgrade Recommendations

| Current Engine      | Upgrade To          | Trigger                                                         |
| ------------------- | ------------------- | --------------------------------------------------------------- |
| Scorecard           | Bayesian            | More than 5 rules AND more than 100 training samples            |
| Bayesian            | Logistic Regression | More than 1,000 samples AND AUC below 0.85                      |
| Logistic Regression | Gradient Boosted    | More than 5,000 samples AND AUC below 0.90 (requires ML Worker) |

***

## Field Reference

### Algorithm Model

| Field                 | Type   | Required | Default   | Description                                                                                                                                                                                                                                                                                                                 |
| --------------------- | ------ | -------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`                 | string | Yes      | --        | Unique identifier (1--255 chars)                                                                                                                                                                                                                                                                                            |
| `name`                | string | Yes      | --        | Human-readable name                                                                                                                                                                                                                                                                                                         |
| `description`         | string | No       | `""`      | Optional description                                                                                                                                                                                                                                                                                                        |
| `modelType`           | enum   | Yes      | --        | `scorecard`, `bayesian`, `logistic_regression`, `gradient_boosted`, `thompson_bandit`, `epsilon_greedy`, `neural_cf`, `online_learner`, `external_endpoint`. `onnx_imported` is also valid but is set only by the [ONNX import endpoint](#imported-onnx-model-byo), not through this configurable enum.                     |
| `status`              | enum   | No       | `"draft"` | `draft`, `training`, `active`, `paused`, `archived`, `error`                                                                                                                                                                                                                                                                |
| `config`              | object | No       | `{}`      | Engine-specific configuration                                                                                                                                                                                                                                                                                               |
| `targetField`         | string | No       | `""`      | Field being predicted                                                                                                                                                                                                                                                                                                       |
| `targetSchemaKey`     | string | No       | `""`      | Schema key for target field                                                                                                                                                                                                                                                                                                 |
| `predictors`          | array  | No       | `[]`      | `{ field, schemaKey, importance, bins, selected }`                                                                                                                                                                                                                                                                          |
| `metrics`             | object | No       | `{}`      | Latest evaluation metrics                                                                                                                                                                                                                                                                                                   |
| `metricsHistory`      | array  | No       | `[]`      | Version-over-version comparison                                                                                                                                                                                                                                                                                             |
| `modelState`          | object | No       | `{}`      | Learned parameters                                                                                                                                                                                                                                                                                                          |
| `learningConfig`      | object | No       | `{}`      | **Reserved metadata — not read by any trainer.** Accepted and persisted, but no code path reads it. Auto-learn behavior is actually driven by the model's `autoLearn`/`learnMode`/`learnSchedule` fields (see [Algorithm Models API](/api-reference/algorithm-models)), and per-modelType hyperparameters live in `config`. |
| `outcomeWeights`      | object | No       | `null`    | Outcome type weights for blended scoring                                                                                                                                                                                                                                                                                    |
| `interactionFeatures` | object | No       | `null`    | Interaction features to extract                                                                                                                                                                                                                                                                                             |
| `evolutionConfig`     | object | No       | `null`    | Auto-upgrade thresholds                                                                                                                                                                                                                                                                                                     |

### Experiment

| Field                      | Type    | Required | Default | Description                   |
| -------------------------- | ------- | -------- | ------- | ----------------------------- |
| `key`                      | string  | Yes      | --      | Unique identifier             |
| `name`                     | string  | Yes      | --      | Human-readable name           |
| `championModelId`          | string  | No       | `null`  | Champion model ID             |
| `trafficSplit.championPct` | number  | No       | 80      | Champion traffic percentage   |
| `challengers[].modelId`    | string  | Yes      | --      | Challenger model ID           |
| `challengers[].trafficPct` | number  | No       | 10      | Challenger traffic percentage |
| `autoPromote`              | boolean | No       | `false` | Auto-promote on win           |
| `promoteThreshold`         | number  | No       | 0.02    | Minimum uplift (2pp)          |
| `promoteAfterDays`         | number  | No       | 14      | Minimum experiment duration   |

***

## API Quick Reference

```bash theme={null}
POST   /api/v1/algorithm-models           # Create a model
POST   /api/v1/algorithm-models/{id}/train # Train from interaction data
POST   /api/v1/algorithm-models/{id}/score # Score offers for a customer
DELETE /api/v1/algorithm-models?id={id}    # Delete permanently
```

### Score Request Example

```json theme={null}
{
  "customerAttributes": { "income": 75000, "reward_tier": "gold" },
  "offers": [
    { "id": "offer_bogo", "attributes": { "discount_pct": 50 } },
    { "id": "offer_stars", "attributes": { "multiplier": 3 } }
  ]
}
```

Returns per-offer scores sorted by propensity.

<Info>
  For full API reference with request/response schemas and error codes, see the [Algorithm Models API Reference](/api-reference/algorithm-models).
</Info>

***

## Worked Example: Three Engines Score the Same Offer

A retail rewards member (`income: 75000`, `reward_tier: gold`, `visit_frequency: 4/week`) is evaluated for a BOGO beverage offer:

| Engine                  | How It Scores                                                                                                                | Result    |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------- |
| **Scorecard**           | `baseScore=50` + 20 (gold tier) + 15 (high frequency) = 85 points. Sigmoid normalization.                                    | **0.818** |
| **Bayesian**            | 500 positive, 300 negative outcomes. Log-likelihood ratios: `reward_tier` +0.31, `visit_frequency` +0.18.                    | **0.724** |
| **Logistic Regression** | Learned weights: `visit_frequency=0.42`, `reward_tier=0.38`, `income=0.15`. Sigmoid of weighted sum.                         | **0.754** |
| **Gradient Boosted**    | 100-tree LightGBM ensemble; sum of leaf values routed through sigmoid; captures `reward_tier × visit_frequency` interaction. | **0.812** |

All four produce a 0--1 score, but arrive at it differently. The scorecard is transparent and manual. The Bayesian adapts from data while remaining interpretable. Logistic regression adds learned linear weights. The gradient boosted ensemble captures non-linear interactions between features — at the cost of needing the ML Worker for training.

***

## Related

<CardGroup cols={3}>
  <Card title="Decision Flows" icon="sitemap" href="/decisioning/decision-flows">
    See how models plug into the Score stage.
  </Card>

  <Card title="Composable Pipeline" icon="diagram-project" href="/data/transforms/composable-pipeline">
    The `score` node uses the same scoring resolver.
  </Card>

  <Card title="Dashboards" icon="chart-line" href="/operations-reporting/dashboards">
    Monitor model health, drift, and experiment results.
  </Card>
</CardGroup>
