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

# Per-category PRIE split

> Route Cards candidates through one scoring profile and Loans through another, in the same flow, against the same customer. The proof-bundle T29 same-customer A/B verified this end to end.

## What this solves

Different product categories often want different ranking priorities. Cards offers are usually conversion-driven (push the highest-converting card to the top). Loans offers in regulated markets need fairness-led ranking (every qualified borrower sees an offer, regardless of model preferences). Without per-category overrides, you'd need a separate flow per category and split traffic at the router — operationally painful and breaks Hungarian uniqueness across the slot set.

## Why this works

The Score node's `strategyOverrides[]` array lets you map a candidate's category (or productType, or channelId) to a specific `RankingProfile`. The PRIE engine reads the profile's `{conversion, recency, margin, fairness}` weights, maps them to `{Wp, Wr, Wi, We}`, and recomputes the geometric-mean score per candidate. Same flow execution, three different weight regimes if you want three.

## The pattern

Three ingredients:

1. A flow whose Score node has `method: "formula"` and a default `strategyProfileId`.
2. Two or more `RankingProfile` rows with the weight regimes you want.
3. `strategyOverrides[]` on the Score node mapping `scope: "category"` (or `"productType"` or `"channel"`) + `value` (the category name, exactly as it appears on the Offer) → the profile id.

## Step 1 — Create the ranking profiles

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/ranking-profiles \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  --cookie "<your session cookie>" \
  -d '{
    "name": "Cards Conversion-Heavy",
    "weights": { "conversion": 0.6, "margin": 0.25, "recency": 0.025, "fairness": 0.025, "fatigue": 0.1 }
  }'

curl -X POST https://playground.kaireonai.com/api/v1/ranking-profiles \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  --cookie "<your session cookie>" \
  -d '{
    "name": "Loans Priority-Led",
    "weights": { "fairness": 0.65, "conversion": 0.1, "margin": 0.1, "recency": 0.05, "fatigue": 0.1 }
  }'
```

Note the IDs returned — call them `CARDS_PROFILE_ID` and `LOANS_PROFILE_ID` for the next step.

## Step 2 — Configure the flow's Score node

PUT the decision flow's `draftConfig`, targeting the Score node:

```json theme={null}
{
  "id": "score",
  "type": "score",
  "config": {
    "method": "formula",
    "modelKey": "bayesian-v2",
    "strategyProfileId": "CARDS_PROFILE_ID",
    "strategyOverrides": [
      { "scope": "category", "value": "Loans", "profileId": "LOANS_PROFILE_ID" }
    ]
  }
}
```

Then `POST /api/v1/decision-flows/publish` to ship it.

## Step 3 — Verify

Fire the same recommend against the same customer twice — once with the override array empty (control), once with the override above. You should see Mortgage's score drop by a known delta while Cashback's stays constant (or moves only when you flip the Cards default).

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/recommend \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  -d '{
    "customerId": "split-X-007",
    "decisionFlowKey": "your-flow-key",
    "attributes": { "tier": "platinum", "loanAmount": 250000, "balance": 5000 }
  }'
```

## What the trace will show

```
customer        | offer            | category | score   | profile applied
split-X-007     | Mortgage Refi    | Loans    | 0.8363  | Loans Priority-Led
split-X-007     | Cashback Card    | Cards    | 0.8619  | Cards Conversion-Heavy (default)
```

Look at `scoringResults[*].explanations[]` on the decision trace — when a strategy override fires, the explanation row records which profile was applied.

## Gotchas

* **Use the category NAME, not the UUID.** The engine matches on `offer.categoryRef.name`. Passing the category id silently no-ops; the engine falls back to the default profile.
* **Same-customer A/B is the cleanest comparison.** The maturity ramp's per-customer deterministic-random roll means two new customers comparing the same offer can see different drop patterns. Use one customer across both stages.
* **Override priority is array order.** First match wins; later entries don't merge. If you want a fallback, put it last with the broadest scope.

## Proof reference

This pattern is exercised in T29 of the [proof bundle](https://github.com/kaireonai/platform/blob/main/PROOF_BUNDLE.html) — same customer across three weight regimes, with verbatim score deltas captured from live engine responses.
