Skip to content
My productPrototypeAI application

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.

ContractAI

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

Drawing the diagram

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

  1. 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.
  2. 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.
  3. 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

python
        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

  • 12
    Scoring rules

    4 required: title, effective date, parties, governing law

  • 3
    Synthetic sample contracts

    Including one deliberately incomplete contract that lands in review

  • 3
    Contract tests

    Extraction with verified citations, the review tier and insights

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.