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.
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| name | VARCHAR | Template name, e.g. "Base MESA" |
| type | ENUM | MESA | CESA | PAYMENT_GUARANTEE |
| created_at | TIMESTAMP | Creation timestamp |
| updated_by | FK → User | Last 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.
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| template_id | FK → BaseTemplate | Parent template |
| version_number | INTEGER | Auto-incrementing version |
| version_name | VARCHAR | Human-readable name |
| is_active | BOOLEAN | Whether this is the current active version |
| created_at | TIMESTAMP | When this version was created |
| notes | TEXT | Change 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.
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| template_version_id | FK → TemplateVersion | Which version this clause belongs to |
| section_name | VARCHAR | Section heading, e.g. "Part 1 · Clause 2.8" |
| section_order | INTEGER | Order of section within the contract |
| clause_order | INTEGER | Order within the section |
| body | TEXT | Clause 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.
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| company_name | VARCHAR | Registered legal name |
| registration_number | VARCHAR | CIPC registration number |
| physical_address | TEXT | Domicilium address |
| VARCHAR | Contractual email | |
| lead_id | FK → Lead | Originating lead |
| created_at | TIMESTAMP | Record 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).
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| customer_id | FK → Customer | Owning customer |
| template_version_id | FK → TemplateVersion | Source template version |
| is_cloned | BOOLEAN | If true, this contract is frozen and unaffected by base updates |
| status | ENUM | DRAFT | ACTIVE | EXPIRED | TERMINATED |
| version | INTEGER | Contract version (increments on variable changes) |
| created_at | TIMESTAMP | Creation 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.
| Field | Type | Description |
|---|---|---|
| id | UUID (PK) | Unique identifier |
| customer_contract_id | FK → CustomerContract | Parent contract |
| variable_name | VARCHAR | Unique semantic key, e.g. "customer.company_name" |
| variable_value | TEXT | Resolved value |
| variable_type | ENUM | TEXT | NUMBER | DATE | AMOUNT | TABLE | ADDRESS |
| version | INTEGER | Tracks 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
- Administrator edits a clause in a base template version.
- The system queries all CustomerContracts linked to that TemplateVersion where
is_cloned = false. - An impact report is displayed showing each affected customer, contract, and the date/version details.
- The administrator reviews and confirms the update.
- 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?