Matthew Loebenstein
Case study Software · 2026

Designing an AI-Powered Contract Platform

A high-level design for an internal platform that assembles, versions, and queries legal contracts with AI. Written at a previous employer, never presented.

// Context

I wrote this HLD at a previous employer (v1.0, April 2026). The project was shelved before I ever got to present it, so it's been sitting in a drawer since. The part I still like: contracts stored as versioned clause collections instead of monolithic documents, with the AI boxed in — read-only SQL, human review before anything goes out, everything logged.

Company and customer names are stripped out. The rest is as I wrote it.

Introduction

Purpose

This document describes the high-level design for an internal platform for managing, constructing, and querying high-value supply agreements. The system handles the full lifecycle of MESA (Master Enterprise Services Agreement), CESA (Contracted Enterprise Services Agreement), and Payment Guarantee contracts between the company and its customers.

Scope

The platform covers contract template management with versioning, customer-specific deal creation and variable capture, AI-powered contract assembly and document generation, natural language querying of contract data, and an API layer for integration with external settlement and portfolio systems.

Definitions

Term Definition
MESA Master Enterprise Services Agreement – the overarching framework agreement between the company and a customer
CESA Contracted Enterprise Services Agreement – the specific supply terms linked to a MESA
Payment Guarantee A bank-issued guarantee securing the customer's payment obligations under the CESA
Deal Variables Customer-specific values (e.g. company name, tariff rates, dates) inserted into contract placeholders
Base Template A versioned master copy of a contract type from which customer contracts are derived
Clause A discrete text block within a contract template, stored independently in the database

System Overview

Architecture summary

The platform is a Django web application backed by PostgreSQL. It follows a layered architecture with a clear separation between the presentation layer (design system UI), application logic (Django views and services), the AI orchestration layer (LLM integration), and the data layer (PostgreSQL).

The system comprises five primary subsystems working together:

  • Contract Template Manager. Maintains versioned base templates (MESA, CESA, Payment Guarantee) as clause collections with unique, semantic placeholder keys (e.g. {{customer.company_name}}).
  • Deal and Customer Manager. Manages leads, customer records, and the deal-variable capture workflow that converts a lead into a fully populated contract.
  • Query Assistant. Translates natural language questions into read-only SQL via an LLM and returns results from the contract database. This is the only AI integration point; no contract text is sent to the AI.
  • Document Renderer. Assembles contracts from retrieved clauses and deal variables, rendering the merged text into a pre-styled .docx template with python-docx. No AI involvement in producing contract text.
  • External API Layer. Serves customer and contract data to downstream systems such as settlements, portfolio management, and reporting via a RESTful API.

Technology stack

Layer Technology Notes
Backend framework Python / Django Django REST Framework for API endpoints
Database PostgreSQL Relational storage for templates, clauses, variables, and customer records
AI integration LLM API (provider-agnostic) Natural language to SQL query generation only
Variable merging Jinja2 Clauses stored as Jinja2 templates, rendered against deal variable values
Document generation python-docx Renders merged clause text into a pre-styled .docx template
Frontend Django templates Server-rendered UI following the company's internal design system
Deployment TBC Containerised deployment; exact infrastructure to be confirmed

Data Model

The PostgreSQL schema is organised around the following core entities.

Contracts are stored as collections of text blocks (clauses) rather than monolithic documents — enabling granular versioning and AI-driven assembly.

BaseTemplate

Represents a contract type (MESA, CESA, or Payment Guarantee). Each base template has a name, type, and a set of versioned clause collections. When a base template is updated, a new version is created. All existing customer contracts linked to a specific version are affected by changes to that version, enabling controlled cascade updates.

FieldTypeDescription
idUUID (PK)Unique identifier
nameVARCHARTemplate name, e.g. "Base MESA"
typeENUMMESA | CESA | PAYMENT_GUARANTEE
created_atTIMESTAMPCreation timestamp
updated_byFK → UserLast editor

TemplateVersion

A specific version of a base template. Contains an ordered list of clause references. The naming convention is [Type][Version Name][Version Number], e.g. "MESA Standard v3". When a base template is updated, a new TemplateVersion is created. Existing contracts reference a specific version, so updates cascade only through the linked version.

FieldTypeDescription
idUUID (PK)Unique identifier
template_idFK → BaseTemplateParent template
version_numberINTEGERAuto-incrementing version
version_nameVARCHARHuman-readable name
is_activeBOOLEANWhether this is the current active version
created_atTIMESTAMPWhen this version was created
notesTEXTChange log / release notes

