Skip to content

Extraction quality

Whether a text is the output of an extraction that worked. See Extraction quality.

vectrixdb.quality

Is this text the output of an extraction that worked?

A scanned PDF that OCRs badly produces chunks that pass every schema check and destroy retrieval: the fields are all there, the text is nonsense, and nothing downstream can tell. This is closer to signal reliability than to validation, and the roadmap held it back until it could be measured against a labelled set rather than shipped as a heuristic nobody trusts.

So it is measured. :func:extraction_quality scores a text on five signals that OCR failure moves and clean prose does not, and the default threshold comes from :func:calibrate run over a labelled set: real prose from this repository's own documentation, and the same prose degraded by a simulator of the substitutions, joins and glyph noise OCR actually produces, at a range of noise levels. scripts/quality_eval.py rebuilds that set and prints the separation; the numbers it printed when the threshold was set are in the docstring of :data:DEFAULT_THRESHOLD.

What it is not: a language model, a dictionary, or a judgement about meaning. A page of clean text about nothing scores well. What it catches is the failure that reads like a permissions problem or a ranking problem when it is an ingestion problem, and it says so at the write, where the stack trace points at the pipeline.

DEFAULT_THRESHOLD = 0.78 module-attribute

ExtractionQuality dataclass

The score, the signals it came from, and the verdict at a threshold.

score is in [0, 1], higher is cleaner. signals are the five components, each in [0, 1], so a low score can be read: a text that is all symbols fails plain_characters; one that is words nobody has seen fails known_words; one cut into fragments fails whole_words.

Calibration dataclass

What a labelled set says about a threshold.

extraction_quality(text, threshold=DEFAULT_THRESHOLD)

Score a text's prose on five signals OCR failure moves and clean prose does not, scaled by a sixth.

A table's rows are not prose. Region: EMEA; Revenue: 1200 has more figures than words, repeats its column names on every line and uses no common words, which is everything a failed OCR pass looks like, and a clean spreadsheet scored 0.20 against a line of 0.78. So the lines a reader writes for rows, and its figure lines, are set aside, and what is left is judged. A text that is nearly all rows has too little prose to judge, and is usable, as a two-word heading is.

calibrate(labelled, threshold=None)

Measure the detector against (text, is_clean) pairs.

With threshold given, reports precision and recall at it. Without, picks the threshold that maximises balanced accuracy over the set, which is how :data:DEFAULT_THRESHOLD was chosen. Precision and recall are for the clean class: precision is how often "usable" is right, recall is how much clean text is kept.

degrade(text, level, seed=0)

Put text through a simulation of a bad OCR pass.

level is the share of characters touched, from 0 (untouched) to 1. Each touched character is swapped for a shape-alike, dropped, joined to its neighbour by losing the space, or replaced with glyph noise, in the proportions those failures show up in real output. Deterministic for a given seed, so a labelled set can be rebuilt.