Skip to main content

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 weights and maps them to the PRIE exponents: conversion → Wp (propensity), recency → Wr (relevance), margin → Wi (impact), fairness → We (emphasis), with optional uplift and clv exponent terms (default 0). It then recomputes the geometric-mean score per candidate. Same flow execution, three different weight regimes if you want three. Any other keys in weights (e.g. fatigue) are stored but ignored by the scoring math.

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

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:
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 shift by a known delta (its Loans candidate now scored under the fairness-led weights) while Cashback’s stays constant (it never leaves the Cards default).

What the trace will show

The override is not stamped by name on the trace — the engine resolves the profile per candidate and applies its weights silently. What you read off the trace is the score: run the recommend once with the override array empty, once with it populated, and the Loans candidate’s score shifts under the new weight regime while the Cards candidate (still on the default profile) stays put.
Read the per-offer score off scoringResults[] (top-10 scored candidates, with modelType and any SHAP explanations) or selectedOffers[] on the decision trace. The applied ranking profile itself is resolved at scoring time and is not persisted as a trace field.

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 internal proof bundle — same customer across three weight regimes, with verbatim score deltas captured from live engine responses.