ContractAI
Know what every contract says, when it renews and where the risk is, with a quote behind every answer.
Abstracts commercial contracts into parties, dates, money, renewal terms and risky clauses, with every value tied to a quote and portfolio views for renewals and risk.

The problem
In-house legal teams and contract managers hold contracts as documents, not data. Finding out which agreements auto-renew next quarter, what notice is needed to stop them, or where liability is uncapped means someone rereading PDFs. Missing a notice deadline quietly commits the business to another term.
An AI summary alone does not fix this, because a confident but wrong renewal date is worse than no date at all.
My approach
I first built ContractAI as a standalone prototype: a FastAPI backend with a React front end and a Power Apps code app, extracting a structured analysis per contract and building renewals and risk views from it. That proved the idea but tied contract logic into several services and shipped with a login stub.
I then ported it onto my Document AI chassis as a single document type package:
- Schema with citations. Every field, party, value, renewal term and key clause extends a cited model, so each carries a page and verbatim quote that is checked against the OCR text.
- Weighted scoring. Twelve field rules, four of them required (title, effective date, parties and governing law), decide the confidence tier. A contract missing a required field always goes to review.
- Portfolio insights with no AI calls. A renewals calendar works out the notice deadline from the renewal date and notice period and flags anything overdue or due in the next 90 days; a risk register lists high and medium risk clauses with their citations.
- Synthetic samples, including a deliberately incomplete contract to prove it lands in review.
Architecture
ContractAI is a package inside the chassis: a Pydantic schema, a prompt, a local extractor for the synthetic samples, scoring rules, insights and a UI layout. The chassis handles upload, OCR, extraction through Azure AI Foundry or the local provider, citation checks, scoring, review, audit and Q&A. The insights functions read only stored extractions, so the renewals calendar and risk register cost nothing to refresh.
Key decisions
- 01
Port the prototype onto a shared chassis
- Context
- The original prototype spread contract logic across services and had an open API with a login stub.
- Decision
- Rebuild ContractAI as a document type package so it inherits citations, tiers, review, auth, audit and Q&A.
- Trade-off
- Features from the first prototype, such as generated risk memos, are not yet carried across.
- 02
Required fields drive the review tier
- Context
- A contract without a title, effective date, parties or governing law cannot be relied on, however confident the rest looks.
- Decision
- Mark those four fields required so their absence always means review, alongside weights for the rest.
- Trade-off
- Some genuinely unusual contracts will always need a person to look.
- 03
Insights with no AI calls
- Context
- Renewals and risk views are read often and must agree with what reviewers approved.
- Decision
- Build them as pure aggregation over stored extractions.
- Trade-off
- Insights only change when documents are reprocessed or corrected.
Code highlights
Renewals calendar with notice deadlines
backend/app/doctypes/contract/insights.py
renewal = extraction.get("auto_renewal") or {}
end_date = parse_date(_value(extraction.get("end_date")))
next_renewal = parse_date(renewal.get("next_renewal_date")) or end_date
notice_days = int(renewal.get("notice_to_prevent_days") or 0)
notice_deadline = next_renewal - timedelta(days=notice_days) if next_renewal and notice_days else None
if next_renewal:
action_date = notice_deadline or next_renewal
renewals.append(
{
"case_id": case["id"],
"title": title,
"parties": parties,
"event": "renews" if renewal.get("is_auto_renew") else "ends",
"overdue": action_date < today,
"is_auto_renew": bool(renewal.get("is_auto_renew")),
"renewal_period": renewal.get("renewal_period") or "",
"next_renewal_date": next_renewal.isoformat(),
"notice_to_prevent_days": notice_days,
"notice_deadline": notice_deadline.isoformat() if notice_deadline else None,
"days_until_notice_deadline": (notice_deadline - today).days if notice_deadline else None,
"citation": renewal.get("citation"),
}
)Works out the date by which notice must be given to stop a renewal, and whether that action is already overdue.
This code comes from a private repository. Happy to walk you through it.Request a walkthrough
Outcomes
- 12Scoring rules
4 required: title, effective date, parties, governing law
- 3Synthetic sample contracts
Including one deliberately incomplete contract that lands in review
- 3Contract tests
Extraction with verified citations, the review tier and insights
Gallery
A contract missing its governing law is flagged for review, with each extracted term cited. Renewal and notice deadlines and high risk clauses are pulled together across contracts.
Lessons learned
- Keeping insights as pure functions over stored extractions made them cheap, testable and consistent with what reviewers approved.
- Notice deadlines, not renewal dates, are what people act on, so the calendar sorts and warns on the deadline.
- Porting onto the chassis was mostly deleting code: the contract-specific parts fitted in one small package once the shared pipeline existed.
Related
More work like this.
Building My Personal Website & Brand Platform
I built my personal website as both a learning project and a professional branding tool. This project helped me explore modern frontend technologies like Framer Motion while giving me a platform to present my career, technical projects, and achievements in a sleek, animated interface.
Dataverse Workbench
A locally hosted .NET tool for browsing, validating and importing data in any Dataverse environment my account can reach, built so that a bulk write cannot happen by accident.

AI Invoice Routing for a Multi-Entity Group
A shared finance mailbox received invoices for around 20 legal entities and someone forwarded each one by hand. I designed and built a Power Automate solution that reads the Bill-To on each invoice with Azure Document Intelligence, matches it against an editable routing table and forwards it to the right Business Central inbox, with an exception queue and a full audit trail.
Want to go deeper?
Happy to talk through how this was built.
The decisions, the trade-offs and the parts I would do differently next time.