Contract Clause Extractor
Overview
Extract and analyze key clauses from construction contracts using NLP. Identify critical provisions for payment, changes, disputes, warranties, and risk allocation. Support contract review and compliance tracking.
Key Clause Categories
┌─────────────────────────────────────────────────────────────────┐
│ CONTRACT CLAUSE CATEGORIES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Payment Changes Risk │
│ ─────── ─────── ──── │
│ 📅 Pay schedule 📝 CO process ⚠️ Indemnification │
│ 💰 Retainage ⏰ Notice period 🔒 Insurance │
│ 📋 Requirements 💵 Pricing 🏛️ Liability limits │
│ │
│ Schedule Disputes Closeout │
│ ──────── ──────── ──────── │
│ 📆 Milestones ⚖️ Resolution ✅ Punch list │
│ 💸 Liquidated $ 🏛️ Jurisdiction 📄 Warranties │
│ ⏱️ Extensions 👤 Mediation 🔑 Final payment │
│ │
└─────────────────────────────────────────────────────────────────┘
Technical Implementation
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from enum import Enum
import re
class ClauseCategory(Enum):
PAYMENT = "payment"
CHANGE_ORDER = "change_order"
SCHEDULE = "schedule"
DISPUTE = "dispute"
INSURANCE = "insurance"
WARRANTY = "warranty"
TERMINATION = "termination"
INDEMNIFICATION = "indemnification"
SAFETY = "safety"
CLOSEOUT = "closeout"
class RiskLevel(Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
@dataclass
class ExtractedClause:
category: ClauseCategory
article_number: str
title: str
full_text: str
key_terms: List[str]
dollar_amounts: List[float]
time_periods: List[str]
risk_level: RiskLevel
notes: str = ""
@dataclass
class ContractSummary:
contract_type: str
parties: Dict[str, str]
contract_value: float
duration_days: int
start_date: str
key_dates: Dict[str, str]
clauses_by_category: Dict[str, List[ExtractedClause]]
risk_assessment: Dict[str, str]
action_items: List[str]
class ContractClauseExtractor:
"""Extract key clauses from construction contracts."""
# Patterns for clause identification
CLAUSE_PATTERNS = {
ClauseCategory.PAYMENT: [
r"payment\s+(terms|schedule|application)",
r"progress\s+payment",
r"retainage|retention",
r"pay\s+when\s+paid",
r"pay\s+if\s+paid",
r"final\s+payment",
],
ClauseCategory.CHANGE_ORDER: [
r"change\s+order",
r"change\s+directive",
r"modifications?\s+to\s+contract",
r"extra\s+work",
r"changes\s+in\s+the\s+work",
],
ClauseCategory.SCHEDULE: [
r"time\s+of\s+completion",
r"liquidated\s+damages",
r"delay|extension\s+of\s+time",
r"substantial\s+completion",
r"schedule\s+of\s+values",
r"milestone",
],
ClauseCategory.DISPUTE: [
r"dispute\s+resolution",
r"mediation",
r"arbitration",
r"claims?\s+procedures?",
r"litigation",
r"governing\s+law",
],
ClauseCategory.INSURANCE: [
r"insurance\s+requirements?",
r"builder'?s?\s+risk",
r"general\s+liability",
r"professional\s+liability",
r"workers'?\s+compensation",
],
ClauseCategory.WARRANTY: [
r"warranty|warranties",
r"guarantee",
r"correction\s+of\s+work",
r"defect",
],
ClauseCategory.INDEMNIFICATION: [
r"indemnif",
r"hold\s+harmless",
r"defend",
r"limitation\s+of\s+liability",
],
ClauseCategory.TERMINATION: [
r"termination",
r"suspension\s+of\s+work",
r"default",
r"for\s+cause|for\s+convenience",
],
}
# Key terms to extract
KEY_TERM_PATTERNS = {
"dollar_amounts": r'\$[\d,]+(?:\.\d{2})?|\d+(?:,\d{3})*\s*dollars',
"percentages": r'\d+(?:\.\d+)?%',
"time_periods": r'\d+\s*(?:days?|weeks?|months?|years?|business\s+days?|calendar\s+days?)',
"notice_periods": r'(?:within|not\s+(?:less|more)\s+than)\s+\d+\s*(?:days?|hours?)',
}
def __init__(self):
self.extracted_clauses: List[ExtractedClause] = []
def extract_clauses(self, contract_text: str) -> List[ExtractedClause]:
"""Extract all identifiable clauses from contract text."""
self.extracted_clauses = []
# Split into articles/sections
sections = self._split_into_sections(contract_text)
for section_num, section_text in sections.items():
# Identify clause category
category = self._identify_category(section_text)
if category:
clause = self._extract_clause_details(
category, section_num, section_text
)
self.extracted_clauses.append(clause)
return self.extracted_clauses
def _split_into_sections(self, text: str) -> Dict[str, str]:
"""Split contract into numbered sections."""
sections = {}
# Pattern for article/section headers
header_pattern = r'(?:ARTICLE|SECTION|Article|Section)\s+(\d+(?:\.\d+)?)[:\.]?\s*([A-Z][A-Za-z\s]+)?'
parts = re.split(header_pattern, text)
current_num = "0"
current_text = ""
for i, part in enumerate(parts):
if re.match(r'^\d+(?:\.\d+)?$', part.strip()):
if current_text:
sections[current_num] = current_text
current_num = part.strip()
current_text = ""
else:
current_text += part
if current_text:
sections[current_num] = current_text
return sections
def _identify_category(self, text: str) -> Optional[ClauseCategory]:
"""Identify clause category from text."""
text_lower = text.lower()
for category, patterns in self.CLAUSE_PATTERNS.items():
for pattern in patterns:
if re.search(pattern, text_lower):
return category
return None
def _extract_clause_details(self, category: ClauseCategory,
section_num: str, text: str) -> ExtractedClause:
"""Extract detailed information from clause."""
# Extract title (first line or capitalized phrase)
title_match = re.search(r'^([A-Z][A-Z\s]+)', text.strip())
title = title_match.group(1).strip() if title_match else f"Section {section_num}"
# Extract dollar amounts
dollar_amounts = []
for match in re.finditer(self.KEY_TERM_PATTERNS["dollar_amounts"], text):
amount_str = match.group().replace('$', '').replace(',', '').replace('dollars', '').strip()
try:
dollar_amounts.append(float(amount_str))
except ValueError:
pass
# Extract time periods
time_periods = re.findall(self.KEY_TERM_PATTERNS["time_periods"], text, re.IGNORECASE)
# Extract key terms
key_terms = self._extract_key_terms(category, text)
# Assess risk level
risk_level = self._assess_risk(category, text)
return ExtractedClause(
category=category,