Clause

A discrete text block within a contract. Clause bodies are stored as Jinja2 templates with unique, semantic variable keys, e.g. {{customer.company_name}} or {{cesa.tariff_schedule}}. Each clause belongs to a section and has an order within that section. Clauses can reference variables that must be populated during deal creation.

FieldTypeDescription
idUUID (PK)Unique identifier
template_version_idFK → TemplateVersionWhich version this clause belongs to
section_nameVARCHARSection heading, e.g. "Part 1 · Clause 2.8"
section_orderINTEGEROrder of section within the contract
clause_orderINTEGEROrder within the section
bodyTEXTClause text as a Jinja2 template with {{variable.key}} placeholders

Customer

A customer record created when a lead is converted into a deal. Stores all customer-specific information and serves as the parent for all contracts belonging to that customer. This record is served via API to external systems.

FieldTypeDescription
idUUID (PK)Unique identifier
company_nameVARCHARRegistered legal name
registration_numberVARCHARCIPC registration number
physical_addressTEXTDomicilium address
emailVARCHARContractual email
lead_idFK → LeadOriginating lead
created_atTIMESTAMPRecord creation timestamp

CustomerContract

Links a customer to a specific template version and stores the resolved variable values. When a user saves deal variables for a customer, the system creates a new CustomerContract record and a corresponding set of DealVariable records. The contract can be either linked (inherits updates from the base version) or cloned (frozen snapshot that does not receive base updates).

FieldTypeDescription
idUUID (PK)Unique identifier
customer_idFK → CustomerOwning customer
template_version_idFK → TemplateVersionSource template version
is_clonedBOOLEANIf true, this contract is frozen and unaffected by base updates
statusENUMDRAFT | ACTIVE | EXPIRED | TERMINATED
versionINTEGERContract version (increments on variable changes)
created_atTIMESTAMPCreation timestamp

DealVariable

Stores a specific variable value for a customer contract. Each row maps a placeholder name to its resolved value. When variables are updated, the system creates a new version of the CustomerContract while preserving the previous version for audit.

FieldTypeDescription
idUUID (PK)Unique identifier
customer_contract_idFK → CustomerContractParent contract
variable_nameVARCHARUnique semantic key, e.g. "customer.company_name"
variable_valueTEXTResolved value
variable_typeENUMTEXT | NUMBER | DATE | AMOUNT | TABLE | ADDRESS
versionINTEGERTracks which contract version this belongs to

Entity relationship summary

// Entity relationships

BaseTemplate 1—* TemplateVersion 1—* Clause

Customer 1—* CustomerContract *—1 TemplateVersion

CustomerContract 1—* DealVariable

A Lead converts into a Customer, which can hold multiple CustomerContracts (e.g. one MESA and multiple CESAs). Each CustomerContract is either linked to a live TemplateVersion (inheriting updates) or cloned as a frozen snapshot.

Key Features & Workflows

Customer view

The customer view is the primary entry point for managing a customer's contract portfolio. It displays the customer name, all existing contracts (MESAs, CESAs, Payment Guarantees), and a panel for viewing and editing deal variables.

Key interactions: Select a customer to view their contracts. Click a contract to view its current deal variables. Edit variables and save – this creates a new version of both the DealVariables and the CustomerContract. The customer record's active contract version is always set to the maximum (latest) deal version.

Actions toolbar: The view provides a set of actions including view contract, edit variables, duplicate contract, and generate document, with a primary CTA for the main action and secondary or ghost buttons for supporting actions.

New deal screen

When creating a new deal, the user follows a guided workflow:

  • Select a lead from the lead lookup control (autocomplete search).
  • Choose the contract type: select an existing base template (e.g. MESA, CESA 1, CESA 2) and whether to link or clone.
  • The system presents the full list of deal variables for the selected template, grouped by section (matching the contract variable reference structure with types: text, number, date, amount, table, address).
  • The user inputs values for each variable. Form validation enforces correct types and formats.
  • On save: a new Customer record is created (if converting from lead), a CustomerContract is created, DealVariable records are persisted, and the customer record is updated.

Contract assembly

