Contract clause analyzer
Analyze construction contract clauses. Identify risks, obligations, and key terms using NLP.From its SKILL.md
npx -y skills add datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction --skill contract-clause-analyzerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
11.3 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Contract Clause Analyzer
Business Case
Problem Statement
Contract review is time-consuming and error-prone:
- Important clauses missed
- Risk provisions overlooked
- Inconsistent interpretation
- Long review cycles
Solution
AI-assisted contract clause analysis that identifies key provisions, flags risks, and extracts critical terms.
Technical Implementation
import pandas as pd
from datetime import datetime, date
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from enum import Enum
import re
class ClauseType(Enum):
SCOPE = "scope"
PAYMENT = "payment"
SCHEDULE = "schedule"
CHANGE_ORDER = "change_order"
TERMINATION = "termination"
INDEMNIFICATION = "indemnification"
INSURANCE = "insurance"
WARRANTY = "warranty"
DISPUTE = "dispute"
LIABILITY = "liability"
FORCE_MAJEURE = "force_majeure"
SAFETY = "safety"
COMPLIANCE = "compliance"
OTHER = "other"
class RiskLevel(Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
@dataclass
class ContractClause:
clause_id: str
section: str
title: str
text: str
clause_type: ClauseType
risk_level: RiskLevel
key_terms: List[str] = field(default_factory=list)
obligations: List[str] = field(default_factory=list)
deadlines: List[str] = field(default_factory=list)
amounts: List[str] = field(default_factory=list)
notes: str = ""
@dataclass
class AnalysisResult:
contract_name: str
analyzed_date: datetime
total_clauses: int
clauses: List[ContractClause]
risk_summary: Dict[str, int]
key_dates: List[Dict[str, str]]
key_amounts: List[Dict[str, str]]
class ContractClauseAnalyzer:
"""Analyze construction contract clauses."""
RISK_KEYWORDS = {
'high': ['indemnify', 'sole discretion', 'waive', 'forfeit', 'liquidated damages',
'consequential', 'unlimited liability', 'hold harmless', 'no limit'],
'medium': ['shall', 'must', 'required', 'obligated', 'responsible', 'liable',
'penalty', 'default', 'breach'],
'low': ['may', 'should', 'reasonable', 'mutual', 'consent', 'approval']
}
CLAUSE_PATTERNS = {
ClauseType.PAYMENT: ['payment', 'invoice', 'retainage', 'progress payment'],
ClauseType.SCHEDULE: ['schedule', 'completion date', 'milestone', 'time is of the essence'],
ClauseType.CHANGE_ORDER: ['change order', 'modification', 'additional work', 'variation'],
ClauseType.TERMINATION: ['termination', 'terminate', 'cancellation'],
ClauseType.INDEMNIFICATION: ['indemnif', 'hold harmless', 'defend'],
ClauseType.INSURANCE: ['insurance', 'coverage', 'policy', 'insured'],
ClauseType.WARRANTY: ['warranty', 'guarantee', 'defect', 'workmanship'],
ClauseType.DISPUTE: ['dispute', 'arbitration', 'mediation', 'litigation'],
ClauseType.LIABILITY: ['liability', 'damages', 'limitation'],
ClauseType.FORCE_MAJEURE: ['force majeure', 'act of god', 'unforeseen'],
}
def __init__(self):
self.clauses: List[ContractClause] = []
def analyze_text(self, contract_name: str, text: str) -> AnalysisResult:
"""Analyze contract text."""
self.clauses = []
# Split into sections/clauses
sections = self._split_into_sections(text)
for i, section in enumerate(sections):
clause = self._analyze_clause(f"CL-{i+1:03d}", section)
self.clauses.append(clause)
# Generate summary
risk_summary = {
'high': sum(1 for c in self.clauses if c.risk_level == RiskLevel.HIGH),
'medium': sum(1 for c in self.clauses if c.risk_level == RiskLevel.MEDIUM),
'low': sum(1 for c in self.clauses if c.risk_level == RiskLevel.LOW)
}
key_dates = []
key_amounts = []
for clause in self.clauses:
for d in clause.deadlines:
key_dates.append({'clause': clause.clause_id, 'date': d})
for a in clause.amounts:
key_amounts.append({'clause': clause.clause_id, 'amount': a})
return AnalysisResult(
contract_name=contract_name,
analyzed_date=datetime.now(),
total_clauses=len(self.clauses),
clauses=self.clauses,
risk_summary=risk_summary,
key_dates=key_dates,
key_amounts=key_amounts
)
def _split_into_sections(self, text: str) -> List[Dict[str, str]]:
"""Split contract into sections."""
sections = []
# Simple split by numbered sections
pattern = r'(\d+\.[\d\.]*\s+[A-Z][^\.]+)'
parts = re.split(pattern, text)
current_title = ""
for i, part in enumerate(parts):
if re.match(r'\d+\.[\d\.]*\s+[A-Z]', part):
current_title = part.strip()
elif part.strip() and current_title:
sections.append({
'title': current_title,
'text': part.strip()
})
current_title = ""
# If no sections found, treat whole text as one
if not sections and text.strip():
sections.append({'title': 'Contract Text', 'text': text.strip()})
return sections
def _analyze_clause(self, clause_id: str, section: Dict[str, str]) -> ContractClause:
"""Analyze single clause."""
text = section.get('text', '')
title = section.get('title', '')
text_lower = text.lower()
# Determine clause type
clause_type = self._determine_type(text_lower)
# Assess risk level
risk_level = self._assess_risk(text_lower)
# Extract key terms
key_terms = self._extract_key_terms(text)
# Extract obligations
obligations = self._extract_obligations(text)
# Extract dates
deadlines = self._extract_dates(text)
# Extract amounts
amounts = self._extract_amounts(text)
return ContractClause(
clause_id=clause_id,
section=clause_id,
title=title,
text=text[:500] + "..." if len(text) > 500 else text,
clause_type=clause_type,
risk_level=risk_level,
key_terms=key_terms,
obligations=obligations,
deadlines=deadlines,
amounts=amounts
)
def _determine_type(self, text: str) -> ClauseType:
"""Determine clause type from content."""
for clause_type, keywords in self.CLAUSE_PATTERNS.items():
if any(kw in text for kw in keywords):
return clause_type
return ClauseType.OTHER
def _assess_risk(self, text: str) -> RiskLevel:
"""Assess risk level of clause."""
high_count = sum(1 for kw in self.RISK_KEYWORDS['high'] if kw in text)
medium_count = sum(1 for kw in self.RISK_KEYWORDS['medium'] if kw in text)
if high_count >= 2:
return RiskLevel.HIGH
elif high_count >= 1 or medium_count >= 3:
return RiskLevel.MEDIUM
elif medium_count >= 1:
return RiskLevel.LOW
return RiskLevel.INFO
def _extract_key_terms(self, text: str) -> List[str]:
"""Extract key defined terms."""
# Look for quoted terms or capitalized multi-word phrases
patterns = [
r'"([^"]+)"',
r"'([^']+)'",
r'\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b'
]
terms = []
for pattern in patterns:
matches = re.findall(pattern, text)
terms.extend(matches[:5])
return list(set(terms))[:10]
def _extract_obligations(self, text: str) -> List[str]:
"""Extract obligation statements."""
patterns = [
r'(?:contractor|owner|party)\s+shall\s+([^\.]+)',
r'(?:contractor|owner|party)\s+must\s+([^\.]+)',
r'(?:contractor|owner|party)\s+is\s+(?:required|obligated)\s+to\s+([^\.]+)'
]
obligations = []
for pattern in patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
obligations.extend(matches[:3])
return obligations[:5]
def _extract_dates(self, text: str) -> List[str]:
"""Extract date references."""
patterns = [
r'\b\d{1,2}/\d{1,2}/\d{2,4}\b',
r'\b(?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},?\s+\d{4}\b',
r'\b\d+\s+(?:calendar|working|business)\s+days\b',
r'\bwithin\s+\d+\s+days\b'
]
dates = []
for pattern in patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
dates.extend(matches)
return dates[:5]
def _extract_amounts(self, text: str) -> List[str]:
"""Extract monetary amounts."""
patterns = [
r'\$[\d,]+(?:\.\d{2})?',
r'\b\d+(?:,\d{3})*(?:\.\d{2})?\s*(?:dollars|USD)\b',
r'\b\d+(?:\.\d+)?%\b'
]
amounts = []
for pattern in patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
amounts.extend(matches)
return amounts[:5]
def get_high_risk_clauses(self) -> List[ContractClause]:
"""Get all high-risk clauses."""
return [c for c in self.clauses if c.risk_level == RiskLevel.HIGH]
def export_analysis(self, result: AnalysisResult, output_path: str):
"""Export analysis to Excel."""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Summary
summary_df = pd.DataFrame([{
'Contract': result.contract_name,
'Analyzed': result.analyzed_date,
'Total Clauses': result.total_clauses,
'High Risk': result.risk_summary['high'],
'Medium Risk': result.risk_summary['medium'],
'Low Risk': result.risk_summary['low']
}])
summary_df.to_excel(writer, sheet_name='Summary', index=False)
# Clauses
clause_data = [{
'ID': c.clause_id,
'Title': c.title[:50],
'Type': c.clause_type.value,
'Risk': c.risk_level.value,
'Key Terms': ', '.join(c.key_terms[:3]),
'Obligations': len(c.obligations),
'Dates': ', '.join(c.deadlines[:2]),
'Amounts': ', '.join(c.amounts[:2])
} for c in result.clauses]
pd.DataFrame(clause_data).to_excel(writer, sheet_name='Clauses', index=False)
return output_path
Quick Start
analyzer = ContractClauseAnalyzer()
# Analyze contract text
contract_text = open("contract.txt").read()
result = analyzer.analyze_text("Construction Contract", contract_text)
print(f"High risk clauses: {result.risk_summary['high']}")
# Get risky clauses
high_risk = analyzer.get_high_risk_clauses()
for clause in high_risk:
print(f"{clause.clause_id}: {clause.title}")
Resources
- DDC Book: Chapter 5 - Contract Management
What ships with it: 2 files
1.6 KB alongside SKILL.md
- claw.json447 B
- instructions.md1.1 KB
Gives 0 of the 12 instructions most legal skills give in ~2.7k tokens
Counted across 234 of the 234 authors here whose files we hold, read 2026-08-07
- Use text operators for text fieldsin 11 of 234, across 6 files
- Consult qualified counsel before usein 11 of 234, across 3 files
- Use PatentSearch API for patent searchesin 10 of 234, across 5 files
- Confirm jurisdiction, employment type, and required clausesin 9 of 234, across 2 files
- Choose a document template and tailor role-specific termsin 9 of 234, across 2 files
- Validate compensation, benefits, and compliance requirementsin 9 of 234, across 2 files
- Add signature, confidentiality, and IP assignment terms as neededin 9 of 234, across 2 files
- Open the implementation playbook for detailed templatesin 9 of 234, across 2 files
- Use TSDR for trademark data retrievalin 9 of 234, across 4 files
- Ask for clarification if required inputs are missingin 8 of 234, across 2 files
- Set the USPTO_API_KEY environment variablein 8 of 234, across 3 files
- Use the uspto-opendata-python library for PEDSin 8 of 234, across 3 files
Said here and by no other author read
- split contract text into sections
- assess risk level using keywords
- extract key terms from clauses
- extract obligations from clauses
- extract date references from clauses
- export the analysis to excel
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.