| name | contract-clause-extractor |
| description | Extract and analyze key clauses from construction contracts. Identify payment terms, change order procedures, dispute resolution, warranties, and risk allocation provisions. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"📝","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]"}}} |
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: [, ]
contract_value:
duration_days:
start_date:
key_dates: [, ]
clauses_by_category: [, [ExtractedClause]]
risk_assessment: [, ]
action_items: []
:
CLAUSE_PATTERNS = {
ClauseCategory.PAYMENT: [
,
,
,
,
,
,
],
ClauseCategory.CHANGE_ORDER: [
,
,
,
,
,
],
ClauseCategory.SCHEDULE: [
,
,
,
,
,
,
],
ClauseCategory.DISPUTE: [
,
,
,
,
,
,
],
ClauseCategory.INSURANCE: [
,
,
,
,
,
],
ClauseCategory.WARRANTY: [
,
,
,
,
],
ClauseCategory.INDEMNIFICATION: [
,
,
,
,
],
ClauseCategory.TERMINATION: [
,
,
,
,
],
}
KEY_TERM_PATTERNS = {
: ,
: ,
: ,
: ,
}
():
.extracted_clauses: [ExtractedClause] = []
() -> [ExtractedClause]:
.extracted_clauses = []
sections = ._split_into_sections(contract_text)
section_num, section_text sections.items():
category = ._identify_category(section_text)
category:
clause = ._extract_clause_details(
category, section_num, section_text
)
.extracted_clauses.append(clause)
.extracted_clauses
() -> [, ]:
sections = {}
header_pattern =
parts = re.split(header_pattern, text)
current_num =
current_text =
i, part (parts):
re.(, part.strip()):
current_text:
sections[current_num] = current_text
current_num = part.strip()
current_text =
:
current_text += part
current_text:
sections[current_num] = current_text
sections
() -> [ClauseCategory]:
text_lower = text.lower()
category, patterns .CLAUSE_PATTERNS.items():
pattern patterns:
re.search(pattern, text_lower):
category
() -> ExtractedClause:
title_match = re.search(, text.strip())
title = title_match.group().strip() title_match
dollar_amounts = []
re.finditer(.KEY_TERM_PATTERNS[], text):
amount_str = .group().replace(, ).replace(, ).replace(, ).strip()
:
dollar_amounts.append((amount_str))
ValueError:
time_periods = re.findall(.KEY_TERM_PATTERNS[], text, re.IGNORECASE)
key_terms = ._extract_key_terms(category, text)
risk_level = ._assess_risk(category, text)
ExtractedClause(
category=category,
article_number=section_num,
title=title,
full_text=text[:],
key_terms=key_terms,
dollar_amounts=dollar_amounts,
time_periods=time_periods,
risk_level=risk_level
)
() -> []:
key_terms = []
text_lower = text.lower()
category_terms = {
ClauseCategory.PAYMENT: [, , , , , , ],
ClauseCategory.CHANGE_ORDER: [, , , ],
ClauseCategory.SCHEDULE: [, , , , ],
ClauseCategory.DISPUTE: [, , , , , ],
ClauseCategory.INSURANCE: [, , , ],
ClauseCategory.WARRANTY: [, , , , ],
ClauseCategory.INDEMNIFICATION: [, , , , ],
}
term category_terms.get(category, []):
term text_lower:
key_terms.append(term)
key_terms
() -> RiskLevel:
text_lower = text.lower()
high_risk_indicators = [
,
,
,
,
,
,
,
,
]
medium_risk_indicators = [
,
,
,
,
]
high_count = ( ind high_risk_indicators ind text_lower)
medium_count = ( ind medium_risk_indicators ind text_lower)
high_count >= :
RiskLevel.HIGH
high_count >= medium_count >= :
RiskLevel.MEDIUM
RiskLevel.LOW
() -> ContractSummary:
clauses = .extract_clauses(contract_text)
clauses_by_category = {}
clause clauses:
cat = clause.category.value
cat clauses_by_category:
clauses_by_category[cat] = []
clauses_by_category[cat].append(clause)
parties = ._extract_parties(contract_text)
contract_value = ._extract_contract_value(contract_text)
risk_assessment = {}
clause clauses:
clause.risk_level == RiskLevel.HIGH:
risk_assessment[clause.title] =
action_items = ._generate_action_items(clauses)
ContractSummary(
contract_type=._identify_contract_type(contract_text),
parties=parties,
contract_value=contract_value,
duration_days=,
start_date=,
key_dates={},
clauses_by_category=clauses_by_category,
risk_assessment=risk_assessment,
action_items=action_items
)
() -> [, ]:
parties = {}
patterns = [
(, ),
(, ),
(, ),
(, ),
]
pattern, party_type patterns:
= re.search(pattern, text)
party_type parties:
parties[party_type] = .group().strip()
parties
() -> :
patterns = [
,
,
,
]
pattern patterns:
= re.search(pattern, text, re.IGNORECASE)
:
:
(.group().replace(, ))
ValueError:
() -> :
text_lower = text.lower()
text_lower text_lower:
text_lower text_lower:
text_lower text_lower:
text_lower:
text_lower text_lower:
() -> []:
actions = []
clause clauses:
clause.risk_level == RiskLevel.HIGH:
actions.append()
clause.category == ClauseCategory.INSURANCE:
actions.append()
clause.category == ClauseCategory.PAYMENT:
.join(clause.key_terms).lower():
actions.append()
clause.time_periods:
actions.append()
((actions))
() -> :
lines = [
,
,
,
summary.contract_value ,
,
,
]
party_type, name summary.parties.items():
lines.append()
lines.extend([, , ])
category, clauses summary.clauses_by_category.items():
lines.append()
clause clauses:
risk_indicator = clause.risk_level == RiskLevel.HIGH clause.risk_level == RiskLevel.MEDIUM
lines.append()
clause.key_terms:
lines.append()
lines.append()
summary.risk_assessment:
lines.extend([, ])
item, note summary.risk_assessment.items():
lines.append()
lines.append()
summary.action_items:
lines.extend([, ])
item summary.action_items:
lines.append()
.join(lines)
Quick Start
extractor = ContractClauseExtractor()
contract_text = """
ARTICLE 5 - PAYMENT
5.1 The Contract Sum is Five Million Dollars ($5,000,000.00).
5.2 Progress payments shall be made monthly based on the Schedule of Values.
Retainage of ten percent (10%) shall be withheld from each payment.
5.3 Final payment shall be made within 30 days of substantial completion.
ARTICLE 7 - CHANGES IN THE WORK
7.1 The Owner may order changes in the Work within the general scope of the
Contract. Such changes shall be authorized by written Change Order.
7.2 Contractor shall provide written notice of any claim for additional cost
or time within 21 days of the event giving rise to such claim.
ARTICLE 12 - INDEMNIFICATION
12.1 Contractor shall indemnify and hold harmless the Owner from and against
all claims arising out of the Contractor's negligence.
"""
clauses = extractor.extract_clauses(contract_text)
for clause in clauses:
print(f"{clause.category.value}: {clause.title} (Risk: {clause.risk_level.value})")
summary = extractor.generate_summary(contract_text)
print(extractor.generate_report(summary))
Requirements
pip install (no external dependencies)