grounded-decision¶
Automated systems act on scores: a triage model says "low risk", so the patient is discharged; a fraud model says "suspicious", so the card is frozen. A score alone can't tell you whether the decision is supported: whether many similar past cases point the same way, or only a handful of loosely related ones do. This library sits between the score and the action. It lets the action go ahead when the supporting data is strong, holds it for a person when the data is thin, and writes down why either way.
This page takes about three minutes. It shows what the library does, one runnable example, who it is for, and where to go next.
What it does¶
You write a short policy: which events lead to which actions
("a suspicious card proposes block"). For each event, the policy
names a proposed action. Before that action is allowed, six
independent checks look at the policy and at the evidence: the
numbers describing the related cases around this decision. If every
check passes, the verdict is act. If any check fails, the verdict is
abstain: the action is held, the proposal is kept, and a person
decides. A policy can also name a weaker fallback in advance
(degrade, for example "serve read-only"). Every decision returns a
record saying what was proposed, what each check found, and why.
flowchart LR
E["Event<br/>(something happened)"] --> P["Policy proposes an action"]
V["Evidence<br/>(related cases)"] --> C
P --> C{"Six checks"}
C -->|all pass| A["Act"]
C -->|any fails| H["Hold for a person<br/>(proposal kept)"]
C -->|policy says so| D["Degrade<br/>(named fallback)"]
A --> R[("Record of why")]
H --> R
D --> R
The six checks, as plain questions:
| Check | Question it asks |
|---|---|
| certificate | Is the policy simple enough to analyse? |
| game | Can the workflow keep going, or can it get stuck? |
| reconstruction | Do the related cases agree with each other strongly enough to inform this one? |
| threshold | Is the data sitting so close to the decision line that a little noise would flip it? |
| conformal | Is this case unlike the recent cases where the answer was right? |
| support | Do the related cases point the same way as the proposed action? |
Why a score isn't enough explains each one with a worked number.
The checks' settings are learned, not guessed. You state the error rate you accept ("at most 5% of automatic blocks wrong"); the library learns the checks' settings from your labelled past decisions, certifies that their error rate is below that target, and records where every value came from. See Learn parameters from your history and Where every number comes from.
Before and after¶
From the clinical-triage demo (make demo). The triage model's tag is
the event; the evidence is the set of similar past visits.
| Arrival | Score-only rule | With this library |
|---|---|---|
low_risk; 17 closely linked similar visits, none deteriorated |
discharge | act: discharge |
high_risk; 12 of 14 similar visits deteriorated |
escalate | act: escalate |
low_risk; only 5 loosely linked visits, mixed outcomes |
discharge | abstain: held for a clinician, discharge kept on the record |
The first two rows are unchanged. The third is the case the score rule gets wrong without knowing it.
Try it¶
After installing, this runs as-is. The two evidence bundles are ready-made helpers: one describes a large group of related cases that agree, the other a thin, mixed one.
from grounded_decision import Event, Module, parse
from grounded_decision.examples.common import (
thinly_corroborated_evidence,
well_corroborated_evidence,
)
policy = parse("test(clean) ; act(approve) | test(suspicious) ; act(block)")
module = Module.from_expr("card_review", policy)
for evidence in (well_corroborated_evidence(), thinly_corroborated_evidence()):
module.reset() # each card is a fresh case
record = module.decide(Event("suspicious"), evidence)
print(record.verdict.value, record.action, record.proposed_action)
print(" ", record.reason)
Output:
act block block
all channels passed
abstain None block
d λ² = 0.0800 vs 1+0.050 (at or below KS; abstain); evidence density inside operational window (theorem width=6.2e+04, vacuous=True)
Same event, same policy, same proposal (block). With strong evidence
the card is blocked. With thin evidence nothing is executed
(action is None), block stays on the record for a reviewer, and
the reason names the two checks that failed: the related cases barely
agree (evidence strength 0.08, below the line at 1.05), and their link
density sits too close to the decision line to trust.
Your first decision walks through every
field.
Is this for me?¶
It fits when:
- a model score or rule already triggers an automated action, and a wrong action is costly (a discharge, a freeze, a take-down, a loan);
- you can name the related cases around a decision (accounts sharing a device, similar past visits, near-duplicate posts) and what happened to them;
- someone can take the held cases, and you want an auditable record of every decision, including the ones not carried out.
It does not fit, or needs care, when:
- You need the library to pick the direction. The direction comes
from the event you send (your model or rule decides
suspiciousversusclean). The support check can hold an action the related cases contradict (acleancard inside a dense fraud ring is held), but only when you tell it which label each action implies; it does not choose an action for you. - Your groups of related cases are small. With fewer than about 10 related cases, the knife-edge check holds unless their link density is far (0.3 or more) from your decision line, because at that size a few links either way change the picture, unless the window learned from your history is narrower. See Choose thresholds and calibration.
- You only have a single score per case and no related cases. Three of the six checks then have nothing to measure.
- You want a workflow engine, a rules UI, or a replacement for human review. This is a Python library you call from your own service. "Abstain" means "this module will not act", not "no one may".
The full list of what each check does and does not establish is in Trust levels and honest limits and the FAQ.
Where to go next¶
Each reader has a short path through the site; see Learning paths.
| You are… | Start with |
|---|---|
| Deciding whether to adopt it | Why a score isn't enough, then Naive vs grounded |
| Wiring it into a service | Install, Your first decision, Build a decision service |
| Owning the decisions (fraud, clinical, ops, moderation) | Why a score isn't enough, then your domain in the Example library |
| Bringing your own data and models | From your data to evidence, Learn parameters from your history |
| Interested in the mathematics | Theory overview |
| Changing the library | Development and automation |
Sections of this site: Get started (tutorials), Concepts (how it works and why), How-to guides (specific tasks), Examples (18 domain cases and more), Theory (the results behind the checks), and Reference (API, command line, glossary).