Felipe Figueiredo
← All Projects

Project

BlackLetter

Contract review split in two: deterministic rules for the black-and-white, a model for the judgment calls — every finding says which half produced it.

Next.jsReact 19TypeScriptTailwind 4FastAPIPythonPyMuPDFPydanticGeminiClaudeDockerVercel
BlackLetter

Reviewing a contract is two different jobs wearing one name. Some of it is mechanical — is the governing law on the approved list, is liability capped, is anything still blank at signature. Some of it needs judgment — is this indemnity one-sided, does this clause quietly contradict that one. Most tools in this space point a language model at the whole document and get back confident findings nobody can verify. BlackLetter splits the work and uses a different tool for each half.

Technical Decisions

Deterministic Engine First

Seven rules match literal text — governing law, liability cap, payment terms, auto-renewal notice, confidentiality term, unfilled placeholders, assignment on change of control. They are driven by a YAML playbook rather than code, so changing a position means editing configuration, not the engine. They cannot hallucinate, they return the same output on the same input, and each finding cites the exact words that triggered it. run_rules isolates every rule: one crashing regex costs one rule's findings, never the review.

A Model That Cannot Free-Hunt

Three judgment calls go to a model, and only over candidates a deterministic prefilter already selected. It confirms or rejects each one and must return a reason, which is rendered on the finding so a reviewer can check the model's work instead of trusting it. On the sample contract it caught the one-way indemnity in Clause 10, flagged an asymmetric termination right in Clause 11 that was not planted, and correctly declined a contradiction candidate — the clause carves itself out of the liability cap explicitly rather than contradicting it in silence.

Decoupled by Design

The deterministic findings are computed and committed before the model is ever called. No key, an API error, a refusal, output that fails schema validation, a verdict pointing at a candidate that does not exist — every failure path degrades to returning nothing rather than failing the review. In legal work a slow answer is survivable; a silent miss is not.

Geometry That Makes Highlights Honest

PyMuPDF assembles text word by word, so every character offset maps back to a rectangle on a page. Rules match on readable text — Net 60, not PDF drawing operators — and doc.locate(start, end) resolves the match to the boxes to highlight. The frontend converts those PDF points to a percentage of the page box, so overlays stay aligned at any container width with nothing recomputed on resize.

Noise Control You Can Audit

Duplicates merge across pages; low-confidence findings are marked, never deleted. That is what makes the confidence slider meaningful — dragging it down reveals what the engine filtered instead of hiding it. A reviewer who cannot audit the filter cannot trust the list.

Provider-Agnostic Judgment Layer

The adjudication question is not vendor-specific, so the provider sits behind a small interface with Gemini and Claude implementations. Both hand a Pydantic schema to the decoder rather than describing JSON in the prompt and hoping — asking a model nicely for JSON produces JSON most of the time, which is the worst kind of reliability.

A Demo That Costs Nothing to Serve

The hosted demo is precomputed: the analysis ran once and ships as static JSON, so it loads instantly, makes no model calls, and offers a visitor no way to spend the budget. The live upload path is in the repo behind a feature flag, with Turnstile, per-IP and global caps, magic-byte validation and a token ceiling — and the code says plainly that in-process counters are defence in depth, not the guarantee. The only limit a bug cannot bypass is a spend cap in the provider console.

Where It Came From

Twelve years practising law before engineering is where the question came from: which parts of this work are mechanical, and which actually need judgment. The answer turned out to be an architecture. 65 tests cover the rules, the degradation guarantees, provider selection, and the cost guards.