What DecisionOps is
Link to section “What DecisionOps is”DecisionOps in 30 seconds
Link to section “DecisionOps in 30 seconds”DecisionOps is the work of running automated decisions the way good teams run software. A decision here is a repeated operational choice made by a system: approve, refer or decline a refund; which driver gets which delivery; which offer a customer sees; whether a payment is blocked. Its logic usually mixes business rules, model scores and sometimes optimisation. DecisionOps gives that logic an owner, a version, tests, a controlled release, a log of every decision made, and a loop that compares decisions with their outcomes so the logic improves.
The idea in one line: the model predicts; the decision acts; DecisionOps operates the act. A fraud model that is 2% better is worth nothing if the threshold that turns its score into “block” is edited by hand in production and nobody can say which version blocked a customer last Tuesday.
Where to start. Beginners: the glossary and Lab 1. Engineers: the reference architecture and the testing ladder. Leaders: the decision framework and the judgement call. Last verified 2026-10-23.
At a glance
Link to section “At a glance”| Aspect | Summary |
|---|---|
| Goal | Automated decisions that are correct by construction, testable before release, explainable and contestable afterwards, and better over time |
| People | Decision owners (business), rules analysts, decision scientists, optimisation engineers, platform engineers, reviewers, risk and compliance |
| In scope | Decision inventory; decision logic (rules, DMN, thresholds, strategies, optimisation); combining rules with model scores; human referral; testing (replay, shadow, champion/challenger, switchback); release and rollback; decision logs and audit; outcome measurement |
| Out of scope | Training models → MLOps; approving models and rule sets as models → ModelOps; metric definitions → AnalyticsOps; human leadership decisions → BizOps; risk appetite → RiskOps; AML and fraud policy → FinCrimeOps |
| Prerequisites | Basic statistics; helps: MLOps or AnalyticsOps |
| Key outputs | A decision inventory, versioned decision logic with tests, a release record per change, a decision log, a monthly decision scorecard |
| Term status | Vendor-specific label (note above); the practice is established under other names |
The problem it solves
Link to section “The problem it solves”Organisations automate thousands of small decisions a day and rarely treat them as a product. The publisher’s description of Taylor and Raden’s 2007 book on enterprise decision management says many business decisions “tend to be hidden in systems that make poor decisions, or don’t make them at all”.[11] Hidden decisions fail in recognisable ways:
- Logic nobody can see whole. A threshold lives in a config file, an exclusion in a stored procedure, a “temporary” override in a spreadsheet. Nobody can say what the policy is, only what the code does.
- Changes nobody can test. A rules analyst edits a cut-off; it goes live at once; the effect shows up weeks later in losses or complaints. Stripe’s documentation warns that a rule “can automatically allow a large number of elevated or high-risk payments or block a large number of legitimate payments, if it isn’t set up properly”.[14]
- Decisions nobody can explain. A customer complains; nobody can reconstruct which version, inputs and rule produced the outcome. In US credit, reasons for adverse action must be specific; “failed to achieve a qualifying score” is not enough.[15]
- Outcomes nobody joins back. Decisions sit in one system, outcomes arrive months later in another.
- Dead logic that wakes up. In 2012 Knight Capital’s new trading code reused a flag that once switched on retired “Power Peg” code; one of eight servers missed the deployment, and the loss ultimately exceeded $460 million.[16] ReleaseOps tells the case in full.
| Operating reality | Without DecisionOps | With DecisionOps |
|---|---|---|
| What the policy is | Spread across code, config and people | One versioned decision definition per decision |
| A change | Edited live | Reviewed, replayed, shadowed, released to a share of traffic |
| A complaint | Hours of archaeology | One decision record: inputs, version, rule path, reviewer |
| Learning | Opinions in a meeting | Champion/challenger results joined to outcomes |
What DecisionOps is not: boundaries and naming
Link to section “What DecisionOps is not: boundaries and naming”- Not MLOps for rules. MLOps produces reliable scores; DecisionOps decides what to do with them, together with policy rules, costs and capacity.
- Not ModelOps. Gartner’s ModelOps definition covers “decision models” including rules and optimisation.[17] This Atlas’s editorial distinction (not an industry rule): ModelOps governs models and rule sets (inventory, tier, approval); DecisionOps runs the decision logic built from them (authoring, testing, release, logging, measurement).
- Not “decision intelligence” by another name. Gartner’s decision intelligence covers how decisions are made and “how outcomes are evaluated, managed and improved via feedback”, human or automated;[13] DecisionOps is the slice run by software. Nor is it a product: a decision list, a rules engine or plain code, a log table and discipline are enough.
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”The pieces are older than the word. A Fair Isaac patent filed in 2000 describes champion/challenger testing: every account gets a two-digit “random digit”, a challenger strategy runs on a slice such as 10% of a portfolio, and a winner is rolled out “as a new champion”.[18] Taylor and Raden argued for enterprise decision management in 2007.[11] OMG published DMN 1.0 in September 2015 and 1.6 in September 2026.[12] Gartner’s Critical Capabilities report for Decision Intelligence Platforms is dated 27 January 2026, as FICO reports.[19] The label DecisionOps came from optimisation: Nextmv’s 1.0 launch (2023) asked “So what if we treated decisions as code?”[2]
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning on this page |
|---|---|
| Decision | DMN: “the act of determining an output value (the chosen option), from a number of input values, using logic”.[20] Here: a repeated, automated or automation-assisted choice |
| Decision requirements graph (DRG) | DMN’s network of decisions, input data and knowledge that each decision requires[20] |
| Decision table and hit policy | Rules in rows; the hit policy says what happens when several match (Unique, First, Priority, Collect…)[20] |
| Decision service | A callable unit that evaluates a set of decisions; DMN requires it to be stateless with no side effects[20] |
| Strategy | A complete version of decision logic for one decision |
| Champion / challenger | The current strategy and a candidate tested against it on a random share of cases[18] |
| Replay (backtest) | Running stored past inputs through a new version offline and comparing outputs |
| Shadow | Running the candidate on live inputs without acting on its output[21] |
| Switchback test | Randomising versions over time windows and regions, not individual units, when units interact[22] |
| Referral / override | Handing a case to a person; a person changing the automated outcome |
| Decision log | One immutable record per decision: inputs, version, path, output, assignment, reviewer, later outcome |
| Outcome window | Time until the outcome is known (minutes for a click, months for a loan default) |
| Selective labels | Outcomes observed only for cases the old logic let through[23] |
| Propensity | The probability with which a randomised assignment chose this version or action[24] |
Mental model: a decision is a function with a contract, a version and a memory. The contract fixes inputs and allowed outputs (including “refer to a person”); the version says which logic ran; the memory is the decision log, later joined to the outcome. DecisionOps keeps all three true while the logic changes weekly.
How DecisionOps relates to other Ops
Link to section “How DecisionOps relates to other Ops”| Neighbour | Shared ground | The neighbour owns | DecisionOps owns |
|---|---|---|---|
| MLOps | Scores used in decisions | Training, serving and monitoring reliable scores | Thresholds, strategies and rules that turn scores into actions |
| ModelOps | Rules and models in production | Inventory, tiering, approval of models and rule sets | Authoring, testing, releasing and logging the decision logic |
| AnalyticsOps | Metrics | Governed metric definitions | Decision-level metrics that use them |
| BizOps | “Decisions” | Human leadership decisions and their decision log | Automated decision logic and its audit |
| RiskOps | Risk appetite | Appetite and acceptance | Encoding limits (exposure caps, referral bands) in logic |
| FinCrimeOps | Fraud and AML rules | Domain policy and investigations | The engine, testing and release of those rules |
| LLMOps | LLMs and agents in decisions | Evaluations, guardrails, traces | The decision contract and log around an LLM step |
| ReleaseOps | Progressive rollout | Release strategy for software | Release of decision versions, which may change without a code deploy |
Also: DataOps supplies the input data; PrivacyOps the lawful basis for using it; ComplianceOps collects audit evidence; KnowledgeOps maintains the policy documents rules come from.
Diagram source (Mermaid)
flowchart TB IN["Inputs: MLOps scores, DataOps data, AnalyticsOps metric definitions"] BZ["BizOps: policy intent from leaders"] DO["DecisionOps: logic, tests, release, decision log, outcomes"] ACT["Actions in business systems"] GOV["ModelOps and RiskOps: approvals and limits"] IN --> DO BZ -.->|"intent"| DO DO --> ACT DO <-.->|"approval, limits"| GOV ACT -->|"outcomes, overrides"| DO classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 class DO core class IN,BZ,GOV neutral class ACT decision
Text equivalent: Inputs flow into DecisionOps: model scores from MLOps, input data from DataOps and metric definitions from AnalyticsOps; BizOps supplies policy intent from leaders. DecisionOps owns the decision logic, its tests and release, the decision log and outcome measurement, and produces actions in business systems. ModelOps and RiskOps exchange approvals and limits with it. Outcomes and human overrides flow back from the business systems to DecisionOps.
Principles and operating model
Link to section “Principles and operating model”Charter and principles
Link to section “Charter and principles”No official manifesto exists; this charter is this Atlas’s proposal.
Mission. Every automated decision has an owner, a definition anyone can read, a tested version, a record of each time it ran, and evidence that it does what its owner intends.
- One decision, one owner, one definition. The owner is a business role accountable for outcomes, not the engineer who wrote the code.
- Logic is versioned with its tests. Tables, thresholds and strategies live in version control with test cases, whatever runs them.
- No silent outcomes. A DMN table with no matching rule returns null unless a default is set.[20] Every decision ends in an allowed output, and “refer to a person” counts as an output.
- Every decision leaves a record of inputs, version, path, output, assignment probability, reviewer and, later, outcome.
- Change climbs a ladder whose height matches the risk: a threshold inside an approved band needs less than a new data source.
- Human review must be real. Reviewers need time, information and authority; otherwise it is, in the Article 29 Working Party’s words, “just a token gesture”.[25]
- Learn from randomised comparisons, not stories. Outcomes observed only for cases the old logic accepted cannot prove a new one.[23]
- Explainable and contestable by design. Reason codes are written with the rule, not reverse-engineered after a complaint.
Non-goals. Approving models (ModelOps) and setting risk appetite (RiskOps).
Building blocks
Link to section “Building blocks”| Block | What it is | Typical form |
|---|---|---|
| Decision inventory | List of automated decisions with owner, volume, effect on people, outcome window | Table or catalogue |
| Decision card | The contract: purpose, inputs, outputs, reason codes, fallback, limits, metrics | YAML or wiki page (template) |
| Decision logic | Tables, rules, scorecards, thresholds, optimisation models | DMN, rules-engine files, code, solver models |
| Decision service | Stateless runtime that evaluates a pinned version | API, library, stream processor |
| Assignment | Chooses champion or challenger per unit or time window; records the probability | Hash on a unit id with a per-experiment salt |
| Decision log | Append-only record per decision | Event stream into a warehouse table |
| Review queue | Cases referred to people; overrides with reasons | Case-management tool |
| Outcome join | Links each decision to its outcome when known | Scheduled job keyed by decision id |
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”R = responsible, A = accountable, C = consulted, I = informed.
| Activity | Decision owner | Decision engineer / rules analyst | Decision scientist | Platform engineer | ModelOps / risk / compliance |
|---|---|---|---|---|---|
| Define the decision, outputs and success metric | A | R | C | I | C |
| Change logic (rules, thresholds, strategy) | A | R | C | I | C (A if the change is material) |
| Design and read experiments | A | C | R | C | I |
| Run the decision service and log | I | C | I | A, R | I |
| Approve models and material rule sets | C | C | C | I | A, R (ModelOps) |
| Handle a contested decision | A | R (reconstruct from log) | C | C | C |
| Retire a decision or rule | A | R | C | R | I |
Small organisations combine roles, but an owner should not approve their own material changes.
Lifecycle
Link to section “Lifecycle”| Stage | What happens | Exit evidence |
|---|---|---|
| Discover | Find hidden decisions; add to inventory | Owner named; volume and effect on people recorded |
| Design | Write the decision card; draw the DRG if decisions depend on each other | Card reviewed; fallback and reason codes defined |
| Build | Author logic and test cases | Table checks pass (no overlaps under Unique, no gaps); unit tests pass |
| Validate offline | Replay stored inputs; check acceptance criteria | Decision-change matrix reviewed and signed |
| Release | Shadow, then a randomised share, then full | No sample ratio mismatch; guardrails held; rollback target named |
| Operate | Monitor mix, referrals, overrides, latency, log completeness | Monthly scorecard |
| Learn | Join outcomes; compare champion and challenger | Decision to promote, iterate or stop, recorded |
| Retire | Remove the decision or a rule, including dead code paths | No callers; logic deleted, not just disabled |
Operating model and governance
Link to section “Operating model and governance”Governance turns on change classes (this Atlas’s proposal; align them with your ModelOps change classes):
| Class | Example | Minimum path | Approver |
|---|---|---|---|
| P: parameter inside an approved band | Referral threshold moves from 0.12 to 0.13 within a band of 0.10–0.15 | Peer review, table checks, replay | Decision owner |
| L: logic change | New rule, reordered table, new input field, new reason code | P + shadow + randomised share with guardrails | Owner; compliance if eligibility, price or a protected group may be affected |
| M: material change | New model or solver formulation, new data source, newly automated decision | L + ModelOps approval + impact assessment | Model risk or equivalent authority |
| E: emergency | Fraud attack; a broken input | Switch to a pre-approved fallback strategy; full review within days | On-call owner, then retrospective approval |
Cadence. Weekly: alerts, referrals, override reasons. Monthly: the scorecard. Quarterly: strategy and inventory review. Redo the impact assessment when scope changes, as Canada’s directive requires.[26]
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture
Link to section “Reference architecture”A decision service evaluates a pinned version of the decision logic. Assignment chooses the version, the log records everything, and outcomes return later.
Diagram source (Mermaid)
flowchart TB
CALL["Calling system: checkout, claims, dispatch"]
ASG["Assignment: champion or challenger, with probability"]
subgraph SVC["Decision service: one pinned version"]
direction TB
IN["Assemble and validate inputs"]
POL["Policy rules: eligibility, hard limits"]
SC["Model score call"]
STR["Strategy table: bands, reason codes"]
OPT["Optimiser (optional)"]
end
REV["Review queue: people with authority"]
LOG[("Decision log: append-only")]
ACT["Action and notice to the customer"]
OUT["Outcome join, days to months later"]
REG["Version registry and tests in Git"]
CALL --> ASG -->|"routes to one version"| SVC
IN -->|"valid"| POL
POL -->|"eligible"| SC --> STR
IN -->|"invalid: refer"| REV
STR -->|"no optimiser"| LOG
STR -->|"allocation needed"| OPT
STR -->|"refer band"| REV
POL -->|"rule refuses: refer"| REV
OPT --> LOG
REV -->|"reviewer's decision"| LOG
LOG -->|"write, then act"| ACT
ACT --> OUT
OUT -.->|"join by decision id"| LOG
REG -.->|"release"| SVC
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class IN,POL,SC,STR,OPT core
class CALL,ACT,OUT,REG neutral
class ASG,REV decision
class LOG risk
Text equivalent: A calling system (checkout, claims or dispatch) sends a request; assignment chooses the champion or a challenger version and records the probability. Inside the decision service (one pinned version), invalid inputs and requests a policy rule refuses are referred to a review queue of people with authority. Eligible requests get a model score; a strategy table applies bands and reason codes, refers its referral band, sends cases needing an allocation to the optional optimiser, and passes the rest on. Every automated result and every reviewer’s decision is first written to the append-only decision log, and only then becomes an action and a notice to the customer (“write, then act”). Outcomes are joined back by decision id days or months later. Versions and tests live in Git and are released into the service.
Design choices and trade-offs.
- Rules before scores, so a score can never approve what policy forbids, and the reason is a rule, not a probability.
- Stateless service. DMN decision services are stateless with no side effects;[20] counters such as
refunds_90dare inputs computed elsewhere, so a replay reproduces the decision. - Pin the version per request, and make every replica run it (Knight’s failure above).
- No record, no action. If logging fails, refer or fail closed. Open Policy Agent, by contrast, drops decision-log events beyond a configured rate limit;[27] know what your stack does.
- Rules engine or code? DMN gives readable tables and a standard, but engines differ: on the DMN TCK (results page updated August 2026), engines with submitted results range from 3,391 of 3,391 tests passed to 2,741, and the suite reflects DMN 1.4, so it says nothing about 1.5 or 1.6 features.[28] Test portability yourself.
End-to-end workflow: one decision, happy and exception paths
Link to section “End-to-end workflow: one decision, happy and exception paths”Happy path (an illustrative refund request):
- Checkout calls
refund_auto_approvalwith order value, customer tenure and refunds in the last 90 days. - Assignment hashes the customer id with the experiment salt: bucket 45, champion 1.3.0, probability 0.9.
- Inputs validate; policy rules pass; the strategy table matches rule R2: APPROVE, reason code
LOW_VALUE_LONG_TENURE. - The service writes the decision record, then returns APPROVE with its reason code; the refund is issued.
- Thirty days later the outcome job marks the refund
not_disputedand joins it to the record.
Exception paths:
- Input missing or out of range (tenure null): the service returns REFER with reason
INPUT_INCOMPLETEand alerts if the rate rises; it never guesses. - No rule matched: the default rule returns REFER and increments a
default_hitcounter that should stay at zero. - Score service timeout: the pre-approved fallback strategy (policy rules only, wider referral band) runs, and the record says so.
- Referral: a reviewer sees inputs, reason codes and the recommendation, decides, and records an override reason if they disagree.
- Contest: support retrieves the record by decision id, gives the reason codes, and a person with authority reconsiders; what the law requires differs by jurisdiction (regulation table).
Practices, frameworks and tools
Link to section “Practices, frameworks and tools”Core practices and playbooks
Link to section “Core practices and playbooks”Playbook 1: build the decision inventory
Link to section “Playbook 1: build the decision inventory”Search for decisions, not systems. For each, record owner, daily volume, outputs, effect on people, outcome window, where the logic lives and whether people review. Rank by volume × cost of a wrong decision × effect on people; start with the top three. Canada’s definition is a useful net: “Any technology that either assists or replaces the judgment of human decision makers”, including “rules-based systems”.[26]
Playbook 2: write logic that can be checked
Link to section “Playbook 2: write logic that can be checked”- Declare the hit policy. DMN’s default, Unique, forbids overlapping rules; First-hit tables “are not considered good practice because they do not offer a clear overview of the decision logic”.[20] If order matters, say so and test it.
- Close every gap. Either make the table complete or add an explicit default that returns REFER, never null.[20]
- Attach reason codes to rules, so every output carries a specific reason, as US Regulation B requires for adverse credit action.[15]
- Type and bound inputs. Ranges, units and null handling belong in the decision card; validation failures become REFER.
- Keep thresholds as data with an approved band, so class-P changes do not need code changes.
- Delete retired logic. A disabled rule or reused flag is a loaded path (Knight Capital).
Playbook 3: climb the testing ladder
Link to section “Playbook 3: climb the testing ladder”Each rung answers a question the previous one cannot. Nextmv separates offline tests (“run on stored data, which does not involve the production system”) from online shadow and switchback tests;[21] Stripe Radar offers a backtest, shadow at 0% traffic, then a rising share.[14]
Diagram source (Mermaid)
flowchart TB T1["1. Table checks and unit tests: is the logic well formed?"] T2["2. Replay on stored inputs: which decisions change, and how many?"] T3["3. Acceptance criteria: are change rates and known outcomes inside limits?"] T4["4. Shadow on live inputs: does it run correctly at production volume?"] T5["5. Randomised share or switchback: what happens to outcomes?"] T6["6. Full rollout with a named rollback version"] T7["7. Post-release review after the outcome window"] STOP["Stop or redesign"] T1 -->|"pass"| T2 -->|"pass"| T3 -->|"pass"| T4 T4 -->|"pass"| T5 -->|"guardrails held"| T6 --> T7 T1 -.->|"fail"| STOP T3 -.->|"fail"| STOP T5 -.->|"guardrail breached or SRM"| STOP classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029 class T1,T2,T3,T4,T6,T7 core class T5 decision class STOP risk
Text equivalent: Seven rungs, top to bottom. 1, table checks and unit tests ask whether the logic is well formed. 2, replay on stored inputs shows which decisions change and how many. 3, acceptance criteria check that change rates and known outcomes are inside limits. 4, a shadow run on live inputs shows the version runs correctly at production volume. 5, a randomised share or switchback test measures what happens to outcomes. 6, full rollout names a rollback version. 7, a post-release review follows the outcome window. Failing rung 1 or 3, or breaching a guardrail or finding a sample ratio mismatch at rung 5, leads to “stop or redesign”.
Rungs 1–4 cannot tell you how newly approved cases behave, because the past contains outcomes only for cases the old logic accepted (selective labels). Only rung 5 measures that. For a class-P change you may stop at rung 3 if the decision-change rate is tiny; for class M, climb every rung.
Choosing rung 5. Randomise per unit (customer, account) when units do not interact: classic champion/challenger with a stable hash.[18] When they share capacity, randomise time windows and regions. DoorDash found A/B testing “is not optimal for testing our assignment algorithm”, because giving one delivery the only available Dasher affects the other; its switchback tests randomise “on geographic region and time ‘units’ rather than users”, and analysis respects those units.[22][29]
Before reading any result, check the split. A sample ratio mismatch, “the situation when the observed sample ratio in the experiment is different from the expected”, is a symptom of data-quality problems; Fabijan et al. warn that ignoring it “may result in a bad product modification appearing to be good”.[30] ProductOps teaches experiment hygiene in depth; for decisions the extra work is a stable assignment and a logged propensity per decision, which Lab 3 builds before a short split check.
Playbook 4: design human review that is real
Link to section “Playbook 4: design human review that is real”- Refer a band, not everything: borderline scores, missing inputs, high-impact cases; audit a random sample of the rest.
- Give reviewers time, context and authority, and capture override reasons. WP29: oversight “should be carried out by someone who has the authority and competence to change the decision”.[25]
- Expect automation bias. A review found it “occurs in both naive and expert participants, cannot be prevented by training or instructions”.[31] Show the recommendation only after a first look in a sample of cases; track override rates per reviewer, where near zero is a warning.
- Canada’s rule for high-impact systems is blunt: at levels III and IV, “The final decision must be made by a human.”[26]
Playbook 5: release and roll back decision versions
Link to section “Playbook 5: release and roll back decision versions”- Build an immutable artefact (tables, thresholds, model versions) with its tests and replay report.
- Release by repointing an instance (Nextmv: “a representation of a version and optional configuration”)[32] or route; rollback is the same pointer change, recorded.
- Check every replica reports the new version before raising traffic.
- In emergencies, switch to a pre-approved fallback strategy; never edit rules live.
- Mind tools whose UI edits apply at once: Stripe keeps rule history “for the past 180 days” (October 2026);[14] export it if you need longer.
Playbook 6: close the outcome loop
Link to section “Playbook 6: close the outcome loop”- Define the outcome and its window in the card (default within 12 months, dispute within 30 days); confirm early proxies when the real outcome arrives. Join by decision id.
- Beware selective labels. In Lakkaraju et al.'s bail example, failure to appear is observed “only if the human judge decides to release the defendant”.[23] Loans, refunds and fraud are the same: you see outcomes only where you said yes.
- Buy information deliberately. A small randomised share, with each action’s probability logged, lets you estimate what other
logic would have done; Bottou et al. stress such data “must be collected with an experiment involving active
randomization”.[33] Vowpal Wabbit’s bandit format stores exactly this:
action:cost:probability.[24]
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Item | Type | What it gives you | Caveats |
|---|---|---|---|
| DMN (OMG) | Formal standard; 1.6 adopted September 2026[12] | Decision requirements graphs, decision tables with hit policies, the FEEL expression language, decision services; three conformance levels (level 3 requires full FEEL)[20] | Engines differ in conformance (TCK); DMN says nothing about testing, release or logging |
| Champion/challenger | Industry practice | Random share of units on a candidate strategy; promote on evidence[18] | Slow when outcomes take months; needs a stable assignment and logging |
| Switchback experiments | Experiment design | Valid comparisons when units interact through shared capacity[22] | Fewer independent units, so less statistical power |
| Counterfactual evaluation | Research method | Estimates for logic you did not run, from randomised logs[33] | Needs logged probabilities and overlap between old and new choices |
Regulation that shapes automated decisions (October 2026; jurisdiction-specific; not legal advice):
| Jurisdiction | Rule | What it means for DecisionOps |
|---|---|---|
| EU (GDPR) | Art. 22: a right not to be subject to a decision “based solely on automated processing” with legal or similarly significant effects, subject to exceptions; for the contract and consent exceptions, safeguards include human intervention and the right to contest[34] | Know which decisions are “solely automated”; token human review does not change that[25] |
| EU (CJEU) | SCHUFA (C-634/21, 7 Dec 2023): a credit score is an automated decision “in so far as” lenders give it “a determining role” in granting credit[35] | A score supplier can be inside Art. 22 when the decision maker relies on it |
| EU (consumer credit) | Directive (EU) 2023/2225, applying from 20 November 2026: where a creditworthiness assessment “involves the use of automated processing of personal data”, the consumer may request human intervention: an explanation, their point of view, a review (Art. 18(8)); rejections must mention this right (Art. 18(9))[36][37] | Not limited to solely automated decisions: credit decisions need a staffed review route; national law sets the detail |
| EU (AI Act) | Annex III lists creditworthiness evaluation and credit scoring as high-risk, except fraud detection[38] | Obligations and dates belong to ModelOps; check current dates |
| US (ECOA) | Regulation B, 12 CFR 1002.9(b)(2): specific principal reasons for adverse action[15] | Reason codes per rule; the CFPB’s 2022 circular on complex algorithms[39] was withdrawn in May 2025, but the regulation still applies[40] |
| Canada (federal) | Directive on Automated Decision-Making: impact assessment, notice, explanation, peer review, human involvement by impact level, keeping all released versions[26] | A ready-made control list, even outside government |
| UK | The Data (Use and Access) Act 2025, s. 80, replaced UK GDPR Art. 22 with Arts 22A–22D, commenced on 5 February 2026:[41][42] “solely automated” means “no meaningful human involvement”; strict limits remain for special-category data and for processing based on recognised legitimate interests; significant solely automated decisions need information, representations, human intervention and contest. ICO draft guidance consultation closed 29 May 2026[43] | Check the ICO’s final guidance; build the four safeguards into the review route |
Tooling landscape
Link to section “Tooling landscape”Categories first; product facts are dated October 2026 and change often.
| Category | Job | Representative options (verified) | Watch-outs |
|---|---|---|---|
| DMN and rules engines | Evaluate decision tables and rules | Engines reporting DMN TCK results, such as Apache KIE Drools, Camunda, IBM, Trisotech and jDMN[28] | Conformance differs; vendor extensions reduce portability |
| Decision platforms | Author, test, deploy and monitor decisions | FICO Platform (DMN or decision flows, champion/challenger, simulation);[6] Nextmv (decision models, offline and online tests)[21] | Lock-in of logic and logs; export both |
| Embedded rules in a domain product | Rules on top of the vendor’s score | Stripe Radar rules with backtest, traffic allocation and shadow at 0%; custom rules depend on the Radar plan, allow rules on request (October 2026)[14] | Limited history and logic outside your version control |
| Optimisation solvers | Choose allocations under constraints | HiGHS;[44] Google OR-Tools[45] | Time limits and optimality gaps make outputs vary; log them |
| Policy engines with decision logs | Authorisation-style decisions | Open Policy Agent, whose decision logs carry decision id, input, result and bundle revision, with masking[27] | Built for access control; logs can drop events under rate limits |
| Experiment assignment and analysis | Randomise, log probability, analyse | In-house hashing plus a statistics library, or a platform such as open-source GrowthBook[46] or Statsig, which also runs switchback tests[47] | Unit of randomisation must match the decision’s interference |
| Decision log store | Keep records joinable for years | Event stream into a warehouse table | Personal data: retention, access, masking |
Select on: readable logic, replay in a test harness, a record per decision, version pinning and rollback, and export of logic and logs.
Automation and AI
Link to section “Automation and AI”- LLMs drafting rules. Stripe’s Radar Assistant drafts a rule from a natural-language request, then “Test rule” runs it against history (October 2026; Stripe may use the chat entries for training).[14] Treat generated logic as a class-L change.
- LLMs or agents inside a decision. Same contract: allowed outputs, reason codes, a REFER exit, full logging, limits enforced by rules after the model. Evaluations belong to LLMOps.
- Automating the operation (mix alerts, fallback on timeout) is cheap and useful; promoting challengers with no person reading the result is not, for decisions with significant effects.
DecisionOps in practice
Link to section “DecisionOps in practice”Reference implementation
Link to section “Reference implementation”Illustrative scenario: a retailer automates refund approvals: about 4,000 requests a day, outputs APPROVE or REFER (people decide every refusal), outcome = dispute or abuse within 30 days. The minimum artefacts:
1. The decision card (the contract). Example status: YAML syntax-checked by the validator; not executed.
decision: refund_auto_approval
owner: head-of-customer-care # a business role, not a person's name
purpose: Approve low-risk refunds instantly; refer the rest to agents
outputs: [APPROVE, REFER] # no automated refusals in this design
inputs:
order_value: {type: number, unit: EUR, min: 0}
tenure_months: {type: integer, min: 0}
refunds_90d: {type: integer, min: 0}
on_invalid_input: REFER # reason INPUT_INCOMPLETE
on_no_rule_match: REFER # reason DEFAULT_RULE; alert if > 0
reason_codes: [LOW_VALUE, LOW_VALUE_LONG_TENURE, NEW_CUSTOMER,
RECENT_REFUNDS, HIGH_VALUE, INPUT_INCOMPLETE, DEFAULT_RULE]
approved_bands:
approve_max_order_value: {min: 400, max: 600} # live value 500; class P inside
service_levels: {p99_latency_ms: 150, log_completeness: 1.0}
outcome: {event: refund_disputed_or_abusive, window_days: 30}
experiments: {unit: customer_id, max_challenger_share: 0.10}
human_review: {queue: refunds-l2, target_handling_minutes: 6}
effects_on_people: financial, reversible; customer may contest via support
2. One decision record. Example status: JSON syntax-checked; not executed. Fields mirror the OPA decision-log idea (decision
id, input, result, version)[27] plus what experiments and audits need. recommendation is what the logic suggests (for a
referral, what the reviewer is shown); final_output is what was done, set by the reviewer when there is one.
{
"decision_id": "d-2026-10-23-000481",
"decision": "refund_auto_approval",
"version": "1.3.0",
"arm": "champion",
"propensity": 0.9,
"timestamp": "2026-10-23T09:14:07Z",
"inputs": {"order_value": 120, "tenure_months": 30, "refunds_90d": 1},
"path": ["policy:eligible", "table:R2"],
"recommendation": "APPROVE",
"output": "APPROVE",
"reason_codes": ["LOW_VALUE_LONG_TENURE"],
"fallback_used": false,
"reviewer": null,
"final_output": "APPROVE",
"outcome": null
}
3. A release manifest. Example status: YAML syntax-checked; not executed.
release: refund_auto_approval 1.4.0
change_class: L
summary: >-
Raise long-tenure approve limit from 500 to 600 EUR (inside the approved band,
class P on its own); new rule R6 makes the release class L
evidence:
table_checks: pass # no overlaps, no gaps, 14/14 tests
replay: {inputs: 90 days, decision_change_rate: 0.031, report: replay-1.4.0.html}
shadow: {days: 7, errors: 0, p99_latency_ms: 41}
rollout:
challenger_share: 0.10
min_days: 21
guardrail: dispute_rate <= champion + 0.5pp
srm_gate: p >= 0.001 before reading outcomes
rollback_to: 1.3.0
approvals: {owner: head-of-customer-care, compliance: not required (no refusals)}
4. Outcome join and scorecard query. It assumes the log is loaded into a decisions table whose columns are the record’s
top-level fields, and an outcomes table (decision_id, disputed). The override rate divides by reviewed cases only, as the
KPI table defines it. Example status: executed on 2026-10-23 in SQLite 3.31 on 100 synthetic rows (20 reviewed,
2 overridden, 5 disputed): override_rate 0.1, referral_rate 0.2, dispute_rate 0.05 as computed by hand; not run on real data.
SELECT d.version, d.arm,
COUNT(*) AS decisions,
AVG(CASE WHEN d.output = 'REFER' THEN 1.0 ELSE 0 END) AS referral_rate,
SUM(CASE WHEN d.reviewer IS NOT NULL
AND d.final_output <> d.recommendation THEN 1.0 ELSE 0 END)
/ NULLIF(SUM(CASE WHEN d.reviewer IS NOT NULL THEN 1 ELSE 0 END), 0)
AS override_rate,
AVG(CASE WHEN o.disputed = 1 THEN 1.0 ELSE 0 END) AS dispute_rate
FROM decisions d
LEFT JOIN outcomes o ON o.decision_id = d.decision_id
WHERE d.decision = 'refund_auto_approval'
AND d.timestamp < DATE('now', '-30 days') -- outcome window has closed
GROUP BY d.version, d.arm;
Hands-on labs
Link to section “Hands-on labs”Three labs, standard library Python 3.8 or later, no network, no cost. All three were executed on 2026-10-23 (Python 3.8.10); the outputs shown are copied from those runs. Teardown for every lab: delete the working folder.
Lab 1 (beginner): find overlaps and gaps in a decision table
Link to section “Lab 1 (beginner): find overlaps and gaps in a decision table”Objective. See why hit policies matter by checking a Unique table automatically. Prerequisites. Python 3.8+; an empty
folder. Cost and risk. None. Save the table as refund_table.json. It contains two planted defects that mirror real policy
drafting: R1 and R4 disagree about customers with exactly two recent refunds, and R5 was typed as “over 1,000” while the other rules
stop at 500. Example status: executed.
{
"decision": "refund_auto_approval",
"version": "1.3.0-draft",
"hit_policy": "UNIQUE",
"inputs": ["order_value", "tenure_months", "refunds_90d"],
"output": "action",
"rules": [
{"id": "R1", "order_value": [0, 50], "tenure_months": null,
"refunds_90d": [0, 3], "action": "APPROVE"},
{"id": "R2", "order_value": [50, 500], "tenure_months": [12, null],
"refunds_90d": [0, 2], "action": "APPROVE"},
{"id": "R3", "order_value": [50, 500], "tenure_months": [0, 12],
"refunds_90d": [0, 2], "action": "REFER"},
{"id": "R4", "order_value": [0, 500], "tenure_months": null,
"refunds_90d": [2, null], "action": "REFER"},
{"id": "R5", "order_value": [1000, null], "tenure_months": null,
"refunds_90d": null, "action": "REFER"}
],
"tests": [
{"in": {"order_value": 20, "tenure_months": 1, "refunds_90d": 0},
"expect": "APPROVE"},
{"in": {"order_value": 120, "tenure_months": 30, "refunds_90d": 1},
"expect": "APPROVE"},
{"in": {"order_value": 120, "tenure_months": 3, "refunds_90d": 0},
"expect": "REFER"},
{"in": {"order_value": 900, "tenure_months": 60, "refunds_90d": 0},
"expect": "REFER"}
]
}
Save the checker as check_table.py (intervals are [low, high); null means unbounded or “any”):
"""Check a DMN-style decision table (hit policy UNIQUE).
Reports overlapping rules, gaps (no rule matches) and failing test cases.
Usage: python3 check_table.py refund_table.json
Intervals are [low, high): low inclusive, high exclusive; null means unbounded or "any".
"""
import itertools
import json
import sys
def matches(cond, value):
if cond is None:
return True
low, high = cond
return (low is None or value >= low) and (high is None or value < high)
def probe_values(rules, name):
"""Each bound plus the values 0.5 below and above it."""
bounds = {0}
for rule in rules:
cond = rule[name]
if cond is not None:
bounds.update(b for b in cond if b is not None)
values = set()
for b in bounds:
values.update({b - 0.5, b, b + 0.5})
return sorted(v for v in values if v >= 0)
def evaluate(table, case):
return [r for r in table["rules"]
if all(matches(r[name], case[name]) for name in table["inputs"])]
def main(path):
table = json.load(open(path))
names = table["inputs"]
grids = [probe_values(table["rules"], n) for n in names]
overlaps, gaps, seen = [], [], set()
for combo in itertools.product(*grids):
case = dict(zip(names, combo))
hits = evaluate(table, case)
if len(hits) > 1:
key = tuple(r["id"] for r in hits)
if key not in seen:
seen.add(key)
overlaps.append((key, case))
elif not hits and len(gaps) < 3:
gaps.append(case)
print(f"{table['decision']} v{table['version']} hit policy {table['hit_policy']}")
print(f"probe points: {len(list(itertools.product(*grids)))}")
for key, case in overlaps:
print(f"OVERLAP {'+'.join(key)} at {case}")
for case in gaps:
print(f"GAP (no rule, result null) at {case}")
failed = 0
for t in table["tests"]:
hits = evaluate(table, t["in"])
got = hits[0]["action"] if len(hits) == 1 else f"{len(hits)} hits"
if got != t["expect"]:
failed += 1
print(f"TEST FAIL {t['in']}")
print(f" expected {t['expect']} got {got}")
print(f"tests: {len(table['tests']) - failed}/{len(table['tests'])} passed")
ok = not overlaps and not gaps and not failed
print("RESULT", "PASS" if ok else "FAIL")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
Run python3 check_table.py refund_table.json. Expected output (exit code 1):
refund_auto_approval v1.3.0-draft hit policy UNIQUE
probe points: 385
OVERLAP R1+R4 at {'order_value': 0, 'tenure_months': 0, 'refunds_90d': 2}
GAP (no rule, result null) at {'order_value': 500, 'tenure_months': 0, 'refunds_90d': 0}
GAP (no rule, result null) at {'order_value': 500, 'tenure_months': 0, 'refunds_90d': 0.5}
GAP (no rule, result null) at {'order_value': 500, 'tenure_months': 0, 'refunds_90d': 1.5}
TEST FAIL {'order_value': 900, 'tenure_months': 60, 'refunds_90d': 0}
expected REFER got 0 hits
tests: 3/4 passed
RESULT FAIL
Validation. Change R1’s refunds_90d to [0, 2] and R5’s order_value to [500, null]: RESULT PASS, exit code 0
(verified). Troubleshooting. KeyError: a rule lacks a declared input; for non-integer inputs add finer probes than ± 0.5.
Lesson. Three of four example tests passed, yet the table was wrong twice: systematic checks find what examples miss.
Lab 2 (intermediate): replay a challenger and see what history cannot tell you
Link to section “Lab 2 (intermediate): replay a challenger and see what history cannot tell you”Objective. Compare a champion and a challenger credit-style strategy on stored inputs and read the decision-change matrix.
Prerequisites. Lab 1 folder. Cost and risk. None; data are synthetic, generated with a fixed seed. Save as replay.py.
Example status: executed.
"""Replay stored inputs through the champion and a challenger; compare decisions.
Usage: python3 replay.py
Synthetic data (fixed seed). Outcomes exist only where the champion approved
(selective labels).
"""
import collections
import random
ACTIONS = ("APPROVE", "REFER", "DECLINE")
def champion(app): # strategy v4.2.0
if app["pd"] > 0.20 or (app["amount"] > 5000 and not app["verified"]):
return "DECLINE"
if app["pd"] > 0.12:
return "REFER"
return "APPROVE"
def challenger(app): # strategy v4.3.0-rc1: wider approve band, higher amount limit
if app["pd"] > 0.22 or (app["amount"] > 8000 and not app["verified"]):
return "DECLINE"
if app["pd"] > 0.14:
return "REFER"
return "APPROVE"
def make_input_set(n=2000, seed=7):
rng = random.Random(seed)
apps = []
for i in range(n):
pd = min(0.6, rng.betavariate(2, 14))
app = {"id": f"A{i:05d}", "pd": round(pd, 3),
"amount": rng.choice([500, 1500, 3000, 6000, 9000]),
"verified": rng.random() < 0.7}
# Outcome is observed only if the champion approved the application in production.
if champion(app) == "APPROVE":
app["defaulted"] = rng.random() < app["pd"]
apps.append(app)
return apps
def main():
apps = make_input_set()
matrix = collections.Counter((champion(a), challenger(a)) for a in apps)
print("champion -> challenger count")
for c in ACTIONS:
for d in ACTIONS:
if matrix[(c, d)]:
flag = "" if c == d else " <- changed"
print(f"{c:>8} -> {d:<10} {matrix[(c, d)]:>6}{flag}")
changed = sum(v for (c, d), v in matrix.items() if c != d)
print(f"decision-change rate: {changed / len(apps):.1%} of {len(apps)}")
swap_in = [a for a in apps if champion(a) != "APPROVE" and challenger(a) == "APPROVE"]
swap_out = [a for a in apps
if champion(a) == "APPROVE" and challenger(a) != "APPROVE"]
labelled = [a for a in swap_in if "defaulted" in a]
print(f"swap-ins (newly approved): {len(swap_in)}, "
f"with observed outcome: {len(labelled)}")
print(f"swap-outs (no longer approved): {len(swap_out)}")
kept = [a for a in apps if champion(a) == "APPROVE" and challenger(a) == "APPROVE"]
rate = sum(a["defaulted"] for a in kept) / len(kept)
print(f"default rate where both approve (observed): {rate:.1%} of {len(kept)}")
print("swap-in default rate: UNKNOWN from history -> needs a controlled test")
if __name__ == "__main__":
main()
Expected output of python3 replay.py:
champion -> challenger count
APPROVE -> APPROVE 1006
REFER -> APPROVE 133 <- changed
REFER -> REFER 341
DECLINE -> APPROVE 72 <- changed
DECLINE -> REFER 89 <- changed
DECLINE -> DECLINE 359
decision-change rate: 14.7% of 2000
swap-ins (newly approved): 205, with observed outcome: 0
swap-outs (no longer approved): 0
default rate where both approve (observed): 5.3% of 1006
swap-in default rate: UNKNOWN from history -> needs a controlled test
Validation. Explain the 72 DECLINE → APPROVE cases (unverified requests for 6,000, now under the 8,000 limit). Lesson. The
205 swap-ins are the challenger’s whole point, and history holds no outcome for any of them: replay sizes the change, only a
randomised test prices it. Troubleshooting. Other numbers mean a different random implementation; the structure is what
matters.
Lab 3 (advanced): assignment, propensity and a sample ratio check
Link to section “Lab 3 (advanced): assignment, propensity and a sample ratio check”Objective. Assign units deterministically and log, for every decision, the version and the probability that chose it: the
raw material for fair comparisons and counterfactual estimates. A short split check at the end stops a broken experiment from being
read (sizing and SRM practice in depth: ProductOps). Prerequisites. Python 3.8+. Cost and risk. None; writes decisions.jsonl (about 8 MB) in the current folder.
Save as experiment.py. Example status: executed.
"""Champion/challenger assignment, a decision log with propensities, an SRM check.
Usage: python3 experiment.py [--drop-rate 0.08]
Writes decisions.jsonl in the current directory. Standard library only.
"""
import argparse
import hashlib
import json
import math
import random
SALT = "refund-exp-2026-10" # new salt per experiment, never reused
CHALLENGER_BUCKETS = range(0, 10) # buckets 0-9 = 10 % challenger
VERSIONS = {"champion": "1.3.0", "challenger": "1.4.0-rc1"}
def bucket(unit_id):
digest = hashlib.sha256(f"{SALT}:{unit_id}".encode()).hexdigest()
return int(digest[:8], 16) % 100
def assign(unit_id):
b = bucket(unit_id)
arm = "challenger" if b in CHALLENGER_BUCKETS else "champion"
propensity = len(CHALLENGER_BUCKETS) / 100 if arm == "challenger" else 0.9
return arm, b, propensity
def srm_p_value(observed, expected_share):
"""Chi-square goodness of fit, 1 degree of freedom."""
total = sum(observed)
expected = [total * s for s in expected_share]
chi2 = sum((o - e) ** 2 / e for o, e in zip(observed, expected))
return chi2, math.erfc(math.sqrt(chi2 / 2))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--drop-rate", type=float, default=0.0,
help="simulate a logging bug that loses challenger events")
args = parser.parse_args()
rng = random.Random(11)
counts = {"champion": 0, "challenger": 0}
with open("decisions.jsonl", "w") as log:
for i in range(50000):
unit = f"cust-{i:06d}"
arm, b, p = assign(unit)
if arm == "challenger" and rng.random() < args.drop_rate:
continue # the bug: event never written
event = {"decision_id": f"d-{i:06d}", "decision": "refund_auto_approval",
"version": VERSIONS[arm], "arm": arm, "bucket": b,
"propensity": p, "unit": unit}
log.write(json.dumps(event) + "\n")
counts[arm] += 1
chi2, p_value = srm_p_value([counts["champion"], counts["challenger"]], [0.9, 0.1])
print(f"logged champion={counts['champion']} challenger={counts['challenger']}")
print(f"expected share 90/10, chi2={chi2:.1f}, p={p_value:.2g}")
if p_value < 0.001:
print("SRM DETECTED: do not read outcome metrics; find the cause first")
else:
print("no SRM: outcome comparison may proceed")
if __name__ == "__main__":
main()
Expected output. python3 experiment.py prints logged champion=45063 challenger=4937, chi2=0.9, p=0.35 and “no SRM”.
python3 experiment.py --drop-rate 0.08 (a bug that loses 8% of challenger records) prints logged champion=45063 challenger=4524, chi2=42.3, p=7.7e-11 and “SRM DETECTED”. Validation. Run the first command twice: the same customers land in
the same arms, because assignment depends only on the salt and the id. Troubleshooting. If the split drifts with no drop rate,
check that the salt and bucket ranges match the experiment plan. Extension. Give 5% of champion-arm REFER cases an APPROVE at
random, log propensity for that action, and you have the raw material for counterfactual estimates.[33]
Use cases and scenarios
Link to section “Use cases and scenarios”| Scenario | Context and method | Trade-offs | Success measure | Pitfall |
|---|---|---|---|---|
| Delivery dispatch (real: DoorDash, 2019)[22] | An assignment algorithm decides which Dasher takes which delivery; changes are tested by switchback over region-time units | Fewer independent units, so less power; analysis aggregated to units | Delivery duration at unit level | Per-delivery A/B tests contaminate control with treatment |
| Card fraud rules on a vendor score (product mechanics: Stripe Radar; scenario illustrative)[14] | Block, review or allow rules combine the vendor’s risk level with business attributes; new rules are back-tested on six months, shadowed at 0%, then ramped (custom rules are plan-dependent) | Review rules cost analyst time; allow rules override everything | Fraud losses and false-positive rate per rule; review-queue size | A broad review rule floods the queue; an allow rule lets fraud through |
| Credit-line strategy (illustrative; rules sourced) | Champion/challenger on 10% of accounts by random digit;[18] reason codes per rule for adverse action[15] | Outcomes take months; challenger losses are a cost of learning | Loss rate, revenue, complaint rate per arm | Reading early delinquency as final; SCHUFA-style reliance on a third-party score[35] |
| Public benefit debts (real: Robodebt, Australia) | Income data were matched “through an automated system” and, without a response, the employer-reported amount for the period “would be divided up evenly” across its fortnights to raise debts[48] | Speed and scale against legality and fairness | Our inference: share of debts upheld against actual fortnightly income | Logic without lawful basis; the Royal Commission called it “a crude and cruel mechanism, neither fair nor legal”[48] |
The Royal Commission’s recommendations 17.1 and 17.2 read like DecisionOps controls: a clear path to review, plain-language explanations, “business rules and algorithms should be made available, to enable independent expert scrutiny”, and a body to monitor and audit automated decision-making.[48]
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”Measure each decision, per version and arm. Outcome metrics come from the business (AnalyticsOps owns their definitions); the rest are operational.
| KPI | Formula | Why it matters | Trap |
|---|---|---|---|
| Output mix | decisions with output x ÷ all decisions, per version | First sign of a broken release or input | A stable mix can hide offsetting errors |
| Referral rate | referred ÷ all decisions | Drives review cost and customer wait | Lowering it is not automatically good |
| Override rate | reviewed cases whose final outcome ≠ recommendation ÷ reviewed cases | Shows disagreement, or rubber-stamping when near zero | Read per reviewer and reason, not only in total |
| Default-rule hits | decisions resolved by the default rule ÷ all decisions | Table gaps or new input values | Should be zero; any non-zero value is a defect |
| Log completeness | decisions logged with version and inputs ÷ actions taken in business systems | Audit and experiments depend on it | Count from the action side, not the log |
| Reproducibility | sampled logged decisions that replay to the same output ÷ sample | Proves you can explain past decisions | Non-deterministic optimisers need seed and limits logged |
| Outcome per arm | for example loss or dispute rate, with a confidence interval | The point of the whole exercise | Only after the outcome window; only if no SRM |
| Contest rate and uphold rate | contested ÷ decisions; overturned ÷ contested | Fairness and quality signal from the people affected | Low contest rates may mean people do not know they can |
Mix, referrals, default hits and log completeness are daily leading signals; outcomes lag by their window. Set targets from the owner’s risk appetite, not another company’s numbers.
Quality, security, privacy and compliance
Link to section “Quality, security, privacy and compliance”- Integrity of logic. Who can change rules is a security question: reviewed releases only, a protected registry, alerts on changes outside the pipeline. Canada’s directive asks departments to “secure data and model integrity to prevent tampering and unauthorized modifications”.[26] Treat allow rules as privileged: in Stripe Radar they “override all your other rules”.[14]
- Decision logs hold personal data. Omit or mask what is not needed to explain or reproduce (OPA can erase or modify fields before upload),[27] restrict access and set retention with PrivacyOps.
- Fairness and versions. Canada requires testing for “unintended biases” before production, scheduled outcome monitoring, and “Obtaining and safeguarding all released versions of software components”.[26]
- Regulatory mapping sits with ModelOps, evidence with ComplianceOps.
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”The main costs are people and learning. Review labour is referrals × handling time (960 hours above). Learning cost is the extra loss while a weaker challenger runs; cap it with share, guardrails and a stop rule. Solver compute: HiGHS stops a mixed-integer solve at a relative gap of 0.0001 by default, with no time limit unless set;[44] set limits and log the gap reached. Energy matters only for large solves (GreenOps).
Assurance of the operating model
Link to section “Assurance of the operating model”- Monthly: the owner reads the scorecard; a sample of logged decisions is replayed with their logged versions (re-performance; below 100% is a defect).
- Quarterly: reconcile the inventory; review override reasons (recurring reasons are requirements).
- Independent review for high-impact decisions; Canada scales peer review by impact level.[26]
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnose | Fix and prevent |
|---|---|---|---|
| Mix shifts with no code deploy | Rule or threshold edited in a tool UI | Tool’s rule history; diff against the registry | Revert; make the registry the only path to production |
| Outputs differ for identical inputs | Mixed versions across replicas, as at Knight Capital[16] | Group the log by version and host | Version check before raising traffic |
| Default-rule hits appear | Table gap or new input value | Inputs of the default-hit records | Close the gap; validate inputs at the edge |
| Challenger “wins” by a lot | SRM, survivorship, or outcome window not closed | SRM test; label coverage per arm | Fix the split; wait for the window |
| Review queue explodes | A broad review rule (Stripe’s example: all prepaid cards)[14] | Queue inflow by reason code | Narrow the rule; add capacity limits |
| Approvals drift over weeks | Upstream model recalibrated, thresholds unchanged | Score distribution by model version | Treat model releases as decision changes; re-replay |
| Override rate near zero | Rubber-stamping (automation bias)[31] | Time per case; blind-review sample | Redesign review; reduce volume |
| A past decision cannot be explained | Log lacks version or inputs; optimiser not deterministic | Re-performance sample | Complete records; log seed, time limit and gap |
Worked runbook: approvals fall sharply after a quiet release
Link to section “Worked runbook: approvals fall sharply after a quiet release”Illustrative incident. At 09:30 on a Tuesday the refund approval rate drops from 71% to 52%; referrals triple; nobody announced a change.
Diagram source (Mermaid)
%%{init: {"flowchart": {"wrappingWidth": 140, "nodeSpacing": 25, "rankSpacing": 40}}}%%
flowchart TB
A["Alert: approvals 52%, normal 71%"]
B{"One version or host only?"}
C["Pin all replicas to last-known-good"]
D{"Any change in 48 hours?"}
E["Redeploy or roll back the released version"]
F{"Inputs shifted?"}
G["Broken feed: policy-only fallback"]
H["Genuine shift: owner decides"]
I["Re-decide affected cases; record the incident"]
A --> B
B -->|"yes"| C
B -->|"no"| D
D -->|"yes"| E
D -->|"no"| F
F -->|"broken feed"| G
F -->|"genuine"| H
C --> I
E --> I
G --> I
H --> I
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class A risk
class B,D,F decision
class C,E,G,H,I core
Text equivalent: An alert reports approvals at 52% against a normal 71%. If the drop is confined to one version or host, pin all replicas to the last-known-good version. Otherwise, if a rule, threshold or model changed in the last 48 hours, redeploy or roll back the released version. Otherwise check the inputs: a broken feed leads to the pre-approved policy-only fallback; a genuine shift goes to the owner. Every branch ends by re-deciding affected cases and recording the incident.
- Confirm from the decision log: approval rate by version, host and hour.
- Isolate: all hosts report 1.4.0, released last week, but the rule tool’s history shows a threshold edited at 09:12 by a user with direct access, so production no longer matches 1.4.0.
- Contain: redeploy 1.4.0 from the registry, overwriting the edit; approvals return to 70%.
- Recover: about 1,900 requests were referred instead of approved; nobody was refused (refusals are human here). Clear the queue oldest first.
- Prevent: remove direct edit rights; alert on changes made outside the pipeline; record the incident with decision ids.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- “The model is the decision.” A better AUC changes nothing until thresholds change, and may break them.
- “Replay proved the challenger.” Replay cannot observe outcomes for newly approved cases.[23]
- “A person clicks approve, so it is not automated.” Fabricated involvement does not escape GDPR Article 22.[25]
- Logging only the output, optimising a proxy (approval rate, review cost) and disabling instead of deleting retired logic.
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Use DecisionOps when a decision is repeated at volume, its logic changes more than a few times a year, a wrong decision costs money or harms people, outcomes can be observed, or regulators and customers ask “why?”.
Do not, or not yet:
- One-off or strategic decisions: BizOps decision records.
- Low volume: a written policy and human judgement are cheaper.
- No observable outcome: you can test correctness but not learn; use decision support.
- No lawful basis or no way to contest, or no specific reasons: automate the preparation, not the decision.
- Unstable policy: agree the policy first.
Hidden responsibilities: a review team, an outcome pipeline, log retention, change governance and on-call.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
Q1{"Repeated, with stable policy and measurable outcomes?"}
N1["Keep human judgement; use decision support"]
Q2{"Significant effects on people?"}
H["Design real human review and contest; check the law"]
Q3{"Do units interact through shared capacity?"}
SB["Switchback test over time and region"]
Q4{"May you randomise who gets which version?"}
AB["Champion/challenger per unit, propensity logged"]
SH["Replay, shadow, staged rollout; state the blind spot"]
Q1 -->|"no"| N1
Q1 -->|"yes"| Q2
Q2 -->|"yes"| H --> Q3
Q2 -->|"no"| Q3
Q3 -->|"yes"| SB
Q3 -->|"no"| Q4
Q4 -->|"yes"| AB
Q4 -->|"no"| SH
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class Q1,Q2,Q3,Q4 decision
class SB,AB,SH,H core
class N1 risk
Text equivalent: If the decision is not repeated, with stable policy and measurable outcomes, keep human judgement with decision support. If it has significant effects on people, first design real human review and a way to contest, and check the law. If units interact through shared capacity, test changes with switchbacks over time and region. Otherwise, if you may randomise who gets which version, run champion/challenger per unit with the propensity logged; if not, use replay, shadow and staged rollout and state the blind spot.
Weighted check for the level of automation (score each 1–5; weights are this Atlas’s suggestion):
| Criterion | Weight | Full automation favoured when… |
|---|---|---|
| Volume and speed needed | 3 | Thousands a day, answer needed in seconds |
| Reversibility of a wrong decision | 3 | Easy and cheap to reverse |
| Effect on people | 3 | Low, or the outcome is always favourable (approve-only) |
| Quality of inputs | 2 | Validated, complete, current |
| Observability of outcomes | 2 | Short window, unbiased labels |
| Explainability of logic | 2 | Rules or a scorecard with reason codes |
Scores above about 60 of 75 suggest full automation with sampling; 40–60, automation with a referral band; below 40, decision support only. Treat the cut-offs as a starting point for discussion, not a rule.
Maturity model
Link to section “Maturity model”This is this Atlas’s model, not an industry standard.
| Level | Observable behaviour | Exit criteria |
|---|---|---|
| 0 Hidden | Decisions live in code and spreadsheets; nobody lists them | Inventory exists with owners |
| 1 Inventoried | Top decisions have cards; logic located | Logic in version control with tests for the top three |
| 2 Versioned and tested | Releases through a pipeline; table checks; replay | Every decision logs version, inputs and path; rollback rehearsed |
| 3 Experimenting | Champion/challenger or switchback with SRM gates; real review design | Outcomes joined per arm; monthly scorecards |
| 4 Outcome-managed | Strategies improve on evidence; re-performance at 100%; contest data used | Sustained for a year; independent review of high-impact decisions |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Window | Actions | Milestone |
|---|---|---|
| Days 1–30 | Build the inventory; pick three decisions; write their cards; locate their logic; add a decision id and version to every record | Inventory and three cards approved by owners |
| Days 31–60 | Move logic into version control; table checks and replay in CI; define fallbacks; design review bands; start the outcome join | First class-P change released through the pipeline with a replay report |
| Days 61–90 | First randomised test with an SRM gate; scorecards live; change classes agreed with ModelOps and compliance | One champion/challenger result read and acted on; first monthly review held |
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Before releasing a decision change
- Change class set; approver named
- Table checks pass: no overlaps under Unique, no gaps or an explicit REFER default
- Every rule has a reason code; test cases cover each rule and each boundary
- Replay report reviewed: decision-change matrix, swap-ins and swap-outs explained
- Shadow run clean at production volume; latency within service level
- Experiment plan: unit, share, guardrails, stop rule, SRM gate, outcome window
- Rollback version named and deployable; fallback strategy tested
Operating readiness
- Decision card current; owner and on-call named
- Every decision logged with version, inputs, path, output, propensity
- Mix, default-hit, latency and log-completeness alerts live
- Review queue staffed for the expected referral volume; override reasons captured
Quarterly governance audit
- Inventory reconciled with code and tools
- Re-performance sample at 100%
- Override and contest reasons reviewed; changes raised
- Retired rules deleted; no direct edit rights outside the pipeline
- Legal and impact assessments still match the decision’s scope
Templates
Link to section “Templates”Decision change request (copy into your tracker):
Decision: refund_auto_approval
Change class: P | L | M | E
Requested by / owner: <role> / <role>
What changes: <rules, thresholds, inputs, models referenced>
Why: <problem, evidence, expected effect on outcome metric>
Replay result: <decision-change rate; swap-ins; swap-outs; report link>
Effect on people: <who gets a different outcome; can they contest?>
Test plan: <shadow days; share; guardrails; stop rule; outcome window>
Rollback: <version>; fallback strategy <name>
Approvals: owner <date>; compliance <date or n/a>; ModelOps <n/a>
Reviewer guidance card (one page per referral reason):
Referral reason: RECENT_REFUNDS
What the system saw: refunds_90d >= 2, order value, tenure, reason codes
What you can check that it could not: order notes, carrier proof, contact history
You may: approve, request evidence, decline (give the specific reason)
Record: final outcome + override reason from list + free text if "other"
Time: target 6 minutes; escalate rather than guess
The decision card and the release manifest above complete the set.
Deep comparisons
Link to section “Deep comparisons”Kinds of decision logic
| Logic | Strength | Weakness | Test with |
|---|---|---|---|
| Rules and decision tables | Readable, reason codes for free, fast to change | Brittle at scale; overlaps and gaps | Table checks, replay |
| Model score plus threshold | Uses many signals; ranks well | Reasons need extra work; drift moves the cut-off’s meaning | Replay, champion/challenger |
| Optimisation | Best allocation under constraints | Results vary with time limits and gaps; units interact | Scenario tests, switchback |
| LLM step inside the logic | Handles unstructured input | Non-deterministic; needs guardrails | LLMOps evaluations, then the same ladder |
Is DecisionOps a job title or a certification? We found neither (October 2026); the label comes from vendors.[2][6]
Do we need DMN or a rules engine? No. DMN helps when business owners must read and change tables and you want a standard;[20] plain code with the same tests, versions and logs is DecisionOps too.
Our decisions only recommend; a person decides. Does this apply? Yes: the recommendation still shapes outcomes, and automation bias means people often follow it.[31] In the EU, a third party’s score can count as the decision when it plays a determining role.[35]
How big should a challenger share be? Large enough to detect the effect you care about within the window, small enough that a bad challenger’s losses are affordable; the Fair Isaac patent’s 10% is an example, not a rule.[18]
Can we skip randomised tests if replay looks good? Only if the change cannot approve anything the old logic declined. Otherwise replay is blind exactly where it matters (Lab 2).
How long should we keep decision logs? As long as decisions can be contested or audited, no longer than privacy law allows.
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Counterfactual evaluation and bandits: with logged propensities, estimate logic you did not run;[33] common in advertising research, rarer elsewhere.
- Simulation (FICO markets “business outcome simulations”)[6] screens changes; validate with live tests.
- Standards drift: DMN 1.7 is in beta;[12] check the TCK before relying on new features.[28]
Learning path
Link to section “Learning path”| Stage | Do this |
|---|---|
| Prerequisites | Basic probability; spreadsheets or Python; one decision you know well |
| 1 day | Read the DMN introduction and hit policies;[20] run Lab 1 |
| 1 week | Labs 2 and 3; write a decision card and a change request for a real decision |
| 30 days | Replay a real change on stored inputs; design a review band; draft a champion/challenger plan with an SRM gate |
| Specialise | Experimentation (switchbacks, counterfactuals), optimisation (solvers), or regulatory decisioning (credit, public sector) |
| Portfolio idea | Publish a replay report and experiment plan for an open dataset, with every assumption stated |
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Topic | Remember |
|---|---|
| Term | Vendor label; practice = decision management, DMN, champion/challenger |
| Unit | The decision: owner, card, version, tests, log |
| Logic hygiene | Declared hit policy; no gaps; reason codes; delete retired rules |
| Testing | Table checks → replay → acceptance → shadow → randomised or switchback → rollout → review |
| Evidence traps | Selective labels, SRM, unclosed outcome windows |
| People | Real review: time, information, authority; watch override rates |
| Metrics | Mix, referrals, overrides, default hits, log completeness, reproducibility, outcomes per arm |
| Next action | Inventory your top three decisions and add version ids to their logs this month |
A judgement call: review every denial, or review the right ones?
Link to section “A judgement call: review every denial, or review the right ones?”Situation (illustrative). A lender automates small personal-loan decisions: 3,000 automated denials a day. Compliance proposes that a person reviews every denial (A). The operations lead proposes reviewing a borderline band plus a 5% random sample, with a fast reconsideration route for anyone who asks (B). Assumptions: 12 reviewers, 7 productive hours a day each; EU consumer credit; denials have significant effects; the lender must decide which Article 22 exception applies, and from 20 November 2026 the Consumer Credit Directive lets every applicant assessed with automated processing request human intervention and a review, and rejections must say so.[36] Transposition and scope are counsel’s job (not legal advice).
Reasoning. (1) Capacity: A gives each denial 12 × 420 minutes ÷ 3,000 ≈ 1.7 minutes. Under that pressure, with the recommendation visible, automation bias predicts agreement;[31] WP29 requires involvement that is “meaningful, rather than just a token gesture”.[25] A buys the appearance of human decisions and may mislead the legal analysis. (2) B’s risk: clear denials are automated, so the lender needs a lawful exception and safeguards, including human intervention and contest.[34] (3) Quality: assume the borderline band holds about 150 denials a day and about 30 applicants a day ask for reconsideration; with the 150 sampled cases that is 330 cases, so B’s 5,040 reviewer-minutes give each about 15 minutes, where judgement changes outcomes, and the random sample measures how often automated denials would be overturned. Under the directive, both options need the request-and-review route; A simply has no capacity left to run it. (4) What would change the answer: if counsel finds no exception, every denial needs a genuinely human decision: more reviewers or fewer automated denials, not faster clicking.
Decision: B, with the override and reconsideration rates published monthly to the owner and compliance. Uncertainty: the borderline band’s width is a guess until the first month’s sample shows where reviewers disagree, and reconsideration requests may rise once rejection notices mention the right to human assessment; size the team from the first months’ data.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you can explain the term’s status and boundaries; write a checkable decision table; choose a testing rung; design human review; read a replay and an experiment; and run a decision incident.
1. A colleague says “we already do MLOps, so our decisions are covered”. What is missing?
MLOps makes the score reliable. Nothing yet owns the thresholds, rules, referral bands, versions, decision log or outcome comparison that turn the score into actions; those are DecisionOps.
2. A Unique-policy table has a gap for orders of exactly 500. What does a DMN engine return, and what should you do?
Null, unless a default output is set.[20] Close the gap, or add an explicit REFER default, and alert on default hits.
3. Replay shows the challenger approves 205 more applicants and the default rate “where both approve” is 5.3%. Can you promote it?
No. The 205 swap-ins have no observed outcomes (selective labels),[23] so their default rate is unknown. Run a randomised share with guardrails and wait for the outcome window.
4. Your dispatch team wants an A/B test of a new assignment algorithm, randomised by order. Objection?
Orders compete for the same drivers, so treatment affects control. Randomise region-time windows (switchback) and analyse at that unit.[22]
5. Planned 10% challenger; logged 11,640 of 120,000. Read the outcomes?
Not yet: χ² ≈ 12, p ≈ 0.0005, a sample ratio mismatch.[30] Find the cause (here, a retry path that skipped logging) first.
6. Reviewers override 0.4% of recommendations. Good news?
Not necessarily. It can mean the recommendations are excellent, or that reviewers rubber-stamp. Check time per case and run a blind sample where reviewers decide before seeing the recommendation.[31]
Conclusion
Link to section “Conclusion”DecisionOps is a young vendor word for an old, under-practised discipline. Its value is not a platform; it is a shift of attention from the model to the act: what the system decided, under which version, for which reason, with what result. Teams that treat each automated decision as a product, with a contract, tests, a ladder of evidence and a memory, change policy quickly and can answer the customer, the auditor and themselves. Teams that do not will keep discovering their policies in incident reviews, or, as Robodebt showed, in a royal commission.[48] Facts are dated October 2026; re-check before relying on them.
Sources and further reading
Link to section “Sources and further reading”Vendor and documentation pages carry no publication date unless stated. Ledger with claim-by-claim notes:
research/decisionops-ledger.md.
Further reading
- DMN 1.6 specification, clause 11 (worked examples, including a lending decision).
- Directive on Automated Decision-Making, Appendix C, as a control checklist by impact level.
Changelog. 2026-10-23: first version (research, ledger, draft, diagrams, three executed labs).
Changelog. 2026-10-23 (r1 revision, after the independent fact check): fixed the scorecard SQL (override rate over reviewed cases; record gains recommendation and final_output; re-run on synthetic rows); added the EU Consumer Credit Directive 2023/2225 and the UK Arts 22A–22D commencement; made the worked example consistent; redrew the reference architecture (write, then act; referral exits; optional optimiser); dated term-status evidence (Nextmv and FICO posts, EURO Practitioners’ Forum); corrected the TCK range, Taylor–Raden attribution, Knight and Robodebt wording; Stripe plan limits; ProductOps link; derived the judgement call’s capacity numbers.
Changelog. 2026-10-23, post-score polish: FICO definition traced to Nextmv (2024); Gartner date narrowed; figure 2 re-routed; experimentation platforms and a Robodebt measure; trims.
Nextmv home page — nextmv.io / FICO, as of October 2026. Checked 2026-10-23. ↩︎
Nextmv 1.0: Accelerate decision model development with a DecisionOps platform — Carolyn Mooney, Nextmv, 2023-11-16. Checked 2026-10-23. ↩︎ ↩︎ ↩︎
Nextmv is now part of FICO — Carolyn Mooney and Ryan O’Neil, Nextmv, 2026-05-19. Checked 2026-10-23. ↩︎
Fair Isaac Corporation completes acquisition of nextmv.io Inc — Faegre Drinker, June 2026. Checked 2026-10-23. ↩︎
What Is DecisionOps? And Why Is It a Critical Piece in Your AI Strategy? — FICO blog, 2026-09-03. Checked 2026-10-23. ↩︎
FICO Platform: Intelligent Decisions — FICO, as of October 2026. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎
What is DecisionOps and why does it matter? — Haley Eshagh, Nextmv, 2024-07-31. Checked 2026-10-23. ↩︎
Business Rules and Decision Management — FlexRule, as of October 2026. Checked 2026-10-23. ↩︎
Decision Ops: What It Is, How It Works… — r4 Technologies, 2026-01-12 (archived copy; the live page blocks automated access). Checked 2026-10-23. ↩︎
13 June 2025 – Empowering reliable automated decision making in ECCO with DecisionOps — EURO Practitioners’ Forum, webinar by Matthias Als (ECCO), 2025-06-13. Checked 2026-10-23. ↩︎
Smart Enough Systems: How to Deliver Competitive Advantage by Automating Hidden Decisions — James Taylor and Neil Raden, Prentice Hall/Pearson, 2007 (publisher page and its unsigned description). Checked 2026-10-23. ↩︎ ↩︎ ↩︎
Decision Model and Notation (DMN), versions — Object Management Group, as of October 2026. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎
Decision Intelligence (Gartner IT Glossary) — Gartner, archived 2025-09-18. Checked 2026-10-23. ↩︎ ↩︎
Fraud prevention rules — Stripe Docs, as of October 2026. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
12 CFR 1002.9: Notifications (Regulation B) — eCFR, text as of 2026-10-01. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎
In the Matter of Knight Capital Americas LLC, Release No. 34-70694 — US Securities and Exchange Commission, 2013-10-16. Checked 2026-10-23. ↩︎ ↩︎
ModelOps (Gartner IT Glossary) — Gartner, archived 2025-05-17. Checked 2026-10-23. ↩︎
US7383215B1: Data center for account management — Fair Isaac Corp., filed 2000-10-26, granted 2008-06-03. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
FICO Recognized in the 2026 Gartner Critical Capabilities Report for Decision Intelligence Platforms — FICO, 2026-01-30. Checked 2026-10-23. ↩︎
Decision Model and Notation, version 1.6 (formal/25-12-02) — Object Management Group, September 2026; clauses 2.1, 5.2, 5.3.3, 8.2.11 and decision-table semantics. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Experiments and testing overview — Nextmv Docs. Checked 2026-10-23. ↩︎ ↩︎ ↩︎
Experiment Rigor for Switchback Experiment Analysis — Carla Sneider and Yixin Tang, DoorDash, 2019-02-20 (archived copy). Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
The Selective Labels Problem: Evaluating Algorithmic Predictions in the Presence of Unobservables — Lakkaraju, Kleinberg, Leskovec, Ludwig and Mullainathan, KDD 2017. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Contextual Bandits and Vowpal Wabbit — Vowpal Wabbit docs. Checked 2026-10-23. ↩︎ ↩︎
Guidelines on Automated individual decision-making and Profiling (WP251rev.01) — Article 29 Data Protection Working Party, revised 2018-02-06. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Directive on Automated Decision-Making — Treasury Board of Canada Secretariat, modified 2025-06-24. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Decision Logs — Open Policy Agent docs. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎
DMN Technology Compatibility Kit results — DMN TCK project, last updated 2026-08-01; reflects DMN 1.4. Checked 2026-10-23. ↩︎ ↩︎ ↩︎
What is switchback testing for decision models? — Nicole Misek and Tiffany Bogich, Nextmv, 2023-10-26. Checked 2026-10-23. ↩︎
Diagnosing Sample Ratio Mismatch in Online Controlled Experiments — Fabijan et al., KDD 2019 (Microsoft Research page). Checked 2026-10-23. ↩︎ ↩︎
Complacency and bias in human use of automation: an attentional integration — Raja Parasuraman and Dietrich Manzey, Human Factors 52(3), 2010. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Application instances — Nextmv Docs. Checked 2026-10-23. ↩︎
Counterfactual Reasoning and Learning Systems: The Example of Computational Advertising — Bottou et al., Journal of Machine Learning Research 14, 2013. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎
Art. 22 GDPR: Automated individual decision-making, including profiling — Regulation (EU) 2016/679, text as reproduced by gdpr-info.eu. Checked 2026-10-23. ↩︎ ↩︎
Press release 186/23: Judgments in C-634/21 SCHUFA Holding (Scoring) and joined cases — Court of Justice of the EU, 2023-12-07. Checked 2026-10-23. ↩︎ ↩︎ ↩︎
Directive (EU) 2023/2225 on credit agreements for consumers — Official Journal of the EU, 2023-10-30; Art. 18(8)–(9), Art. 48 (EUR-Lex text read via the Internet Archive, snapshot 2026-10-10). Checked 2026-10-23. ↩︎ ↩︎
Consumer protection in financial services — European Commission, as of October 2026. Checked 2026-10-23. ↩︎
EU AI Act, Annex III: High-risk AI systems — Regulation (EU) 2024/1689 as reproduced by artificialintelligenceact.eu (not the Official Journal). Checked 2026-10-23. ↩︎
Consumer Financial Protection Circular 2022-03 (complex algorithms and adverse action notices) — CFPB, 2022-05-26. Checked 2026-10-23. ↩︎
Interpretive Rules, Policy Statements, and Advisory Opinions; Withdrawal — CFPB, Federal Register, 2025-05-12. Checked 2026-10-23. ↩︎
Data (Use and Access) Act 2025, section 80: Automated decision-making — legislation.gov.uk (read via the Internet Archive, snapshot 2026-10-02). Checked 2026-10-23. ↩︎
The Data (Use and Access) Act 2025 (Commencement No. 6 and Transitional and Saving Provisions) Regulations 2026, SI 2026/82 — made 2026-01-29, legislation.gov.uk (read via the Internet Archive). Checked 2026-10-23. ↩︎
ICO consultation on the draft guidance about automated decision-making, including profiling — Information Commissioner’s Office, March–May 2026. Checked 2026-10-23. ↩︎
HiGHS option definitions — HiGHS docs (development version). Checked 2026-10-23. ↩︎ ↩︎
GrowthBook documentation — GrowthBook, as of October 2026. Checked 2026-10-23. ↩︎
Switchback tests — Statsig Docs, as of October 2026. Checked 2026-10-23. ↩︎
Report of the Royal Commission into the Robodebt Scheme — Commonwealth of Australia, 2023-07-07 (corrected edition 2023-07-11; read via the Internet Archive). Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