What must a rules-plus-model decision layer record so a support agent can explain a denied refund months later?
answer
- written at decision time, never reconstructed
- which rules fired, which branch won
- both versions: rule set and model
- codes from a fixed vocabulary
- keep the inputs the conditions read
basics
~20 sThe emitted action and its reason codes, every rule that matched with each rule's version, the model version and score band, which branch resolved the decision, and the input values it actually used - all written at decision time.
solid answer
~50 sWrite the record on the decision path, not by reconstruction afterwards. It carries the request and account identifiers, the rule-set version and every rule that matched, the model version and its score or band, which branch produced the action, the emitted action itself, and a small set of **reason codes**. Reason codes come from a fixed, curated vocabulary that is mapped separately to customer-facing wording, so what an agent reads and what a customer is told can both change without touching the decision logic. A matched rule supplies its reason for free - it is a named condition. A score does not supply one in specific terms; at best it contributes a band and a few top contributing inputs, which is why decisions that must be explained precisely are usually carried by a rule rather than by the band.
code
json · 16 lines{
"decisionId": "d-8f31c2",
"emittedAt": "2026-09-03T11:42:07Z",
"accountId": "a-55120",
"action": "DECLINE_REFUND",
"resolvedBy": "rule",
"ruleSetVersion": "rs-2026-09-01.4",
"rulesFired": [
{ "id": "promo_reuse_linked_devices", "version": 4, "action": "DECLINE_REFUND", "precedence": 20 },
{ "id": "new_account_refund_velocity", "version": 2, "action": "CHALLENGE", "precedence": 60 }
],
"modelVersion": "risk-2026-08-02",
"scoreBand": "medium",
"reasonCodes": ["PROMO_REUSE", "LINKED_ACCOUNTS"],
"inputs": { "accountAgeHours": 9, "promoRedemptions30d": 6, "refundsRequested30d": 3, "linkedDevices": 4 }
}go deeper
Recall what a decision record holds: the action taken, the rules that matched, the model version and score band, and reason codes drawn from a fixed list rather than free text.
Explain why the record is written on the decision path instead of reconstructed - moved rule versions, a replaced model version, time-dependent counters that cannot be recovered - and how reason codes map to customer wording separately.
Show the judgment: a content-derived rule-set version so records cannot point at edited text, the model band kept even when a rule won so disagreement is visible, and an explanation granularity that is true to the reviewer without being a recipe for whoever is probing.
Decide what the business commits to being able to explain, and to whom, since that commitment is what sets the record's contents and its retention. Weigh the cost of writing it on every decision against the cost of a dispute you cannot answer.
## Two audiences, one record When a customer disputes a denied refund weeks later, two different people need something from the same event. The **support agent or case reviewer** needs to decide whether the decision was right: they want the specific rule, its version, the score band and the input values the layer saw. The **customer** gets a deliberately coarser sentence - both because the exact condition is not useful to them and because naming the precise constant that fired tells anyone probing the system exactly what to stay under. One record serves both, because the coarse wording is derived from the reason codes rather than stored separately. ## What the record holds - **Identifiers** - a decision id, the account, the request, and the timestamp the decision was emitted. - **The emitted action** - allow, challenge, decline, route to human case review - and which branch resolved it: a rule, the model's band, or the default. - **The rule side** - the rule-set version, and for every rule that matched: its id, its version, the action it proposed and its precedence. - **The model side** - the model version and the score or its band, recorded even when a rule won the resolution, because 'the rule declined a request the score thought was fine' is the most useful thing a reviewer can learn. - **The inputs that mattered** - the handful of values the conditions read: account age, redemptions in the last 30 days, refunds requested, linked-device count. A reviewer cannot judge a decision from the verdict alone. - **Reason codes** - a short list from a fixed vocabulary. ## Reason codes are a vocabulary, not free text If every rule author writes their own sentence, the reason field becomes prose that cannot be counted, translated, or mapped to a customer message. A curated code set fixes that: | Property | What it buys | |---|---| | Fixed and enumerated | Decisions can be counted by reason, so a spike in one is visible | | Mapped to wording separately | Customer phrasing and agent phrasing change without a decision-logic change | | Coarser than the rule id | The exact condition stays internal while the reason is still true | | Many-to-one from rules | Several rules can share a code, and the record keeps the rule id anyway | ## Why a blended score resists explanation A matched rule is self-explaining: it is a named condition with a version, and saying it fired is a complete account of why. A score is not. It is a function of many inputs at once, and the honest statement is 'this request landed in the high band', optionally with the inputs that contributed most. That is an explanation of a *kind*, not of a *case*. The consequence shows up in design: where a decision must be explained in specific terms, carry it with a rule rather than with a band. This is one of the quieter reasons the rules layer survives after a model is added - not accuracy, but attributability. And when rule contributions and the model score are fused into a single number, even the rule's own attributability is blurred, which is an argument for keeping the rule's hit in the record separately from the fused score. ## Write it at decision time, not later The tempting shortcut is to store only the action and reconstruct the rest by re-running the rules when somebody asks. It does not work, for three independent reasons: 1. **The rule set has moved.** Rules were edited, retired, re-ranked. Re-running today answers 'what would we decide now', which is a different question from 'why was this refund denied on the 3rd'. 2. **The model version has moved.** The score that decided is not the score a current model produces on the same inputs. 3. **The inputs are gone.** Velocity counters are time-dependent by construction; their value at that instant is not recoverable from today's aggregates, and a value reconstructed from a later state is simply a different number. Two practices make the record trustworthy. Give the rule set a **version identifier derived from its content** - a content hash over the published rule text - so a record can never point at a rule-set version that was quietly edited underneath it. And write the record on the decision path itself, as part of emitting the action, rather than assembling it from separate log lines afterwards, so a decision can never exist without its explanation.
- Why can a past decision not be reliably reconstructed by re-running the rules?The rule set, the model version and the input values have all moved. Velocity counters are time-dependent, so their reading at that instant is not recoverable from today's aggregates, and rules may have been edited or retired since. Re-running answers what the layer would decide now, which is a different question from why that refund was denied on that day.
- What does a case reviewer need that the customer-facing message should not carry?The reviewer needs the rule id and version, the score band and the input values, because they are judging whether the decision was correct. The customer message is deliberately coarser: stating the exact condition and its constant tells whoever is probing the system precisely what to stay under, so the reason code maps to wording that is true without being a recipe.
- Why record the model's score even when a rule resolved the decision?Because the disagreement is the signal. A rule declining requests the score consistently bands as low risk is either covering something the model misses - its justification - or over-blocking, and you cannot tell which without the band on the record. It also makes it possible to ask later what the score-only decision layer would have done.
saying these in an interview costs you the question
- The score is the explanation, so show the customer the number
- We can reconstruct any past decision by re-running the rules
- Free-text reasons written per rule author are good enough
- Logging the emitted action is enough, the inputs are in upstream tables
- Only the winning rule matters, so other matches need not be recorded