Zum Hauptinhalt springen

Recommendation Hint Catalog (POS v2)

Wire shape: EvaluateResponseV2.recommendations[].params: many KeyValuePair.

The v2 evaluate response surfaces near-miss + bug-fix hints as structured triples — { code, params, defaultMessage } — so POS systems can render each hint in the cashier's locale without round-tripping English template strings. POS teams ship one i18n bundle per locale keyed by code; the engine is locale-agnostic.

This catalog is the versioned contract between engine and POS:

  • Codes are permanent: once a code is shipped, it is never repurposed.
  • New codes are additive and bump EvaluateResponseV2.minorVersion.
  • A POS that does not recognise a code MUST fall back to defaultMessage.

The 13 codes

codeRequired params keysEnglish defaultMessage template
MISSING_ARTICLEarticleNumber (or articleListId)Add article {articleNumber} to qualify for this promotion.
MISSING_QUANTITY_IN_GROUParticleGroupId, missingQuantity, thresholdQuantityAdd {missingQuantity} more item(s) from group {articleGroupId} to reach the threshold of {thresholdQuantity}.
BELOW_MIN_QUANTITYarticleNumber, currentQuantity, requiredQuantityAdd {requiredQuantity, plural, =1 {1 unit} other {# units}} of article {articleNumber} (currently {currentQuantity}).
BELOW_RECEIPT_THRESHOLDcurrentAmount, requiredAmount, currencySpend {requiredAmount} {currency} (currently {currentAmount}) to qualify.
CUSTOMER_GROUP_REQUIREDcustomerGroupThis promotion is reserved for customer group {customerGroup}.
CUSTOMER_REQUIREDcustomerIdThis promotion requires a customer to be identified at checkout.
LOYALTY_TIER_REQUIREDrequiredTier, currentTierLoyalty tier {requiredTier} required (current tier: {currentTier}).
PAYMENT_MEANS_REQUIREDrequiredMeansPay with {requiredMeans} to qualify for this promotion.
MANUFACTURER_REQUIREDmanufacturerIdAdd an item from manufacturer {manufacturerId} to qualify.
MANUAL_DISCOUNT_REQUIRED(none — boolean)A manual discount must be applied to qualify for this promotion.
TIME_WINDOW_INVALIDvalidFrom, validUntilThis promotion is valid from {validFrom} to {validUntil}.
CUSTOM_FIELD_REQUIREDfieldKey, expectedValueField {fieldKey} must equal {expectedValue} to qualify.
CHANNEL_REQUIREDrequiredChannelThis promotion is available on channel {requiredChannel}.

The English templates use ICU MessageFormat-style placeholders. Locale bundles MAY use ICU directly or any other format library — the engine does not interpret them; it only ships them as fallback strings.

Wire encoding

On the wire, params is many KeyValuePair. Each entry is { key, value }, both strings. Numeric and boolean values arrive as their string representation — POS coerces per the documented contract for each code.

Example wire payload:

{
"kind": "NEAR_MISS",
"code": "BELOW_RECEIPT_THRESHOLD",
"params": [
{ "key": "currentAmount", "value": "42.50" },
{ "key": "requiredAmount", "value": "50.00" },
{ "key": "currency", "value": "EUR" }
],
"defaultMessage": "Spend {requiredAmount} {currency} (currently {currentAmount}) to qualify."
}

POS-side rendering

function renderRecommendation(rec, i18n) {
// 1. Decode params back into a plain object.
const data = Object.fromEntries(
rec.params.map(p => [p.key, p.value])
);

// 2. Coerce per-code typed values. Per the contract for
// BELOW_RECEIPT_THRESHOLD, currentAmount and requiredAmount are decimals.
if (rec.code === 'BELOW_RECEIPT_THRESHOLD') {
data.currentAmount = parseFloat(data.currentAmount);
data.requiredAmount = parseFloat(data.requiredAmount);
}

// 3. Look up locale bundle, fall through to defaultMessage.
const tpl = i18n.t(`hint.${rec.code}`) || rec.defaultMessage;
return formatMessage(tpl, data);
}

Every POS adapter MUST implement the fallback to defaultMessage so an unknown code (forward-compat) still surfaces a usable string.

Versioning rules

  • Adding a new code is additive → bump EvaluateResponseV2.minorVersion.
  • Removing a code is a breaking change → requires a major version bump AND a deprecation cycle (out of scope for the v2 release).
  • Renaming a code is forbidden — codes are user-facing contract.
  • Changing the required params keys on an existing code is breaking — add a new code with the extended params instead.
  • The English defaultMessage template MAY be reworded for clarity in any release; POS bundles drive the user-visible copy.

See also