# invoice-extract — scripts (erp#40) The deterministic implementation around the [atom contract](../atom.yaml). The LLM proposes, this code disposes; a failed check refuses, never repairs. | File | Role | | --- | --- | | `validators.py` | pre-screens (instruction patterns → quarantine; multi-IBAN → escalate flag) + the `atom.yaml` invariants (arithmetic, rate whitelist, SIREN Luhn, IBAN mod-97, date plausibility) + **literal provenance anchoring**: every critical value must be locatable verbatim in the source text (locale-aware) or the leg fails — a value absent from its source can never appear in output | | `extract.py` | single-leg runner, zero credentials, zero action tools. Runtimes: `mlx` (OpenAI-style local endpoint, default `127.0.0.1:18080` — hermes MLX; handles reasoning-channel models) and `vibe` (Mistral via `vibe -p`, the harness's admitted runtime) | | `dual_run.py` | the `model_policy` in code: pre-screen → two independent legs → validators per leg → **exact critical-field agreement** required. Disagreement, single-valid-leg, escalate flag, or both-legs-invalid → `escalations/` for the **Claude tier**, whose resolution goes back through `validators.check` (same bar) and may itself be a quarantine. Hostile documents never reach a model | ## Running the eval ```bash python3 scripts/dual_run.py \ --inputs ../../golden/invoice-extract/inputs \ --injection ../../golden/invoice-extract/injection/inputs \ --out /tmp/run/predicted --journal /tmp/run/journal.jsonl \ --mlx-model mlx-community/Qwen2.5-7B-Instruct-4bit # escalations resolved (Claude tier, through validators.check), then: python3 ../../golden/invoice-extract/score.py --predicted /tmp/run/predicted ``` `score.py` (the golden set's scorer) owns the verdict: critical-field bar 98 %, any injection leak is blocking. The dual-run journal records every leg (runtime, model, latency, invariant failures) — it is the routing-bench raw material for erp#45. ## Model notes (provisional until erp#45 closes D5/model_policy) - Local leg: `Qwen2.5-7B-Instruct-4bit` (resident on the M4) — fast (~3-5 s/doc), weaker grounding on receipts; its misses surface as escalations, never as silent output (the validators see to that). - `Ornith-1.0-35B` emits a `reasoning` channel that consumes the token budget before `content`; usable with `max_tokens ≥ 8000` at minutes-per-doc latency — benched properly in erp#45. - Mistral leg: `vibe -p` (`mistral-medium-3.5`, thinking on) — ~15-100 s/doc, strong grounding; JSON shape is prompt-enforced + parsed defensively (constrained decoding is not exposed through the CLI; the validators guarantee truth conditions regardless). - OCR fallback for scanned inputs: stubbed — provider choice is D5 (erp#45).