When a user saves deal variables or requests a document, the Document Renderer assembles the contract in two stages. The pipeline runs entirely within the Django application; no contract text or customer data is sent to the AI.

  • Stage 1 — variable merging. The system retrieves all clauses for the contract's linked template version, ordered by section and clause order. Clause bodies are stored as Jinja2 templates with unique, semantic variable keys (e.g. {{customer.company_name}}). The contract's DealVariables are retrieved and loaded into a dictionary, and each clause template is rendered against it. Any unresolved or empty key raises an error, giving the system a built-in completeness check before a document is produced.
  • Stage 2 — document generation. The merged clause text is rendered into a pre-styled .docx template with python-docx. The pre-styled template carries the company's legal document formatting standards (fonts, spacing, headers, page setup), so the output meets the required standard without any formatting logic in the rendering code.
  • Delivery. The generated document is returned to the user for download or further review.

Base template management

The Manage Base Templates screen allows administrators to create, version, and update base contract templates (MESA, CESA, Payment Guarantee).

Template structure: Each base template contains an ordered list of sections, and each section contains ordered clauses. The clause body is stored as a Jinja2 template with {{variable.key}} placeholders. Sections can be reordered via drag-and-drop (up/down controls).

Versioning: When a base template is updated, the system creates a new TemplateVersion. The administrator can view which existing customer contracts are affected by the version change. Updates cascade through all linked contracts for that version. Cloned contracts are explicitly excluded from cascade updates.

Impact analysis: Before publishing a new version, the system displays all affected customers and their contracts. This ensures administrators understand the downstream impact of template changes.

// Example impact report

Customer A — 02.10.24 — version 3

Customer B — 01.06.24 — version 2

Create, New, Update actions: The Create action generates a brand new base template. The New action creates a new version of an existing template. The Update action modifies clauses within the current active version. The New and Update actions are only active when a MESA or CESA base template is selected.

Contract view and AI query

The contract view provides a natural language query capability:

SQL query assistant

Users can ask natural language questions such as "How many contracts have variable X with value Y?" or "Which customer has the highest contracted tariff rate?". The question is passed to an LLM, which generates the correct SQL query, executes it against the PostgreSQL database, and returns the results in a formatted table. This makes contract data queryable without requiring SQL knowledge.

User Interface Design

Screen inventory

Screen Primary purpose Key controls
Customer view View and manage a customer's contract portfolio Customer selector, contract list, variable panel, action toolbar
New deal screen Create a new contract from a lead Lead lookup, template selector (radio), variable input form, save/cancel
Contract view Query contract data with natural language AI query input, contract list table, results panel
Manage base templates Create and version contract templates Template list, section editor, clause text editor, version history, impact panel

Form controls

The variable input forms use typed controls throughout: standard text inputs, select dropdowns for ENUM fields, date pickers for date variables, currency-formatted inputs for ZAR amount fields, and inline table editors for complex table-type variables (such as tariff schedules and time-of-use periods). All inputs include labels and helper text where the variable description provides guidance.

Alerts and feedback

System feedback uses four alert levels: Info for general notifications, Success for completed actions like successful saves, Warning for approaching deadlines or template impact notices, and Danger (used sparingly) for critical actions like version cascade confirmations.

API Design

External API

The external API exposes customer and contract data to downstream systems including settlements and portfolio management. It is a RESTful JSON API built with Django REST Framework, authenticated via API key or OAuth2 tokens.

Key endpoints

Method Endpoint Description
GET /api/v1/customers/ List all customers with pagination and filtering
GET /api/v1/customers/{id}/ Retrieve a single customer record with all deal variables
GET /api/v1/customers/{id}/contracts/ List all contracts for a customer
GET /api/v1/contracts/{id}/ Retrieve a specific contract with resolved variables
GET /api/v1/contracts/{id}/variables/ Retrieve deal variables for a specific contract version
GET /api/v1/templates/ List available base templates and active versions
POST /api/v1/contracts/{id}/generate/ Trigger contract assembly and return the generated .docx

Internal API (AI orchestration)

The internal API handles communication between the Django application and the LLM. This consists of a single endpoint for SQL query generation: it accepts a natural language question and returns a validated, read-only SQL query. Contract assembly and document generation run within the Django application and do not involve the AI layer.

AI Integration Architecture

The system integrates with an LLM at a single point: natural language to SQL translation. The LLM receives the user's question and the database schema, and returns a SQL query. No contract text, clause content, or customer data is sent to the AI. All answers come from the database.

SQL query assistant

