A document pipeline that survives contact with real PDFs. Extraction is the easy part; validation is what earns trust, so this does both and routes anything doubtful to a human with the evidence attached.
$ docpipe run ./inbox --out ./out
processed 4 documents: 2 ok, 2 exceptions → out/exceptions.md
$ docpipe eval ./inbox ./labels.json
documents 3 · type accuracy 100%
field precision recall n
due_date 100% 100% 2
invoice_number 100% 100% 2
subtotal 100% 100% 2
total 100% 100% 3
- Classify the document type (invoice, purchase order, …) and refuse to guess below a confidence floor.
- Extract to a strict schema: typed fields, each with a confidence and the page it came from.
- Validate: required fields, formats (regex), types (numbers, dates in any common format), confidence floors, and cross-field rules like
total == subtotal + taxwith a tolerance. - Route: records that pass go to
records.jsonl/summary.csv; anything with an error goes toexceptions.mdfor a person, with the extracted values and page numbers. - Measure:
docpipe evalscores per-field precision/recall against your labels, so you know the numbers before it touches production.
uv tool install "docpipe[anthropic]" # or pipx
export ANTHROPIC_API_KEY=... # omit to use the offline regex extractor
docpipe run ./pdfs --out ./out # built-in invoice / purchase-order schema
docpipe --schema schema.yaml run ./pdfs # your own document typesmin_confidence: 0.6
doc_types:
invoice:
keywords: [invoice, "amount due", "bill to"]
fields:
invoice_number: {type: string, required: true, regex: "^[A-Z]{2,5}-\\d{3,8}$",
patterns: ["Invoice\\s*#?\\s*([A-Z]{2,5}-\\d{3,8})"]}
subtotal: {type: number, required: true, patterns: ["Subtotal:?\\s*\\$?([\\d,]+\\.\\d{2})"]}
tax: {type: number, default: 0, patterns: ["Tax:?\\s*\\$?([\\d,]+\\.\\d{2})"]}
total: {type: number, required: true, patterns: ["\\bTotal(?: Due)?:?\\s*\\$?([\\d,]+\\.\\d{2})"]}
due_date: {type: date, required: true, patterns: ["Due(?: Date)?:?\\s*(\\S.+)$"]}
rules:
- {expr: "total == subtotal + tax", tolerance: 0.01, message: "total must equal subtotal + tax"}
- {expr: "total > 0", message: "total must be positive"}patterns drive the offline extractor and double as a cheap sanity check next to the model. Rules are tiny arithmetic/comparison expressions over numeric fields; nothing else is evaluated.
anthropic: Claude returns JSON with a quote per field; the quote is used to locate the page.rule: regex patterns from the schema. Deterministic; used for tests, CI and quick baselines.
Scanned PDFs have no text layer: run OCR first (or add an OCR loader) and feed the text in.
uv venv && uv pip install -e ".[dev]" && .venv/bin/python -m pytest -qThe tests build a real single-page PDF in memory and run it through the whole pipeline.
MIT © Fady Youssef