Skip to content

Configuration reference

Every knob the cascade has, with the measurement that fixed its default.

This page is generated from the pydantic model during the site build, by docs/hooks/config_reference.py. Each description below is the field's own description= string, copied verbatim — there is no second place where a threshold is explained, so there is no second place for one to go stale.

Config is frozen and rejects unknown keys (extra='forbid'): a typo in a field name is a ValidationError at construction rather than a setting that silently does nothing. To vary one field, use config.model_copy(update={...}).

No field points at a network

There is no host, port, URL or credential on this model, and tests/unit/test_config.py::test_no_field_points_at_a_network is what keeps it that way. All networking lives in the constructor of a step somebody wrote by hand — see ADR 0001.

from autosxtract import Cascade, Config

cascade = Cascade(Config(dpi=150, min_agreement=0.60, engines=["paddle"]))

Step order

Which steps the cascade assembles, and in what order.

engines

list[str] | None · default None

Explicit order of OCR engines. None lets the platform decide: Vision first on Apple hardware, PP-OCRv6 elsewhere.

use_native

bool · default True

Read the PDF's text layer before any OCR. Measured: 31% of a real archive's documents are resolved here, at 13.4 ms — free against the ~400 ms of any OCR.

Rasterising

How a page becomes pixels. Every OCR step downstream is handed the result of these three, and is handed the same result — two engines compared on one document must see identical images, or the difference between them is preprocessing noise.

dpi

int · default 150 · ≥ 36, ≤ 600

Render resolution for OCR. 150 is the minimum that preserves numeric anchors: at 100 DPI preservation falls to 85.5%, losing dates and tax numbers in 35 of 60 documents.

grayscale

bool · default True

Rasterise in a single channel. The resolution does not change — only chrominance is discarded, and a scanned document carries almost none. It saves ~84 ms per document in the encoder.

max_pages

int · default 64 · ≥ 1

Ceiling on rasterised pages per document.

The acceptance gate

The single criterion, applied by quality/gate.py. The step that thinks it solved the document and the cascade that decides whether to pay for the next one read these same numbers.

min_useful_words

int · default 12 · ≥ 0

Floor of alphabetic words outside the stamp. Below this, what is left is not content. Derived from an audit of 1,339 documents: 403 had text that looked fine and was only the digital signature stamp.

min_chars_per_page

int · default 200 · ≥ 0

Minimum density. A page of text yields hundreds of characters.

min_confidence

float · default 70.0 · ≥ 0.0, ≤ 100.0

Engine confidence floor. It does NOT arbitrate quality — measured on 60 audited documents, OCR confidence does not separate a good reading from an unsafe one. It only guards against degenerate output.

min_score

float · default 0.35 · ≥ 0.0, ≤ 1.0

Below this text-quality score the step is refused.

native_accept_score

float · default 0.75 · ≥ 0.0, ≤ 1.0

Score from which the native text ends the cascade without paying for OCR. It comes with the coverage caveat: a high score describes the text that came out, not the fraction of the page left behind.

Gates between steps

The four that stop the cascade from paying for a step it does not need.

coverage_gate

bool · default True

Refuse the native text when there is a large image in a region with no text. In a filing that embeds an official letter as an image, the native text is flawless and the attachment — the actual content — is never read.

consensus_gate

bool · default True

Declare the page empty when independent engines agree there is no content. One dissenter is enough to escalate.

agreement_gate

bool · default True

Stop when two engines read THE SAME THING — proof that the reading is complete, not that the page is short.

min_agreement

float · default 0.6 · ≥ 0.0, ≤ 1.0

Vocabulary Jaccard from which two readings agree. Calibrated on 24 real escalations: 13 cases landed between 0.00 and 0.48 and 11 between 0.65 and 0.80 — the threshold sits in the gap.

per_page_routing

bool · default True

In a mixed PDF, OCR only the pages without native text. Measured: of the 419 pages that went through OCR on a real case file, 54 (12.9%) already had text.

fix_orientation

bool · default True

Detect and correct a sideways page BEFORE OCR, with Tesseract's OSD (needs the [veto] extra; without it the correction is skipped and the reason goes to the provenance and to diagnose). On by default because a rotated page is read badly by every engine, and everything downstream then judges a bad reading caused by an input defect it cannot see. UNMEASURED on this project's archive: the cost is one OSD pass per rasterised page, so a document resolved by the native text layer pays nothing, and no number here yet says what it buys. Measure with scripts/compare_engines.py before relying on it, and turn it off if your scans are known upright.

Around an expensive step

The five vetoes that run before a step marked expensive, and the replacement gate that judges what it produced.

expensive_step_vetoes

bool · default True

Run the five vetoes before a step marked expensive. Measured on 19 escalated documents: they save 27.2 minutes of the expensive step and the largest content lost is a 124-character stamp.

replacement_gate

bool · default True

Submit an expensive step's text to the anchor and coverage gate before letting it replace what already exists. Length alone gets both of the costliest cases wrong.

veto_engine

str | None · default 'tesseract'

The local engine that acts as WITNESS for vetoes 3 to 5. It must be of a different architecture from those already run — a second engine of the same family is not independent evidence. None turns off the three vetoes that depend on it.

veto_max_pages

int · default 3 · ≥ 1

Pages the witness reads. Measuring 3 already separates the archive.

min_reliable_words

int · default 3 · ≥ 0

Floor of legible words for the witness to declare there IS text. Measured on 489 documents: below 3 the expensive step yielded at most 124 characters; from 7 upwards, at least 257. The band is empty.

rebuild_prose

bool · default True

