Skip to content

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 suspicious versus clean). The support check can hold an action the related cases contradict (a clean card 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).