Skip to main content

Scoring, Normalization & Thresholds

This document explains how Alligator scores candidates, normalises them, and decides whether a cell is matched.


Pipeline overview​

ML model prediction  →  raw score  →  normalisation  →  match decision

The rerank stage is the final stage that produces the scores and match flags stored in MongoDB and returned by the API.


1. Raw ML score​

The ML model outputs a raw probability in [0, 1] for each candidate, representing how likely that candidate is the correct entity for the cell.

raw_score = model.predict(features)[:, 1]   # class-1 probability

Each candidate's raw score is preserved in raw_score before any further transformation so it can be recovered if needed.


2. Per-cell score normalisation​

All candidates within the same cell are normalised relative to the top candidate in that cell:

norm_score = raw_score / max_raw_score_in_cell
candidateraw_scorenorm_score
best0.801.00
second0.640.80
third0.240.30

Why relative normalisation? The absolute raw score is model-dependent and hard to threshold universally. Relative normalisation expresses each candidate's confidence as a fraction of the best available option, making MATCH_THRESHOLD interpretable as "accept if at least X% as confident as the top candidate".


3. RAW_MIN_CONFIDENCE — the confidence floor​

Environment variable: RAW_MIN_CONFIDENCE (default 0.1)

Before normalisation, the raw scores are checked against this floor:

if max_raw_score < RAW_MIN_CONFIDENCE:
all norm_scores = 0.0 # cell is below confidence floor
score = raw_score # restore raw score for display
match = False for all # no candidate is accepted

Purpose: guard against cells where the model is universally uncertain. Even if a candidate would technically win the relative comparison, the model may have very low absolute confidence for the entire cell (e.g. a vague or ambiguous mention). When max_raw < RAW_MIN_CONFIDENCE the cell is treated as unresolvable: candidates are still returned for human review but the top candidate is not auto-matched.

Tuning guide​

ValueEffect
0.0Disable the floor entirely — every cell is eligible for auto-matching
0.1 (default)Require at least 10% raw confidence before auto-matching
0.3Stricter — only high-confidence cells are auto-matched

4. MATCH_THRESHOLD — the acceptance threshold​

Environment variable: MATCH_THRESHOLD (default 0.5)

Applied to the normalised score of the top candidate:

top_auto_match = norm_score_of_top >= MATCH_THRESHOLD

Because the top candidate always has norm_score = 1.0 (if above the confidence floor), this threshold by itself would always match the top candidate. Its real role becomes apparent when combined with MATCH_MARGIN_DELTA.

Tuning guide​

ValueEffect
0.5 (default)Accept if the top candidate's norm score ≥ 0.5
0.9Only accept when the top candidate dominates the cell clearly
1.0Only accept when the top candidate is the single candidate above the floor

5. MATCH_MARGIN_DELTA — the margin guard​

Environment variable: MATCH_MARGIN_DELTA (default 0.1)

An alternative accept condition based on how far ahead the top candidate is from the second:

top_auto_match = (norm_score_top >= MATCH_THRESHOLD)
OR (norm_score_top - norm_score_second >= MARGIN_DELTA)

Purpose: catch cases where the top candidate has a slightly lower normalised score (e.g. 0.45, just below the threshold) but is clearly better than the runner-up (e.g. second at 0.10, margin 0.35 >> 0.1). Without the margin guard those cells would be left unmatched despite an unambiguous winner.

Tuning guide​

ValueEffect
1.0Disable the margin guard — only MATCH_THRESHOLD decides
0.1 (default)Accept when the top candidate leads by ≥ 10 percentage points
0.5Only accept when the top candidate is dramatically ahead

6. Match flag assignment​

Only the top candidate can ever receive match = True. All other candidates always receive match = False.

for cand in cands_to_save:
cand["match"] = False
cands_to_save[0]["match"] = top_auto_match # True only if conditions above are met

7. Full decision flowchart​

For each cell:
│
├─ max_raw < RAW_MIN_CONFIDENCE?
│ ├─ YES → all norm_scores = 0, restore raw scores for display
│ │ match = False for all candidates ← STOP
│ └─ NO → continue
│
├─ normalise: norm_score = raw_score / max_raw
│
├─ top_auto_match = (norm_top >= MATCH_THRESHOLD)
│ OR (norm_top - norm_second >= MARGIN_DELTA)
│
├─ all candidates: match = False
└─ top candidate: match = top_auto_match

8. Environment variable summary​

VariableDefaultDescription
RAW_MIN_CONFIDENCE0.1Minimum raw ML score the best candidate must reach for the cell to be eligible for auto-matching
MATCH_THRESHOLD0.5Minimum normalised score the top candidate must have to be auto-matched
MATCH_MARGIN_DELTA0.1Alternative accept condition: top candidate is auto-matched if its normalised score exceeds the second by at least this delta

All three can be set in the Docker environment (e.g. docker-compose.yml) or as regular environment variables before starting the service.