Join the lines the OCR broke at each visual line of the page. Measured: 85% of the lines do not end in punctuation, and without this the text is a list of fragments, not prose.

Line containment layers

They need an engine that exposes line geometry (read_page). With any other engine the cascade records engine without geometry and moves on — nothing breaks, there is simply no layer.

layers

bool · default True

Contain stamps, signatures and junk line by line. Measured on 895 pages: entity recall 0.902 -> 0.921, median CER 0.132 -> 0.129, and p50 latency FALLS from 298 to 236 ms. 79 pages improve, 4 get worse (all already bad), clean pages are untouched.

layer2

bool · default True

Re-read the targets: rotate the vertical line 90 degrees, crop the dirty line tighter. It costs ~30 ms at p50 and is what recovers the protocol stamp instead of merely marking it.

max_layer2_targets

int · default 10 · ≥ 0

Ceiling on re-reads per page — it bounds the latency tail.

min_layer2_gain

float · default 0.08 · ≥ 0.0

How far the re-reading's score must beat the original's to replace the line. Swapping on a tie is noise.

lexicon

Any | None · default None

The Lexicon legibility is judged against. None uses the built-in one — a floor of legal Portuguese. Building your own from validated texts is measurably better: with a small lexicon, correct text falls into suspect more often (the safe side of the error, but it escalates pages for nothing).

signature_detector

str | None · default None

Path to a YOLO in ONNX for signature detection (Layer 1.5). None leaves only the structural rule, which always runs. Off by default because a model trained on a public signature dataset did NOT transfer: 19% of pages with a detection, most of them false positives on seals, stamps, logos and QR codes.

page_routing

bool · default False

Classify the page type (table / stamped_digital / degraded / normal) and record it in the provenance. It does not change the extraction — it is a signal for whoever consumes it.

Engines and parallelism

The three parallelism fields accept None, meaning decide from this machine, and that is the default: the same library runs on a two-core laptop and a seventy-two-core server, and one fixed number serves both badly. An explicit number is obeyed — what it is not, is a promise.

engine_options

dict[str, dict[str, Any]] | None · default None

Constructor arguments for a SPECIFIC engine, by name. The registry builds engines with no arguments, so everything their constructors accept — PP-OCR's tier, the INT8 flag, the thread count, the preprocessing, the execution providers — was unreachable from an assembled cascade. A knob that cannot be turned from outside is not a knob. {'paddle': {'quantized': True}} asks for INT8 weights; {'paddle': {'det': 'small', 'rec': 'medium'}} picks a tier. Different options are a different engine and load their own model.

engine_parallelism

dict[str, int] | None · default None

Pages in flight for a SPECIFIC engine, by name — the caller's last word over the engine's own statement about itself. An engine declares scales_with_threads = False when it knows a single hardware queue sits behind it (Apple's Neural Engine serves one request at a time), and that declaration is a good DEFAULT because whoever configures the cascade cannot see behind the engine. It is a bad law: it was measured on one machine, and registered engines are built with no arguments, so without this there is no way at all to tune them. {'vision': 4} overrides one; the others keep deciding for themselves.

page_parallelism

int | None · default None · ≥ 1

Threads per document in OCR. None resolves to min(4, cores). PyMuPDF is OUTSIDE this count: it is serialised by a process lock, because it crashes the process under concurrency (segfault in page_get_textpage). An engine with a single hardware queue — Apple's — ignores this number and uses 1: measured from 1 to 12 threads, constant throughput and latency from 430 ms to 3,492 ms.

document_parallelism

int | None · default None · ≥ 1

Documents in flight during batch processing. None resolves to min(4, cores).

concurrency_cap

int | None · default None · ≥ 0

Maximum pages in flight across ALL documents of a batch. None resolves to cores x 2; 0 turns the cap off. It exists because the product documents x pages grows without anyone noticing — 4 x 8 is 32 simultaneous pages, each holding a rendered image and the model's activations. On a small machine the memory limit arrives before the CPU limit.

Domain

The two fields that point at data rather than at a number. This is the adaptation seam: see a new pattern pack.

language

str · default 'pt-BR'

Preferred OCR engine language, and the pack of the pattern catalogue that goes with it. A tag with no bundled pack keeps the default one — which is what the library did before the catalogue existed, when the patterns were Portuguese whatever this said.

patterns

Any | None · default None

The pattern catalogue: a PatternSet, or a path to a TOML file or a directory of them. It overrides the bundled packs ENTRY BY ENTRY, so a pack that redefines one stamp inherits the other sixty patterns and keeps receiving their fixes. None resolves AUTOSXTRACT_PATTERNS, then the pack for language, then the neutral base.

stamps

tuple[str, ...] | None · default None

Stamp patterns (regexes) to strip before measuring the extraction. None takes stamp.conformity from the catalogue — the Brazilian court ones, unless a pack replaced them. It stays a field of its own because it is the override reached for most often: the stamp survives when the body of the page produces nothing, so it has to go before the measurement, and saying so should not require writing a pack.

Methods, not fields

Five things on Config resolve when they are called rather than when the object is built, and the reason is the same for all of them: the machine that resolves may not be the one that serialised the configuration. A preset stored in YAML and used in two environments has to answer differently in each, and a path resolved in the constructor points at a file that is not there.

Config.pattern_set()

The resolved catalogue for this configuration.

Config.stamp_patterns()

The stamp list in force — stamps if given, the catalogue's if not.

Config.pages_in_flight()

Threads per document, already resolved for this machine.

Config.documents_in_flight()

Simultaneous documents in a batch, already resolved for this machine.

Config.batch_concurrency()

(documents, pages) already limited by the aggregate cap.