When a user types a natural language question in the contract view, the system passes the question along with the database schema to the LLM. The LLM generates a read-only SQL query (SELECT only), which is executed against the PostgreSQL database. The results are returned to the user in a formatted table. All generated SQL is logged for audit purposes. Write operations are explicitly blocked at the database connection level to prevent accidental data modification.

Security and guardrails

  • All AI-generated SQL is restricted to SELECT statements via a read-only database role
  • Contract assembly outputs are reviewed by the user before any external distribution
  • All AI interactions are logged with timestamps, user identity, and input/output payloads
  • The LLM's context is scoped to the minimum data required: the user's question and the database schema

Version Cascade & Template Updates

Linked vs. cloned contracts

When a user creates a customer contract from a base template, they choose between two modes:

Linked: The customer contract references a specific TemplateVersion. When that version is updated (e.g. a clause is amended), the change cascades to all linked contracts. The user is warned of affected contracts before the update is published.

Cloned: The system creates a deep copy of the template version's clauses into the customer contract. Subsequent updates to the base template do not affect cloned contracts. This is appropriate for contracts that have been negotiated with bespoke terms.

Cascade update workflow

  1. Administrator edits a clause in a base template version.
  2. The system queries all CustomerContracts linked to that TemplateVersion where is_cloned = false.
  3. An impact report is displayed showing each affected customer, contract, and the date/version details.
  4. The administrator reviews and confirms the update.
  5. The template version is updated. All linked contracts now reflect the new clause text when next assembled or viewed.

Version numbering

Template versions follow the naming convention: [Type][Version Name][Version Number], e.g. "CESA Standard v3". Customer contract versions are independent integers that increment each time deal variables are updated. The customer record always points to the latest contract version via a computed max(version) reference.

Contract Variable Reference

The system supports 38 contract variables across three document types (MESA, CESA, and Payment Guarantee), grouped into eight logical sections. Each variable has a unique, semantic placeholder key (e.g. {{customer.company_name}}, {{cesa.tariff_schedule}}, {{guarantee.amount}}), a data type (text, number, date, amount, table, address), and an impact classification (high, medium, low) indicating the downstream effect of changes.

The variable reference document (attached separately) provides the complete catalogue. The following table summarises the variable distribution by document type:

Document type Variable count High impact Key sections
MESA + CESA (shared) 5 3 Cover page, party details, signature
MESA 7 2 Conditions precedent, savings protection, threshold values
CESA 18 10 Commercial terms, allocation, tariffs, FX adjustment, power of attorney
Payment Guarantee 8 4 Parties, guarantee amount, validity, bank details

Variables marked as "table" type (such as Time-of-Use Periods, Contracted Tariff Schedule, and Foreign Exchange Rate Table) require a structured inline table editor in the UI, as these contain multi-row, multi-column data that cannot be captured in a simple text input.

Non-Functional Requirements

Category Requirement
Performance Contract assembly and document generation should complete within 30 seconds. SQL query responses should return within 10 seconds for standard queries.
Scalability The system should support up to 500 active customer contracts and 50 concurrent users without degradation.
Audit trail All contract changes, variable updates, template versions, and AI interactions must be logged with timestamps, user identity, and change details.
Security Role-based access control (RBAC) for template management vs. deal creation vs. read-only API access. All AI-generated SQL restricted to read-only operations.
Availability 99.5% uptime target during business hours (07:00–18:00 SAST).
Data integrity Soft deletes only – no hard deletion of contracts or customer records. Full version history preserved.
Compliance South African POPIA compliance for customer personal data handling.
Accessibility WCAG AA compliance, including generous touch targets and focus-visible indicators.

Risks & Open Questions

Risks

Risk Impact Mitigation
SQL injection via AI-generated queries HIGH Read-only database role; parameterised queries; query logging and review
Template cascade affects live contracts unintentionally MED Impact analysis display before publishing; confirmation workflow; cloned contracts excluded
Complex table variables difficult to capture in UI MED Dedicated inline table editor component; validation rules per variable type

Open questions

  • Deployment infrastructure: containerised on-premise, cloud-hosted, or hybrid? Affects latency for AI calls.
  • Authentication for external API: API keys, OAuth2, or both? Depends on consumer system capabilities.
  • Document storage: should generated .docx files be stored in the database, a file store, or regenerated on demand each time?
  • Multi-tenancy: will the system serve multiple business units or a single instance?
  • Approval workflow: should template version updates require multi-step approval before publishing?

— End of document —


All writing