"""Confidence-band classification — single source of truth for the cut points. The workbook colours and the review queue MUST agree about what counts as red. If each applied its own threshold they would drift the first time one is tuned, and a reviewer would see a red cell the queue never asked them to check. Everything that needs a band imports from here. """ from __future__ import annotations from typing import Optional # --------------------------------------------------------------------------- # Cut points # --------------------------------------------------------------------------- # Calibrated against the measured distribution over all 225 extracted rows of # the six sample documents: # # 1.00 69.8% crisp print # 0.90-0.99 18.2% crisp print # 0.80-0.89 11.0% almost entirely handwritten CPT and modifiers # 0.70-0.79 0.0% <- empty; the natural gap in this data # <0.70 1.1% handwritten modifiers # # 0.80 left only 1.1% of cells red so nearly everything went green and the # colour carried no information. 0.90 sits in the empty band and separates # the handwriting cluster from printed text. CONFIDENCE_THRESHOLD: float = 0.90 # Above CONFIDENCE_THRESHOLD the 0.90-0.99 band is still 18.2% of cells, and # routing treats it differently from a clean 1.00: reviewers sample it rather # than either trusting it outright or checking every one. A second cut at # 0.95 names that band so the sheet shows what the routing rule already assumes. # # GREEN >= 0.95 clean print, straight through # AMBER 0.90-0.95 slightly unclear, sampled # RED < 0.90 handwriting cluster, always reviewed GREEN_THRESHOLD: float = 0.95 # --------------------------------------------------------------------------- # Band name constants # --------------------------------------------------------------------------- BAND_GREEN = "green" BAND_AMBER = "amber" BAND_RED = "red" def band_of(score: Optional[float]) -> Optional[str]: """Return the confidence band name, or ``None`` when the value was never scored. WHAT THE BAND CLAIMS: green means the source text this value came from was transcribed cleanly. It does NOT mean the value is correct. The score is the model's own legibility rating, so a confident misread comes back scored high. Verifying correctness needs a second, independent read. Red is the useful direction: it reliably surfaces handwriting. Amber does not mean "probably fine". It means the reading was good enough to escape the handwriting cluster but not clean enough to trust unsampled, which is a routing decision, not a quality claim. """ if score is None: return None if score >= GREEN_THRESHOLD: return BAND_GREEN return BAND_AMBER if score >= CONFIDENCE_THRESHOLD else BAND_RED