"""Confidence bands: the single source of truth for the cut points. Copy this file into the backend. Everything that needs a band (the review queue, an exported sheet, the approve gate) imports from here, so they never disagree about what counts as red. GREEN >= 0.95 clean print, straight through AMBER 0.90-0.95 slightly unclear, sampled RED < 0.90 handwriting or a messy scan, always reviewed BLANK a mandatory field with no value, always flagged UNSCORED no word scores came back for the value Calibration (from the prototype these rules came from): crisp print scored 0.90 and up, handwriting clustered in 0.80-0.89, and 0.70-0.79 was empty. 0.90 sits in that gap. 0.95 splits off the 0.90-0.99 band that reviewers sample rather than trust. What a band claims: the score is the model's own legibility rating. Green means the text was read cleanly, NOT that the value is correct; a confident misread scores green. Red is the useful direction: it reliably surfaces handwriting. """ from __future__ import annotations from typing import Iterable, Optional GREEN_THRESHOLD = 0.95 CONFIDENCE_THRESHOLD = 0.90 BANDS = ("green", "amber", "red", "blank", "unscored") def band_of(score: float) -> str: if score >= GREEN_THRESHOLD: return "green" return "amber" if score >= CONFIDENCE_THRESHOLD else "red" def field_band(word_scores: Optional[Iterable[float]], value: Optional[str], mandatory: bool = False) -> str: """A field's band. It takes its WORST word score, so one shaky word can't hide in a clean-looking field. A blank mandatory field is flagged whatever its score.""" if mandatory and not (value or "").strip(): return "blank" scores = list(word_scores or []) return band_of(min(scores)) if scores else "unscored" def needs_review(band: str) -> bool: """Every band but green must be verified by a person before the run can be approved.""" return band != "green"