Project: Document Processing Agent
Capstone three: PDFs in, structured fields out, low-confidence cases to humans, results into your systems — extraction at scale with honesty built in.
▶ Watch this reelWhat you'll learn
- Field extraction
- Validation & exception queue
- Integration writes
- The whole loop
Remember this
- Extraction is contract-driven: typed schemas with code validators, two-pass extraction with text-support verification, confidence computed from independent signals
- The exception queue routes by computed confidence and serves side-by-side review packages; corrections feed the golden set and pipeline improvements
- Integration writes are idempotent upserts with per-record results, full provenance, and reversibility; the whole loop is a content-agnostic template for every extraction problem
Extraction
- Parse layer (pdfplumber/OCR) upstream · typed schema contract (Pydantic + code validators).
- Two-pass: extract then verify text-support · confidence computed (validation + verification + parse quality).
Exception queue
- Computed-confidence routing · side-by-side packages with per-field issues · corrections → golden cases + pipeline fixes.
- Metrics: exception rate, issue classes, correction rate · sampled audit of auto-accepts.
Integration
- Idempotent upserts (natural keys) · per-record results · full provenance audit · reversibility.
- Queue-driven throughput · daily reconciliation (in = out + queued).
Template
- Content-agnostic: parse / contract / confidence layers · instantiate per document type (claims, onboarding, filings).
- Build order: parse → one schema E2E → confidence tuning → review UI → writes + reconciliation → evals.
Code: The extraction loop, one function
async def process_document(pdf_path: str) -> Route:
pages = parse_layer.extract(pdf_path) # text + quality flags
record, issues = await extract_and_verify(pages)
conf = confidence(record_valid=record is not None,
verify_issues=issues,
parse_quality=pages.quality)
if conf >= AUTO_ACCEPT and record:
result = await systems.upsert(record, key=record.natural_key())
audit.log(hash(pdf_path), record, conf, result)
return Route("written", result)
package = review_package(pdf=pdf_path, record=record,
issues=issues, confidence=conf)
await exception_queue.push(package) # human decides
audit.log(hash(pdf_path), record, conf, "queued")
return Route("review", package.id)
# Invariants: idempotent (natural keys) · complete (reconciled counts) ·
# provenance (audit from byte to row) · learning (corrections → gold).