Read first

Start Here

A minimal but real contract-intelligence pipeline. Build it end-to-end in ~3 hours. Designed to be a foundation you can extend toward a production system — not a toy.

What you'll build

By the end of this guide you'll have:

  • A folder-watching ingestion job that picks up PDFs, computes a stable document_id, and persists file metadata to Postgres.
  • An extraction pipeline that OCRs scanned PDFs (or parses native ones), then calls an LLM with a structured-output prompt to extract parties, effective date, renewal terms, and obligations.
  • A Postgres schema that follows the contract data model — documents, agreements, obligations, suppliers — with provenance and confidence on every extracted field.
  • An eval harness — small gold set, scoring script, Markdown report. Run before every prompt or model change.
  • A FastAPI service exposing the extracted data: list agreements, fetch with obligations, search by supplier.

It's deliberately minimal. The point is to build the full loop once so you understand each piece, then extend toward production where it matters.

Prerequisites

  • Python 3.11+.
  • Docker — for Postgres in a container. No need to install Postgres natively.
  • Anthropic API key (Claude) or OpenAI API key. The code shows Claude by default; swapping is one config line.
  • Tesseract — for OCR on scanned PDFs. Install with brew install tesseract (macOS) or apt install tesseract-ocr (Linux). Optional if all your sample PDFs are native.
  • A handful of sample contracts — 5 to 10 PDFs work. Public MSA templates from law-firm websites are fine for a first pass. Real customer contracts are not appropriate to use here.
  • Comfort with Python, FastAPI, SQLAlchemy, and pytest. You don't need to be an expert; the code is annotated.
Don't use real customer data

This is a learning project. Use public templates, anonymized samples, or synthetic contracts. Customer contracts contain confidential terms, PII, and regulated data — not appropriate for a tutorial codebase.

The shape of the project

repo layout
contract-intelligence/
├── docker-compose.yml          # Postgres
├── pyproject.toml              # dependencies
├── README.md
├── data/
│   ├── inbox/                  # drop PDFs here
│   └── gold/                   # eval gold set
├── src/
│   └── ci/
│       ├── __init__.py
│       ├── config.py
│       ├── db.py               # SQLAlchemy session
│       ├── models.py           # SQLAlchemy ORM
│       ├── ingestion.py        # folder watcher
│       ├── extraction/
│       │   ├── ocr.py
│       │   ├── llm.py          # Claude / OpenAI extractor
│       │   └── schemas.py      # Pydantic output schemas
│       ├── eval/
│       │   ├── runner.py
│       │   └── scoring.py
│       └── api.py              # FastAPI app
└── tests/
    ├── test_ingestion.py
    ├── test_extraction.py
    └── test_api.py

By the end of chapter 06 every file in this layout will exist with working code. Chapter 07 is extensions — where to grow from here.

How to use this guide

Each chapter is one focused build step with code you can copy. The pattern:

  1. Read the chapter through once before typing.
  2. Copy the code as written. Don't refactor on the first pass.
  3. Run the verification commands at the end of each chapter — they confirm the step works before you proceed.
  4. If something breaks, the chapter ends with a short troubleshooting list for the most common issues.
  5. Once the whole pipeline works end-to-end, return to chapter 07 to pick a direction to extend.

Time budgets are noted on each chapter. The full sequence is ~3 hours for someone comfortable with the stack; longer if you stop to deeply understand each piece.

Caveats & non-goals

  • Not production-ready. It's a learning project. No auth, no multi-tenant, no proper observability, basic error handling. The extensions chapter (07) points at what to add.
  • Not a benchmark. The eval harness is illustrative. Production eval needs real customer-grade ground truth and statistical rigor — covered in Document AI Extraction §08.
  • Single-customer. One Postgres, one schema. Multi-tenancy is a non-trivial extension; not included here.
  • One model family. Code uses Claude; OpenAI swap shown as a config change. We don't compare model performance — that's not the point.
  • Not the only way. Choices made here (FastAPI, SQLAlchemy, Tesseract) are sane defaults, not the only sane choices. The architecture is the point; the libraries are interchangeable.