
Implementation Guide: Draft subcontractor agreements, change orders, and project closeout reports
Step-by-step implementation guide for deploying AI to draft subcontractor agreements, change orders, and project closeout reports for Construction & Contractors clients.
Hardware Procurement
Dell OptiPlex 7020 Micro Desktop
$900–$1,100 per unit MSP cost via TD SYNNEX/Ingram Micro / $1,200–$1,500 suggested resale
Office workstations for project managers and office staff to run Microsoft 365 with Copilot, access web-based contract platforms (LegalOn/Spellbook), and review/edit AI-generated documents in Word. Dual-monitor capable for side-by-side contract review.
Lenovo ThinkPad T14s Gen 6 Laptop
$1,200–$1,400 per unit MSP cost via Ingram Micro / $1,500–$1,800 suggested resale
Portable workstations for superintendents and field PMs who need to generate or review change orders and subcontractor agreements from job trailers or client offices. NPU-equipped for local Copilot acceleration.
Microsoft Surface Pro 10 for Business
$1,200–$1,400 per unit MSP cost via Pax8/Ingram Micro / $1,500–$1,800 suggested resale
Tablet-format devices for on-site contract presentations, e-signature capture, and field walk-through documentation for closeout reports. Includes pen support for markup annotations.
Fujitsu ScanSnap iX1600 Document Scanner
$350–$400 MSP cost / $475–$550 suggested resale
Digitize existing paper contracts, lien waivers, insurance certificates, and historical subcontractor agreements for ingestion into the AI system's reference document library. 40 ppm duplex scanning with direct-to-SharePoint capability.
Dell UltraSharp U2724D 27-inch QHD Monitor
$280–$320 per unit MSP cost / $375–$425 suggested resale
Secondary monitors for office workstations enabling side-by-side contract review: AI-generated draft on one screen, source project data or reference contract on the other. Essential for efficient human review of AI outputs.
Software Procurement
Microsoft 365 Business Premium
$22/user/month MSP cost via Pax8 / $28/user/month suggested resale; 10 seats = $220/mo MSP cost, $280/mo resale
Foundation platform providing Word, Excel, SharePoint Online, OneDrive, Teams, Entra ID, Intune MDM, and Azure Information Protection. SharePoint serves as the central document repository for all contract templates, generated drafts, and executed agreements. Required prerequisite for Copilot add-on.
Microsoft 365 Copilot Add-on
$25–$27/user/month MSP cost via Pax8 / $35–$40/user/month suggested resale; 10 seats = $250–$270/mo MSP cost, $350–$400/mo resale
AI assistant embedded in Word, Excel, PowerPoint, and SharePoint. Powers in-document contract drafting using custom prompts and SharePoint-hosted templates. Copilot Studio (included) enables building custom AI agents for document generation workflows without additional licensing.
LegalOn AI
$3,000–$8,000/year for small team (3–10 users); MSP referral/reseller margin 10–15%
Construction-specific AI contract review platform with pre-built playbooks for Master Design-Build Agreements, Construction Agreements, and Subcontractor Agreements. Flags risks by severity, provides attorney-curated guidance, and ensures compliance with industry standards. Acts as the legal guardrail layer on top of Copilot-generated drafts.
DocuSign Business Pro
$40–$65/user/month billed annually; 5 seats = $200–$325/month; MSP reseller margin 10–20% via DocuSign Partner Program
Electronic signature platform for executing subcontractor agreements, change orders, and closeout acknowledgments. Includes envelope routing, signing order workflows, construction contract templates, and audit trails compliant with ESIGN Act and UETA. Integrates with Procore and SharePoint.
Zapier (Professional Plan)
$49/month for 2,000 tasks; MSP manages as part of service wrap
Workflow automation connecting Procore/Buildertrend project data to AI document generation pipeline, then routing outputs to DocuSign for signature and back to SharePoint/Procore for storage. Enables trigger-based automation (e.g., new subcontractor added in Procore triggers agreement draft).
OpenAI API (GPT-5.4)
$2.50/million input tokens + $10.00/million output tokens; estimated $50–$200/month for typical contractor usage (100–300 documents/month)
Foundation AI model for custom document generation via API. Used in custom automation scripts and Copilot Studio agents for generating subcontractor agreements, change orders, and closeout reports from structured project data. 128K token context window supports ingestion of full contract templates plus project specifics.
AIA Contract Documents - Unlimited Subscription
$750–$1,500/year depending on firm size; client procures directly
Industry-standard contract templates (A101, A201, A401 subcontractor agreement, G701 change order, G704 certificate of substantial completion, etc.) used as base templates for AI-powered generation. Over 300 forms covering all project phases. AI system references these as authoritative source documents.
Procore (if not already subscribed)
Starting at $375/month based on Annual Construction Volume (ACV); client typically already has this
Construction project management platform providing the source data (project details, subcontractor lists, budget line items, RFIs, submittals) that feeds into AI document generation. API exposes project data for automated workflows. Not resold by MSP—ensure client has active subscription with API access enabled.
Spellbook (Alternative to LegalOn)
$20–$40/user/month entry tier; $179/user/month mid-tier with advanced features
Alternative construction-specific AI contract tool that operates as a Microsoft Word add-in. Lower learning curve than standalone platforms. Suitable if client prefers all work to happen inside Word rather than switching to a separate contract review platform.
Prerequisites
- Active Microsoft 365 Business Premium (or higher) tenant with at least 10 licensed users and SharePoint Online provisioned
- Microsoft Entra ID (Azure AD) configured with MFA enforced for all users who will access document generation or signing workflows
- Active Procore or Buildertrend subscription with API access enabled and at least one project with subcontractor data populated
- Existing subcontractor agreement templates, change order forms, and closeout report templates (paper or digital) collected from client—minimum 10–20 representative examples of each document type
- Client's construction attorney identified and available for template review during Weeks 4–5 and 8–9 of implementation; attorney must review and approve all AI-generated template outputs before production use
- Internet connectivity at all office locations: minimum 25 Mbps down / 10 Mbps up; field locations need minimum 5 Mbps via mobile hotspot or job-site internet
- Windows 11 Pro on all workstations (required for Copilot NPU features and Entra ID join)
- Domain name and DNS access for configuring SSO and email routing if not already managed by MSP
- Client has identified 2–3 power users (typically project managers or office managers) who will serve as champions during training and UAT
- State-specific compliance requirements documented: identify which states the client operates in and whether they have statutory lien waiver forms (AZ, CA, FL, GA, MI, MS, MO, NV, TX, UT, WI, WY), prevailing wage requirements, or specific e-signature restrictions
- OpenAI API account created with billing configured and API key generated; or Microsoft Azure OpenAI Service provisioned if client requires data residency controls
- Backup and disaster recovery plan in place for SharePoint document libraries (Microsoft 365 retention policies configured or third-party backup like Veeam for M365)
Installation Steps
...
Step 1: Provision and Configure Microsoft 365 Environment
Set up or verify the Microsoft 365 Business Premium tenant, assign licenses, configure Entra ID with MFA, and create the security groups needed for role-based access to contract documents. Create three security groups: AI-ContractAdmins (office managers, PMs who generate contracts), AI-ContractReviewers (superintendents, executives who review/approve), and AI-ContractSigners (authorized signatories). Enable Conditional Access policies requiring MFA and compliant device for access to SharePoint contract libraries.
Connect-MgGraph -Scopes 'User.ReadWrite.All','Group.ReadWrite.All','Policy.ReadWrite.ConditionalAccess'
New-MgGroup -DisplayName 'AI-ContractAdmins' -MailEnabled:$false -SecurityEnabled:$true -MailNickname 'ai-contractadmins'
New-MgGroup -DisplayName 'AI-ContractReviewers' -MailEnabled:$false -SecurityEnabled:$true -MailNickname 'ai-contractreviewers'
New-MgGroup -DisplayName 'AI-ContractSigners' -MailEnabled:$false -SecurityEnabled:$true -MailNickname 'ai-contractsigners'
# Assign M365 Business Premium licenses to all users via Entra ID group-based licensing
# Assign Copilot add-on licenses to AI-ContractAdmins group members
Set-MgGroupLicenseAssignment -GroupId <group-id> -AddLicenses @{SkuId='<copilot-sku-id>'}Copilot licenses should initially go to power users (5–7 seats) rather than all 10 users. Expand after adoption is proven. Verify the tenant is on the latest Microsoft 365 update channel (Current Channel) for full Copilot feature availability. Allow 24–48 hours for Copilot features to activate after license assignment.
Step 2: Build SharePoint Document Architecture
Create a dedicated SharePoint site for construction contract management with document libraries organized by document type. This serves as the single source of truth for all templates, AI-generated drafts, in-review documents, and executed contracts. Configure metadata columns for project name, document type, subcontractor name, status (Draft/In Review/Approved/Executed), state jurisdiction, and contract value. Enable versioning with major and minor versions to track all AI-generated iterations.
# PowerShell using PnP.PowerShell module
Connect-PnPOnline -Url https://<tenant>.sharepoint.com -Interactive
New-PnPSite -Type TeamSite -Title 'Construction Contracts AI' -Alias 'contracts-ai' -Description 'AI-powered contract generation and management'
# Create document libraries
New-PnPList -Title 'Contract Templates' -Template DocumentLibrary
New-PnPList -Title 'Subcontractor Agreements' -Template DocumentLibrary
New-PnPList -Title 'Change Orders' -Template DocumentLibrary
New-PnPList -Title 'Closeout Reports' -Template DocumentLibrary
New-PnPList -Title 'Reference Documents' -Template DocumentLibrary
# Add metadata columns to Subcontractor Agreements library
Add-PnPField -List 'Subcontractor Agreements' -DisplayName 'Project Name' -InternalName 'ProjectName' -Type Text
Add-PnPField -List 'Subcontractor Agreements' -DisplayName 'Subcontractor' -InternalName 'SubcontractorName' -Type Text
Add-PnPField -List 'Subcontractor Agreements' -DisplayName 'Contract Status' -InternalName 'ContractStatus' -Type Choice -Choices 'Draft','AI Generated','In Review','Attorney Reviewed','Approved','Sent for Signature','Executed','Archived'
Add-PnPField -List 'Subcontractor Agreements' -DisplayName 'State Jurisdiction' -InternalName 'StateJurisdiction' -Type Choice -Choices 'AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY'
Add-PnPField -List 'Subcontractor Agreements' -DisplayName 'Contract Value' -InternalName 'ContractValue' -Type Currency
# Enable versioning
Set-PnPList -Identity 'Subcontractor Agreements' -EnableVersioning $true -MajorVersions 50 -EnableMinorVersions $true
# Set permissions - AI-ContractAdmins get Contribute, AI-ContractReviewers get Read, AI-ContractSigners get Contribute
Set-PnPGroupPermissions -Identity 'AI-ContractAdmins' -List 'Subcontractor Agreements' -AddRole 'Contribute'Repeat metadata column creation for Change Orders and Closeout Reports libraries with appropriate fields (e.g., Change Order Number, Original Contract Value, Change Amount for Change Orders; Substantial Completion Date, Punch List Status for Closeout Reports). Apply the same library structure pattern to all three document type libraries. Consider creating a Power Automate flow to auto-set metadata from document content.
Step 3: Upload and Organize Template Library
Collect all existing client contract templates, AIA standard forms, state-specific clause libraries, and historical executed contracts. Scan paper documents using the Fujitsu ScanSnap iX1600 with OCR enabled. Upload everything to the Contract Templates and Reference Documents SharePoint libraries. Organize templates by document type and tag with metadata. This reference corpus is what the AI will draw from when generating new documents.
# Scan paper documents with ScanSnap iX1600 - configure ScanSnap Home software:
# 1. Set scan profile: 'Contract Archive' - Color, Auto Resolution, Searchable PDF
# 2. Set destination: SharePoint Online > Construction Contracts AI > Reference Documents
# 3. Enable OCR: Language = English, Searchable PDF format
# Bulk upload digital templates via PnP PowerShell
$templates = Get-ChildItem -Path 'C:\ClientTemplates\SubcontractorAgreements\' -Filter *.docx
foreach ($template in $templates) {
Add-PnPFile -Path $template.FullName -Folder 'Contract Templates/Subcontractor Agreements'
Write-Host "Uploaded: $($template.Name)"
}
# Upload AIA forms (after client purchases AIA Unlimited subscription)
# AIA forms download as .docx from aiacontracts.com
Add-PnPFile -Path 'C:\AIA_Forms\A401-2017_SubcontractorAgreement.docx' -Folder 'Contract Templates/AIA Standard Forms'
Add-PnPFile -Path 'C:\AIA_Forms\G701-2017_ChangeOrder.docx' -Folder 'Contract Templates/AIA Standard Forms'
Add-PnPFile -Path 'C:\AIA_Forms\G704-2017_CertificateSubstantialCompletion.docx' -Folder 'Contract Templates/AIA Standard Forms'Critical: Obtain at least 10 executed subcontractor agreements, 10 change orders, and 5 closeout reports from the client's recent projects. These serve as the AI's reference examples for tone, formatting, and clause inclusion patterns. Remove any truly confidential financial data from reference documents but preserve structure and legal language. AIA forms are copyrighted—ensure client has valid AIA subscription before using as templates.
Step 4: Configure OpenAI API Access and Test Connectivity
Set up the OpenAI API account (or Azure OpenAI Service for clients requiring data residency), configure API keys, set usage limits, and test basic connectivity. Create a dedicated API key for this project with appropriate rate limits. If the client handles government or highly sensitive contracts, use Azure OpenAI Service instead for data residency and compliance guarantees.
# Install OpenAI Python library (on admin workstation or automation server)
pip install openai
# Test API connectivity with a simple construction contract prompt
python -c "
import openai
client = openai.OpenAI(api_key='sk-proj-XXXXXXXXXXXX')
response = client.chat.completions.create(
model='gpt-5.4',
messages=[
{'role': 'system', 'content': 'You are a construction contract drafting assistant.'},
{'role': 'user', 'content': 'Generate a one-paragraph scope of work clause for a drywall subcontractor on a commercial office building project.'}
],
max_tokens=500
)
print(response.choices[0].message.content)
print(f'Tokens used: {response.usage.total_tokens}')
"
# Set monthly usage limit in OpenAI dashboard: Settings > Billing > Usage Limits
# Recommended: $200/month hard limit, $150/month soft limit (email alert)
# For Azure OpenAI alternative:
# az cognitiveservices account create --name construction-ai --resource-group rg-construction --kind OpenAI --sku S0 --location eastus2
# az cognitiveservices account deployment create --name construction-ai --resource-group rg-construction --deployment-name gpt-5.4 --model-name gpt-5.4 --model-version 2024-08-06 --model-format OpenAI --sku-capacity 30 --sku-name StandardOpenAI API with enterprise agreement includes zero-data-training guarantees (inputs/outputs not used for model training). For maximum data privacy, use Azure OpenAI Service which keeps all data within the Azure tenant and provides private endpoints. Set up API key rotation schedule (every 90 days). Store API keys in Azure Key Vault or environment variables—never hardcode in scripts.
Step 5: Configure Microsoft Copilot and Copilot Studio Agents
Enable Microsoft 365 Copilot for licensed users, configure Copilot's access to SharePoint document libraries so it can reference contract templates and historical documents, and build custom Copilot Studio agents for each document type. Copilot Studio is included with Copilot licenses at no additional cost. Create three agents: Subcontractor Agreement Drafter, Change Order Generator, and Closeout Report Builder.
Copilot Studio agents take 1–2 hours each to configure. The key quality driver is the system prompt (see custom_ai_components section). Copilot's responses are only as good as the reference documents in SharePoint—ensure the template library is comprehensive before testing. Copilot respects SharePoint permissions, so users will only see documents they have access to. Test each agent with real project data before proceeding to integration steps.
Step 6: Install and Configure LegalOn AI
Set up LegalOn AI accounts for the contract review team (typically 3–5 users: project managers, estimator, office manager). Configure construction-specific playbooks and customize risk thresholds to match the client's risk tolerance. LegalOn will serve as the legal guardrail layer—every AI-generated contract draft passes through LegalOn review before human sign-off.
If client prefers an in-Word experience over a separate platform, substitute Spellbook ($20–$179/user/month) here instead. Spellbook installs as a Word add-in and provides similar risk flagging directly inside the document editing environment. LegalOn's construction playbooks are pre-built by attorneys and cover AIA and ConsensusDocs forms—this is a significant time-saver over configuring generic AI tools. Allow 1 week for the client's attorney to review and customize the playbook risk thresholds.
Step 7: Configure DocuSign for E-Signature Workflows
Set up DocuSign Business Pro accounts, configure envelope templates for each document type, establish signing workflows with proper routing order (subcontractor signs first, then GC authorized signatory), and integrate with SharePoint for automatic storage of executed documents. Configure construction-specific templates including change order approval routing.
DocuSign Business Pro is required (not Standard) for the advanced workflow routing and PowerForms features. Each DocuSign Business Pro license includes 100 envelopes per user per year—estimate 50–80 subcontractor agreements + 30–60 change orders + 10–20 closeout docs per year for a mid-size GC. Purchase additional envelope packs if needed. Ensure the attorney reviews DocuSign envelope templates for proper signature block placement and required witness/notary provisions if applicable in the client's jurisdiction.
Step 8: Set Up Procore API Integration
Configure Procore API access to extract project data, subcontractor information, budget line items, and change event data that feeds into the AI document generation pipeline. Create a Procore App (sandbox first, then production) using their Developer Portal. Extract the data needed to auto-populate contract templates.
# 3. Test API connectivity:
curl -X GET 'https://api.procore.com/rest/v1.0/projects' \
-H 'Authorization: Bearer <access_token>' \
-H 'Procore-Company-Id: <company_id>'
# 4. Retrieve subcontractor data for a project:
curl -X GET 'https://api.procore.com/rest/v1.0/projects/<project_id>/vendors?filters[trade]=true' \
-H 'Authorization: Bearer <access_token>' \
-H 'Procore-Company-Id: <company_id>'
# 5. Retrieve commitment (subcontract) data:
curl -X GET 'https://api.procore.com/rest/v1.0/projects/<project_id>/commitments' \
-H 'Authorization: Bearer <access_token>' \
-H 'Procore-Company-Id: <company_id>'
# 6. Retrieve change order data:
curl -X GET 'https://api.procore.com/rest/v1.0/projects/<project_id>/change_order_packages' \
-H 'Authorization: Bearer <access_token>' \
-H 'Procore-Company-Id: <company_id>'If client uses Buildertrend instead of Procore, the API approach is similar but uses Buildertrend's REST API (https://developer.buildertrend.com). Buildertrend provides subcontractor, change order, and selection data via similar endpoints. For clients without Procore/Buildertrend (using spreadsheets or basic tools), create a SharePoint List as a structured data entry form that feeds the AI pipeline—this adds 1 week to implementation but eliminates API dependency.
Step 9: Build Zapier Automation Workflows
Create Zapier workflows (Zaps) that connect the entire document generation pipeline: Procore triggers → OpenAI API document generation → SharePoint storage → DocuSign routing → Status updates back to Procore. Build three primary Zaps corresponding to the three document types.
- Zap 1: Subcontractor Agreement Auto-Draft — Trigger: New Commitment Created in Procore Step 1: Procore - New Commitment (trigger) Step 2: Procore - Get Project Details (lookup project address, owner, dates) Step 3: Procore - Get Vendor Details (lookup subcontractor info: name, address, license, insurance) Step 4: OpenAI - Send Prompt (GPT-5.4 with subcontractor agreement system prompt + project data) Step 5: Microsoft Word - Create Document from Template (insert AI-generated content into .docx template) Step 6: SharePoint - Upload File to 'Subcontractor Agreements' library Step 7: SharePoint - Update File Metadata (set status = 'AI Generated', project name, sub name) Step 8: Microsoft Teams - Send Message to 'Contract Review' channel with link to draft Step 9: (After manual review) DocuSign - Create and Send Envelope from template
- Zap 2: Change Order Auto-Draft — Trigger: New Change Event in Procore OR manual trigger via Teams bot Step 1: Procore - New Change Event (trigger) Step 2: Procore - Get Original Commitment details (original contract sum, scope) Step 3: OpenAI - Send Prompt (change order system prompt + change event data) Step 4: Microsoft Word - Create Document from CO template Step 5: SharePoint - Upload to 'Change Orders' library Step 6: Microsoft Teams - Notify PM for review
- Zap 3: Closeout Report Auto-Draft — Trigger: Project Status changed to 'Closeout' in Procore OR manual trigger Step 1: Procore - Project updated (trigger, filter: status = closeout) Step 2: Procore - Get all project data (budget summary, change orders, RFI log, submittal log) Step 3: OpenAI - Send Prompt (closeout report system prompt + comprehensive project data) Step 4: Microsoft Word - Create Document from closeout template Step 5: SharePoint - Upload to 'Closeout Reports' library Step 6: Microsoft Teams - Notify PM and superintendent
Zapier Professional plan ($49/month) supports 2,000 tasks/month which is sufficient for most mid-size contractors. If task volume exceeds this, upgrade to Team ($69/month for 20,000 tasks) or switch to Make.com ($9–$16/month for 10,000 operations) which is more cost-effective at scale. Each Zap should include error handling steps: if OpenAI returns an error, send a Teams notification to the MSP admin channel. Set up Zapier's built-in error alerting to notify the MSP via email. Consider Make.com as an alternative if the client needs more complex branching logic (e.g., different templates for different states or project types).
Step 10: Deploy and Test State-Specific Clause Libraries
Build a structured clause library in SharePoint containing state-specific legal provisions that the AI must include in contracts based on the project's jurisdiction. This covers statutory lien waiver forms, prevailing wage clauses, retainage limitations, prompt payment act provisions, and mechanics lien notice requirements. The AI prompts reference this library to ensure jurisdictional compliance.
# Create and populate the State Compliance Clauses SharePoint list
# Create SharePoint List: 'State Compliance Clauses'
# Columns: State (choice), ClauseType (choice: Lien Waiver, Prevailing Wage, Retainage, Prompt Payment, Insurance Minimum, OSHA, Notice Requirement), ClauseText (multi-line text), Effective Date, Source (text), MandatoryInclusion (yes/no)
New-PnPList -Title 'State Compliance Clauses' -Template GenericList
Add-PnPField -List 'State Compliance Clauses' -DisplayName 'State' -InternalName 'State' -Type Choice -Choices 'AZ','CA','CO','FL','GA','IL','MI','MN','MO','NV','NY','NC','OH','OR','PA','TN','TX','UT','VA','WA','WI'
Add-PnPField -List 'State Compliance Clauses' -DisplayName 'Clause Type' -InternalName 'ClauseType' -Type Choice -Choices 'Lien Waiver','Prevailing Wage','Retainage Limit','Prompt Payment','Insurance Minimum','OSHA Compliance','Notice Requirement','Indemnification Limitation','Right to Cure','Dispute Resolution'
Add-PnPField -List 'State Compliance Clauses' -DisplayName 'Clause Text' -InternalName 'ClauseText' -Type Note
Add-PnPField -List 'State Compliance Clauses' -DisplayName 'Mandatory' -InternalName 'MandatoryInclusion' -Type Boolean
# Populate with state-specific clauses (example entries):
# State: CA, ClauseType: Lien Waiver, MandatoryInclusion: Yes
# ClauseText: 'This project is located in California. All lien waivers must comply with California Civil Code §8132-8138. Only the statutory forms (Conditional Waiver and Release on Progress Payment, Unconditional Waiver and Release on Progress Payment, Conditional Waiver and Release on Final Payment, Unconditional Waiver and Release on Final Payment) are valid. Any modification to the statutory language renders the waiver unenforceable.'
# State: TX, ClauseType: Retainage Limit, MandatoryInclusion: Yes
# ClauseText: 'Retainage on this subcontract shall not exceed ten percent (10%) of the contract sum per Texas Property Code §53.101. Retainage shall be released within the 30th day after the date the subcontractor completes its work scope, subject to the resolution of any outstanding punch list items.'The state compliance clause library is the single most important compliance component. Work with the client's attorney to populate clauses for every state where the client operates. Twelve states have statutory lien waiver forms that cannot be modified: AZ, CA, FL, GA, MI, MS, MO, NV, TX, UT, WI, WY. The AI system MUST use exact statutory language in these states. Schedule quarterly reviews of this library as laws change—this is a key managed service deliverable. For Davis-Bacon prevailing wage projects (federal), create a separate clause set that includes certified payroll requirements, wage determination references, and DOL reporting obligations.
Step 11: End-to-End Integration Testing with Real Project Data
Test the complete pipeline using a real (but non-critical) project from the client's Procore/Buildertrend system. Generate one of each document type (subcontractor agreement, change order, closeout report), route through LegalOn review, send through DocuSign for test signatures, and verify all metadata flows correctly to SharePoint. This is the critical validation step before going live.
# checks SharePoint file metadata, required clauses, placeholder population,
# DocuSign envelope status, and document word count
python3 validate_pipeline.py --project-id <test_project_id> --doc-type subcontractor_agreement --check-allAllow 2–3 days for integration testing. Common issues at this stage: (1) Procore API rate limits causing timeouts—add retry logic in Zapier; (2) OpenAI token limits exceeded on long contracts—split generation into sections; (3) SharePoint metadata mapping errors—verify column internal names match exactly; (4) DocuSign template field mapping misaligned—re-map in DocuSign admin. Have the client's most experienced PM review the AI-generated test documents for accuracy of construction-specific content (scope descriptions, payment terms, schedule requirements). This is where domain expertise matters most.
Step 12: User Training and Controlled Go-Live
Conduct training sessions for three user groups: power users (PMs and office managers who generate documents), reviewers (executives and superintendents who approve), and field users (who may initiate change orders from the field). Deploy in controlled go-live with first 2–3 real projects before full rollout. Monitor all AI-generated documents closely during the first 30 days.
- Session 1: Power Users (PMs, Office Manager) — 2 hours: Copilot in Word (contract drafting agents), SharePoint (navigating document libraries, understanding metadata), Zapier (how automated triggers work, what to do when they don't fire), LegalOn (running contract reviews, understanding risk flags), DocuSign (sending envelopes, monitoring signature status). CRITICAL: When to override AI suggestions, what requires attorney review.
- Session 2: Reviewers (Executives, Superintendents) — 1 hour: Reviewing AI-generated drafts in Word/SharePoint, approval workflow in Teams (approve/reject/request changes), reading LegalOn risk summaries, mobile access for on-site approvals.
- Session 3: Field Users (Superintendents, Foremen) — 1 hour: Initiating change order requests from mobile/tablet, reviewing and approving closeout documentation, e-signature on Surface Pro / mobile devices.
- Create quick-reference cards (laminated for job trailers): 'How to Generate a Subcontractor Agreement' (5 steps), 'How to Create a Change Order' (5 steps), 'How to Initiate Project Closeout' (5 steps), 'When to Call the MSP' (escalation triggers).
Schedule training during a typically slow period (Friday afternoons or early mornings before job site hours). Record all training sessions in Teams for future reference and new employee onboarding. The most critical training point: AI-generated documents are DRAFTS that require human review. Never send an AI-generated contract for signature without PM review AND (for new templates/unusual terms) attorney review. Create a shared Teams channel 'AI Contract Support' where users can ask questions and the MSP can monitor adoption. Plan a 30-day check-in meeting to review adoption metrics, common issues, and prompt refinement needs.
Custom AI Components
Subcontractor Agreement Drafting Agent
Type: agent A Copilot Studio agent (also deployable as a standalone Python script via OpenAI API) that generates complete subcontractor agreement drafts from structured project data. The agent accepts project details, subcontractor information, scope of work, contract value, and schedule dates, then produces a fully formatted subcontractor agreement incorporating the client's standard terms, AIA A401-compatible structure, and state-specific compliance clauses. The agent is the core document generator for the most complex and highest-value document type in the system.
Implementation
Agent Name: Subcontractor Agreement Drafter Knowledge Sources: SharePoint > Contract Templates, Reference Documents, State Compliance Clauses list Model: GPT-5.4 (via Copilot or direct API)
System Prompt (Copilot Studio Topic or OpenAI API system message)
# Python script for Zapier Code Step or standalone use
import openai
import json
from datetime import datetime
def generate_subcontractor_agreement(project_data: dict, sub_data: dict, state: str, api_key: str) -> str:
client = openai.OpenAI(api_key=api_key)
# Load state-specific clauses (from SharePoint API or local cache)
state_clauses = get_state_clauses(state) # Implement SharePoint Graph API call
user_prompt = f"""Generate a complete Subcontractor Agreement with the following details:
PROJECT INFORMATION:
- Project Name: {project_data.get('name', '[REQUIRES INPUT]')}
- Project Address: {project_data.get('address', '[REQUIRES INPUT]')}
- Project Number: {project_data.get('number', '[REQUIRES INPUT]')}
- Owner: {project_data.get('owner_name', '[REQUIRES INPUT]')}
- Architect: {project_data.get('architect', '[REQUIRES INPUT]')}
- Project Type: {project_data.get('type', 'Commercial')}
- Is Public Works / Prevailing Wage: {project_data.get('prevailing_wage', 'No')}
CONTRACTOR (GC) INFORMATION:
- Company: {project_data.get('gc_name', '[REQUIRES INPUT]')}
- Address: {project_data.get('gc_address', '[REQUIRES INPUT]')}
- License Number: {project_data.get('gc_license', '[REQUIRES INPUT]')}
SUBCONTRACTOR INFORMATION:
- Company: {sub_data.get('name', '[REQUIRES INPUT]')}
- Address: {sub_data.get('address', '[REQUIRES INPUT]')}
- License Number: {sub_data.get('license_number', '[REQUIRES INPUT]')}
- Trade: {sub_data.get('trade', '[REQUIRES INPUT]')}
CONTRACT TERMS:
- Scope of Work: {sub_data.get('scope', '[REQUIRES INPUT]')}
- Contract Sum: ${sub_data.get('contract_value', '[REQUIRES INPUT]')}
- Contract Type: {sub_data.get('contract_type', 'Lump Sum')}
- Start Date: {sub_data.get('start_date', '[REQUIRES INPUT]')}
- Completion Date: {sub_data.get('end_date', '[REQUIRES INPUT]')}
- Retainage: {sub_data.get('retainage_pct', '10')}%
- Payment Terms: {sub_data.get('payment_terms', 'Net 30')}
STATE: {state}
STATE-SPECIFIC CLAUSES TO INCLUDE:
{state_clauses}
Generate the complete agreement following the document structure in your instructions."""
response = client.chat.completions.create(
model='gpt-5.4',
messages=[
{'role': 'system', 'content': SYSTEM_PROMPT}, # Use full system prompt above
{'role': 'user', 'content': user_prompt}
],
max_tokens=8000,
temperature=0.3 # Low temperature for consistent, professional output
)
return response.choices[0].message.content
def get_state_clauses(state: str) -> str:
"""Retrieve state-specific clauses from SharePoint list via Microsoft Graph API"""
import requests
graph_url = f"https://graph.microsoft.com/v1.0/sites/{{site-id}}/lists/{{list-id}}/items?$filter=fields/State eq '{state}' and fields/MandatoryInclusion eq true&$expand=fields"
headers = {'Authorization': f'Bearer {{access_token}}'}
response = requests.get(graph_url, headers=headers)
items = response.json().get('value', [])
clauses = []
for item in items:
fields = item.get('fields', {})
clauses.append(f"[{fields.get('ClauseType', '')}]: {fields.get('ClauseText', '')}")
return '\n\n'.join(clauses)Change Order Generator Agent
Type: agent A Copilot Studio agent and API-callable function that generates change order documents from change event data in Procore. Handles additions, deductions, and no-cost changes. Automatically calculates new contract sums and references the original subcontract terms. Includes proper justification narratives, cost breakdowns, and schedule impact analysis.
Implementation
System Prompt for Change Order Generation
Python Implementation
def generate_change_order(change_event: dict, original_commitment: dict, project: dict, api_key: str) -> str:
client = openai.OpenAI(api_key=api_key)
# Calculate running totals
original_sum = float(original_commitment.get('contract_sum', 0))
previous_changes = float(original_commitment.get('approved_changes_total', 0))
current_sum = original_sum + previous_changes
this_change = float(change_event.get('amount', 0))
new_sum = current_sum + this_change
user_prompt = f"""Generate a Change Order document with these details:
PROJECT: {project.get('name')} (#{project.get('number')})
CONTRACTOR: {project.get('gc_name')}
SUBCONTRACTOR: {original_commitment.get('vendor_name')}
ORIGINAL SUBCONTRACT: #{original_commitment.get('number')} dated {original_commitment.get('date')}
CHANGE ORDER NUMBER: {change_event.get('co_number', '[NEXT SEQUENTIAL]')}
DATE: {datetime.now().strftime('%B %d, %Y')}
DESCRIPTION OF CHANGE:
{change_event.get('description', '[REQUIRES INPUT]')}
REASON FOR CHANGE: {change_event.get('reason', 'Owner/Architect directed change')}
TRIGGERED BY: {change_event.get('reference', 'Field condition / RFI #[NUMBER]')}
COST BREAKDOWN:
{json.dumps(change_event.get('cost_breakdown', {}), indent=2)}
TOTAL THIS CHANGE: ${this_change:,.2f}
SCHEDULE IMPACT: {change_event.get('schedule_impact_days', '[TO BE DETERMINED]')} days
CONTRACT ADJUSTMENTS:
- Original Contract Sum: ${original_sum:,.2f}
- Previous Approved Changes: ${previous_changes:,.2f}
- Current Contract Sum: ${current_sum:,.2f}
- This Change Order: ${this_change:,.2f}
- New Contract Sum: ${new_sum:,.2f}
Generate the complete change order document following the structure in your instructions."""
response = client.chat.completions.create(
model='gpt-5.4',
messages=[
{'role': 'system', 'content': CHANGE_ORDER_SYSTEM_PROMPT},
{'role': 'user', 'content': user_prompt}
],
max_tokens=4000,
temperature=0.2
)
return response.choices[0].message.contentProject Closeout Report Builder Agent
Type: agent A Copilot Studio agent that aggregates project data from Procore to generate comprehensive project closeout reports. Pulls budget summaries, change order histories, RFI logs, submittal logs, punch list status, warranty information, and safety incident records to create a complete closeout package. Generates both a narrative summary report and a checklist of outstanding items.
Implementation:
System Prompt for Closeout Report Generation
Python Implementation
def generate_closeout_report(project_id: str, procore_token: str, api_key: str) -> str:
import requests
# Aggregate all project data from Procore
headers = {'Authorization': f'Bearer {procore_token}', 'Procore-Company-Id': COMPANY_ID}
base_url = 'https://api.procore.com/rest/v1.0'
# Get project details
project = requests.get(f'{base_url}/projects/{project_id}', headers=headers).json()
# Get budget summary
budget = requests.get(f'{base_url}/projects/{project_id}/budget/views', headers=headers).json()
# Get all commitments (subcontracts)
commitments = requests.get(f'{base_url}/projects/{project_id}/commitments', headers=headers).json()
# Get change order packages
change_orders = requests.get(f'{base_url}/projects/{project_id}/change_order_packages', headers=headers).json()
# Get punch list items
punch_items = requests.get(f'{base_url}/projects/{project_id}/punch_items', headers=headers).json()
# Get RFI summary
rfis = requests.get(f'{base_url}/projects/{project_id}/rfis', headers=headers).json()
# Compile subcontractor status
sub_status = []
for commitment in commitments:
sub_status.append({
'name': commitment.get('vendor', {}).get('name', 'Unknown'),
'trade': commitment.get('title', 'Unknown'),
'original_value': commitment.get('grand_total', 0),
'approved_cos': commitment.get('approved_change_orders_total', 0),
'final_value': commitment.get('grand_total', 0) + commitment.get('approved_change_orders_total', 0),
'paid': commitment.get('paid_to_date', 0),
'retainage_held': commitment.get('retainage_held', 0)
})
# Compile punch list summary
total_punch = len(punch_items)
completed_punch = len([p for p in punch_items if p.get('status') == 'closed'])
open_punch = total_punch - completed_punch
user_prompt = f"""Generate a complete Project Closeout Report:
PROJECT DETAILS:
{json.dumps(project, indent=2, default=str)}
BUDGET SUMMARY:
{json.dumps(budget, indent=2, default=str)}
SUBCONTRACTOR STATUS:
{json.dumps(sub_status, indent=2, default=str)}
CHANGE ORDER SUMMARY:
Total COs: {len(change_orders)}
{json.dumps(change_orders[:20], indent=2, default=str)}
PUNCH LIST:
Total items: {total_punch}
Completed: {completed_punch}
Outstanding: {open_punch}
Open items: {json.dumps([p for p in punch_items if p.get('status') != 'closed'][:10], indent=2, default=str)}
RFI SUMMARY:
Total RFIs: {len(rfis)}
Open RFIs: {len([r for r in rfis if r.get('status') != 'closed'])}
Generate the complete closeout report following the structure in your instructions.
"""
client = openai.OpenAI(api_key=api_key)
response = client.chat.completions.create(
model='gpt-5.4',
messages=[
{'role': 'system', 'content': CLOSEOUT_SYSTEM_PROMPT},
{'role': 'user', 'content': user_prompt}
],
max_tokens=8000,
temperature=0.2
)
return response.choices[0].message.contentContract Compliance Validator
Type: workflow A validation workflow that runs after every AI-generated document to check for common compliance failures: missing state-specific clauses, placeholder text not filled in, insurance minimums below thresholds, incorrect retainage percentages, missing OSHA clauses, and missing Davis-Bacon provisions on prevailing wage projects. Returns a pass/fail report with specific findings.
Implementation:
import re
from typing import List, Dict, Tuple
class ContractComplianceValidator:
"""Validates AI-generated construction documents for compliance."""
# States with statutory lien waiver forms (MUST use exact statutory language)
STATUTORY_LIEN_STATES = ['AZ', 'CA', 'FL', 'GA', 'MI', 'MS', 'MO', 'NV', 'TX', 'UT', 'WI', 'WY']
# Minimum insurance requirements (configurable per client)
DEFAULT_INSURANCE_MINS = {
'gl_per_occurrence': 1000000,
'gl_aggregate': 2000000,
'auto_liability': 1000000,
'workers_comp': 'statutory',
'umbrella': 2000000
}
def __init__(self, state: str, is_prevailing_wage: bool = False, insurance_mins: dict = None):
self.state = state.upper()
self.is_prevailing_wage = is_prevailing_wage
self.insurance_mins = insurance_mins or self.DEFAULT_INSURANCE_MINS
self.findings: List[Dict] = []
def validate(self, document_text: str, doc_type: str = 'subcontractor_agreement') -> Dict:
"""Run all applicable validations and return results."""
self.findings = []
# Universal checks
self._check_unfilled_placeholders(document_text)
self._check_draft_watermark(document_text)
self._check_signature_blocks(document_text)
self._check_governing_law(document_text)
if doc_type == 'subcontractor_agreement':
self._check_insurance_requirements(document_text)
self._check_indemnification(document_text)
self._check_lien_waiver_provisions(document_text)
self._check_osha_compliance(document_text)
self._check_retainage(document_text)
self._check_payment_terms(document_text)
self._check_dispute_resolution(document_text)
self._check_termination_clauses(document_text)
if self.is_prevailing_wage:
self._check_davis_bacon(document_text)
if self.state in self.STATUTORY_LIEN_STATES:
self._check_statutory_lien_waiver(document_text)
elif doc_type == 'change_order':
self._check_co_financial_summary(document_text)
self._check_co_authorization(document_text)
elif doc_type == 'closeout_report':
self._check_closeout_checklist(document_text)
self._check_lien_waiver_status(document_text)
# Determine overall result
critical_findings = [f for f in self.findings if f['severity'] == 'CRITICAL']
warning_findings = [f for f in self.findings if f['severity'] == 'WARNING']
return {
'passed': len(critical_findings) == 0,
'total_findings': len(self.findings),
'critical': len(critical_findings),
'warnings': len(warning_findings),
'findings': self.findings,
'recommendation': 'BLOCK - Critical issues must be resolved' if critical_findings else 'PROCEED with review of warnings' if warning_findings else 'CLEAN - Ready for human review'
}
def _check_unfilled_placeholders(self, text: str):
patterns = [
r'\[REQUIRES INPUT[^\]]*\]',
r'\[BLANK\]',
r'\{\{[^}]+\}\}',
r'\[TBD\]',
r'\[INSERT[^\]]*\]',
r'_____+',
r'\[DATA NOT PROVIDED[^\]]*\]'
]
for pattern in patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
if matches:
self.findings.append({
'severity': 'CRITICAL',
'category': 'Unfilled Placeholder',
'detail': f'Found {len(matches)} unfilled placeholders matching pattern: {matches[:3]}',
'action': 'Fill in all placeholder values before proceeding'
})
def _check_draft_watermark(self, text: str):
if 'DRAFT' not in text.upper():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Draft Watermark',
'detail': 'Document does not contain DRAFT watermark',
'action': 'Add DRAFT - AI GENERATED - REQUIRES LEGAL REVIEW header'
})
def _check_insurance_requirements(self, text: str):
insurance_keywords = ['general liability', 'commercial general liability', 'workers compensation', 'automobile liability']
missing = [kw for kw in insurance_keywords if kw not in text.lower()]
if missing:
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Insurance Requirements',
'detail': f'Missing insurance provisions for: {missing}',
'action': 'Add complete insurance requirements section'
})
# Check minimum amounts
gl_match = re.search(r'\$([\d,]+).*per occurrence', text, re.IGNORECASE)
if gl_match:
amount = int(gl_match.group(1).replace(',', ''))
if amount < self.insurance_mins['gl_per_occurrence']:
self.findings.append({
'severity': 'WARNING',
'category': 'Insurance Below Minimum',
'detail': f'GL per occurrence ${amount:,} is below minimum ${self.insurance_mins["gl_per_occurrence"]:,}',
'action': 'Verify insurance minimums with risk management'
})
def _check_lien_waiver_provisions(self, text: str):
if 'lien waiver' not in text.lower() and 'lien release' not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Lien Waiver Provisions',
'detail': 'No lien waiver requirements found in document',
'action': 'Add lien waiver requirements including state-specific statutory forms'
})
def _check_statutory_lien_waiver(self, text: str):
state_references = {
'CA': 'California Civil Code',
'TX': 'Texas Property Code',
'FL': 'Florida Statute',
'AZ': 'Arizona Revised Statutes',
'GA': 'Georgia Code'
}
if self.state in state_references:
if state_references[self.state].lower() not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Statutory Lien Waiver Reference',
'detail': f'{self.state} requires statutory lien waiver forms. Document does not reference {state_references[self.state]}.',
'action': f'Add {state_references[self.state]} statutory lien waiver form references and include forms as exhibit'
})
def _check_osha_compliance(self, text: str):
if 'osha' not in text.lower() and 'occupational safety' not in text.lower():
self.findings.append({
'severity': 'WARNING',
'category': 'Missing OSHA Compliance',
'detail': 'No OSHA compliance clause found',
'action': 'Add OSHA compliance and site safety requirements'
})
def _check_davis_bacon(self, text: str):
db_terms = ['davis-bacon', 'prevailing wage', 'certified payroll', 'wage determination']
missing = [term for term in db_terms if term not in text.lower()]
if missing:
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Prevailing Wage Provisions',
'detail': f'Prevailing wage project missing required terms: {missing}',
'action': 'Add Davis-Bacon Act compliance clauses, prevailing wage requirements, and certified payroll obligations'
})
def _check_retainage(self, text: str):
if 'retainage' not in text.lower() and 'retention' not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Retainage Provisions',
'detail': 'No retainage/retention terms found',
'action': 'Add retainage percentage, release conditions, and state-specific limitations'
})
def _check_indemnification(self, text: str):
if 'indemnif' not in text.lower():
self.findings.append({
'severity': 'WARNING',
'category': 'Missing Indemnification',
'detail': 'No indemnification clause found',
'action': 'Add indemnification provisions compliant with state anti-indemnity statutes'
})
def _check_payment_terms(self, text: str):
if 'payment' not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Payment Terms',
'detail': 'No payment provisions found',
'action': 'Add payment application procedures, timing, and state prompt payment act compliance'
})
def _check_dispute_resolution(self, text: str):
if not any(term in text.lower() for term in ['mediation', 'arbitration', 'dispute resolution']):
self.findings.append({
'severity': 'WARNING',
'category': 'Missing Dispute Resolution',
'detail': 'No dispute resolution mechanism found',
'action': 'Add mediation/arbitration/litigation clause with venue and governing law'
})
def _check_termination_clauses(self, text: str):
if 'terminat' not in text.lower():
self.findings.append({
'severity': 'WARNING',
'category': 'Missing Termination Clause',
'detail': 'No termination provisions found',
'action': 'Add termination for cause and termination for convenience clauses'
})
def _check_governing_law(self, text: str):
if 'governing law' not in text.lower() and 'governed by' not in text.lower():
self.findings.append({
'severity': 'WARNING',
'category': 'Missing Governing Law',
'detail': 'No governing law clause found',
'action': 'Add governing law provision specifying project state'
})
def _check_signature_blocks(self, text: str):
if 'signature' not in text.lower() and 'sign' not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Signature Blocks',
'detail': 'No signature blocks found',
'action': 'Add proper signature blocks for all parties'
})
def _check_co_financial_summary(self, text: str):
required_terms = ['original contract sum', 'new contract sum']
missing = [term for term in required_terms if term not in text.lower()]
if missing:
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Financial Summary',
'detail': f'Change order missing required financial terms: {missing}',
'action': 'Add complete contract adjustment summary showing original, previous changes, and new totals'
})
def _check_co_authorization(self, text: str):
if 'authorized' not in text.lower() and 'written' not in text.lower():
self.findings.append({
'severity': 'WARNING',
'category': 'Missing Authorization Reference',
'detail': 'No written authorization reference found',
'action': 'Add clause stating work shall not proceed until CO is fully executed'
})
def _check_closeout_checklist(self, text: str):
required_items = ['lien waiver', 'warranty', 'as-built', 'punch list', 'certificate of occupancy']
missing = [item for item in required_items if item not in text.lower()]
if missing:
self.findings.append({
'severity': 'WARNING',
'category': 'Incomplete Closeout Checklist',
'detail': f'Closeout report missing references to: {missing}',
'action': 'Ensure all standard closeout items are addressed'
})
def _check_lien_waiver_status(self, text: str):
if 'lien waiver' not in text.lower():
self.findings.append({
'severity': 'CRITICAL',
'category': 'Missing Lien Waiver Status',
'detail': 'Closeout report does not address lien waiver collection status',
'action': 'Add lien waiver status for every subcontractor and supplier'
})
# Usage example:
validator = ContractComplianceValidator(state='CA', is_prevailing_wage=True)
results = validator.validate(document_text, doc_type='subcontractor_agreement')
print(json.dumps(results, indent=2))Procore-to-AI Data Extraction Pipeline
Type: integration A reusable integration module that extracts structured project data from Procore's REST API and transforms it into the format required by the AI document generation agents. Handles authentication (OAuth2), pagination, rate limiting, error handling, and data transformation. Provides clean, structured JSON payloads for each document type.
Implementation:
import requests
import time
import json
from typing import Dict, List, Optional
from datetime import datetime
class ProcoreDataExtractor:
"""Extracts and transforms Procore project data for AI document generation."""
BASE_URL = 'https://api.procore.com/rest/v1.0'
RATE_LIMIT_DELAY = 0.5 # seconds between requests to avoid rate limits
def __init__(self, access_token: str, company_id: str):
self.headers = {
'Authorization': f'Bearer {access_token}',
'Procore-Company-Id': company_id,
'Content-Type': 'application/json'
}
self.company_id = company_id
def _get(self, endpoint: str, params: dict = None) -> dict:
"""Make authenticated GET request with rate limiting and error handling."""
time.sleep(self.RATE_LIMIT_DELAY)
url = f'{self.BASE_URL}{endpoint}'
response = requests.get(url, headers=self.headers, params=params or {})
if response.status_code == 429: # Rate limited
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
return self._get(endpoint, params)
response.raise_for_status()
return response.json()
def get_project_details(self, project_id: str) -> Dict:
"""Get comprehensive project information."""
project = self._get(f'/projects/{project_id}')
return {
'name': project.get('name', ''),
'number': project.get('project_number', ''),
'address': f"{project.get('address', '')} {project.get('city', '')}, {project.get('state_code', '')} {project.get('zip', '')}".strip(),
'state': project.get('state_code', ''),
'owner_name': project.get('owner', {}).get('name', '[REQUIRES INPUT]'),
'start_date': project.get('start_date', ''),
'completion_date': project.get('completion_date', ''),
'project_type': project.get('project_type', {}).get('name', 'Commercial'),
'status': project.get('status', {}).get('name', ''),
'gc_name': project.get('company', {}).get('name', '[REQUIRES INPUT]'),
'gc_address': '[FROM COMPANY SETTINGS]',
'architect': project.get('architect', '[REQUIRES INPUT]')
}
def get_subcontractor_data(self, project_id: str, vendor_id: str) -> Dict:
"""Get subcontractor details for agreement generation."""
vendor = self._get(f'/companies/{self.company_id}/vendors/{vendor_id}')
return {
'name': vendor.get('name', ''),
'address': f"{vendor.get('address', '')} {vendor.get('city', '')}, {vendor.get('state_code', '')} {vendor.get('zip', '')}".strip(),
'license_number': vendor.get('license_number', '[REQUIRES INPUT]'),
'phone': vendor.get('phone', ''),
'email': vendor.get('email_address', ''),
'trade': vendor.get('trade_name', ''),
'ein': vendor.get('tax_id', '[REQUIRES INPUT]')
}
def get_commitment_data(self, project_id: str, commitment_id: str) -> Dict:
"""Get commitment (subcontract) details for change orders."""
commitment = self._get(f'/projects/{project_id}/commitments/{commitment_id}')
return {
'number': commitment.get('number', ''),
'title': commitment.get('title', ''),
'date': commitment.get('executed_date', commitment.get('created_at', '')[:10]),
'vendor_name': commitment.get('vendor', {}).get('name', ''),
'vendor_id': commitment.get('vendor', {}).get('id', ''),
'contract_sum': commitment.get('grand_total', 0),
'approved_changes_total': commitment.get('approved_change_orders_total', 0),
'scope': commitment.get('description', ''),
'status': commitment.get('status', ''),
'retainage_pct': commitment.get('retainage_percent', 10)
}
def get_change_event_data(self, project_id: str, change_event_id: str) -> Dict:
"""Get change event details for change order generation."""
ce = self._get(f'/projects/{project_id}/change_events/{change_event_id}')
return {
'title': ce.get('title', ''),
'description': ce.get('description', ''),
'amount': ce.get('grand_total', 0),
'status': ce.get('status', ''),
'reason': ce.get('change_reason', {}).get('name', 'Undetermined'),
'reference': ce.get('number', ''),
'schedule_impact_days': ce.get('schedule_impact', '[TO BE DETERMINED]'),
'cost_breakdown': ce.get('line_items', [])
}
def get_closeout_data(self, project_id: str) -> Dict:
"""Aggregate all project data needed for closeout report."""
project = self.get_project_details(project_id)
# Get all commitments
commitments = self._get(f'/projects/{project_id}/commitments')
# Get all change order packages
cos = self._get(f'/projects/{project_id}/change_order_packages')
# Get punch items
try:
punch_items = self._get(f'/projects/{project_id}/punch_items', {'per_page': 500})
except:
punch_items = []
# Get RFIs
try:
rfis = self._get(f'/projects/{project_id}/rfis', {'per_page': 500})
except:
rfis = []
return {
'project': project,
'commitments': [{
'vendor': c.get('vendor', {}).get('name', ''),
'trade': c.get('title', ''),
'original_value': c.get('grand_total', 0),
'changes': c.get('approved_change_orders_total', 0),
'final_value': c.get('grand_total', 0) + c.get('approved_change_orders_total', 0),
'paid_to_date': c.get('paid_to_date', 0),
'retainage_held': c.get('retainage_held', 0),
'status': c.get('status', '')
} for c in commitments],
'change_orders': [{
'number': co.get('number', ''),
'title': co.get('title', ''),
'amount': co.get('grand_total', 0),
'status': co.get('status', '')
} for co in cos],
'punch_list': {
'total': len(punch_items),
'completed': len([p for p in punch_items if p.get('status', {}).get('name', '') == 'Closed']),
'open_items': [{
'title': p.get('name', ''),
'assignee': p.get('assignee', {}).get('name', 'Unassigned'),
'due_date': p.get('due_date', 'No date')
} for p in punch_items if p.get('status', {}).get('name', '') != 'Closed'][:20]
},
'rfis': {
'total': len(rfis),
'open': len([r for r in rfis if r.get('status', '') != 'closed'])
}
}
# Usage:
extractor = ProcoreDataExtractor(access_token='...', company_id='12345')
project_data = extractor.get_project_details('67890')
sub_data = extractor.get_subcontractor_data('67890', 'vendor_111')
closeout_data = extractor.get_closeout_data('67890')Document Generation Orchestrator
Type: workflow A master orchestration workflow that ties together data extraction, AI generation, compliance validation, SharePoint storage, Teams notification, and DocuSign routing into a single end-to-end pipeline. Handles error recovery, logging, and status tracking. Callable from Zapier, Power Automate, or directly via API.
import json
import logging
from datetime import datetime
from typing import Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('DocumentOrchestrator')
class DocumentOrchestrator:
"""Orchestrates the complete AI document generation pipeline."""
def __init__(self, config: Dict):
self.openai_key = config['openai_api_key']
self.procore_token = config['procore_access_token']
self.procore_company_id = config['procore_company_id']
self.graph_token = config['ms_graph_token']
self.sharepoint_site_id = config['sharepoint_site_id']
self.docusign_config = config.get('docusign', {})
self.teams_webhook_url = config.get('teams_webhook_url', '')
# Initialize sub-components
from procore_data_extractor import ProcoreDataExtractor
self.procore = ProcoreDataExtractor(self.procore_token, self.procore_company_id)
def generate_document(self, doc_type: str, project_id: str, **kwargs) -> Dict:
"""Main entry point - generates any document type end-to-end."""
logger.info(f'Starting {doc_type} generation for project {project_id}')
result = {'status': 'started', 'doc_type': doc_type, 'timestamp': datetime.now().isoformat()}
try:
# Step 1: Extract data from Procore
logger.info('Step 1: Extracting project data from Procore')
project_data = self.procore.get_project_details(project_id)
state = project_data.get('state', 'TX') # Default to TX if not found
# Step 2: Generate document based on type
logger.info(f'Step 2: Generating {doc_type} via AI')
if doc_type == 'subcontractor_agreement':
vendor_id = kwargs.get('vendor_id')
commitment_id = kwargs.get('commitment_id')
sub_data = self.procore.get_subcontractor_data(project_id, vendor_id)
commitment = self.procore.get_commitment_data(project_id, commitment_id)
sub_data.update(commitment)
document_text = generate_subcontractor_agreement(project_data, sub_data, state, self.openai_key)
library = 'Subcontractor Agreements'
filename = f"SubAgreement_{project_data['number']}_{sub_data['name']}_{datetime.now().strftime('%Y%m%d')}.docx"
elif doc_type == 'change_order':
change_event_id = kwargs.get('change_event_id')
commitment_id = kwargs.get('commitment_id')
change_data = self.procore.get_change_event_data(project_id, change_event_id)
commitment = self.procore.get_commitment_data(project_id, commitment_id)
document_text = generate_change_order(change_data, commitment, project_data, self.openai_key)
library = 'Change Orders'
filename = f"CO_{project_data['number']}_{change_data.get('reference', 'NEW')}_{datetime.now().strftime('%Y%m%d')}.docx"
elif doc_type == 'closeout_report':
closeout_data = self.procore.get_closeout_data(project_id)
document_text = generate_closeout_report(project_id, self.procore_token, self.openai_key)
library = 'Closeout Reports'
filename = f"Closeout_{project_data['number']}_{datetime.now().strftime('%Y%m%d')}.docx"
else:
raise ValueError(f'Unknown document type: {doc_type}')
result['generated'] = True
# Step 3: Validate compliance
logger.info('Step 3: Running compliance validation')
is_prevailing = kwargs.get('is_prevailing_wage', False)
validator = ContractComplianceValidator(state=state, is_prevailing_wage=is_prevailing)
validation = validator.validate(document_text, doc_type)
result['validation'] = validation
if not validation['passed']:
logger.warning(f'Validation FAILED with {validation["critical"]} critical findings')
# Still save the document but flag it
filename = f'REVIEW_NEEDED_{filename}'
# Step 4: Save to SharePoint
logger.info(f'Step 4: Uploading to SharePoint library: {library}')
file_url = self._upload_to_sharepoint(library, filename, document_text, {
'ContractStatus': 'AI Generated' if validation['passed'] else 'Needs Review',
'ProjectName': project_data.get('name', ''),
'StateJurisdiction': state
})
result['sharepoint_url'] = file_url
# Step 5: Send Teams notification
logger.info('Step 5: Sending Teams notification')
self._notify_teams(
title=f'New {doc_type.replace("_", " ").title()} Draft Generated',
message=f'Project: {project_data["name"]}\nFile: {filename}\nValidation: {validation["recommendation"]}\n\n[Open Document]({file_url})',
color='attention' if not validation['passed'] else 'good'
)
result['status'] = 'completed'
result['filename'] = filename
logger.info(f'Document generation complete: {filename}')
except Exception as e:
logger.error(f'Document generation failed: {str(e)}')
result['status'] = 'failed'
result['error'] = str(e)
# Notify MSP of failure
self._notify_teams(
title=f'FAILED: {doc_type} Generation Error',
message=f'Project: {project_id}\nError: {str(e)}\n\nMSP action required.',
color='attention'
)
return result
def _upload_to_sharepoint(self, library: str, filename: str, content: str, metadata: dict) -> str:
"""Upload document to SharePoint via Microsoft Graph API."""
import requests
# Convert text to docx (simplified - in production use python-docx)
from docx import Document
import io
doc = Document()
doc.add_heading('DRAFT - AI GENERATED - REQUIRES LEGAL REVIEW', level=0)
for para in content.split('\n'):
doc.add_paragraph(para)
buffer = io.BytesIO()
doc.save(buffer)
buffer.seek(0)
# Upload via Graph API
upload_url = f'https://graph.microsoft.com/v1.0/sites/{self.sharepoint_site_id}/drive/root:/{library}/{filename}:/content'
headers = {
'Authorization': f'Bearer {self.graph_token}',
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}
response = requests.put(upload_url, headers=headers, data=buffer.read())
response.raise_for_status()
return response.json().get('webUrl', '')
def _notify_teams(self, title: str, message: str, color: str = 'default'):
"""Send notification to Teams channel via webhook."""
import requests
if not self.teams_webhook_url:
return
payload = {
'@type': 'MessageCard',
'themeColor': '0076D7' if color == 'good' else 'FF0000',
'summary': title,
'sections': [{
'activityTitle': title,
'text': message,
'markdown': True
}]
}
requests.post(self.teams_webhook_url, json=payload)
# Configuration template for MSP deployment:
CONFIG_TEMPLATE = {
'openai_api_key': 'sk-proj-XXXXX', # Store in Azure Key Vault
'procore_access_token': 'XXXXX', # OAuth2 refresh token flow
'procore_company_id': '12345',
'ms_graph_token': 'XXXXX', # App registration with Sites.ReadWrite.All
'sharepoint_site_id': 'XXXXX',
'teams_webhook_url': 'https://outlook.office.com/webhook/XXXXX',
'docusign': {
'account_id': 'XXXXX',
'base_url': 'https://na4.docusign.net',
'integration_key': 'XXXXX'
}
}Testing & Validation
- TEMPLATE LIBRARY TEST: Upload 3 sample AIA forms (A401, G701, G704) to SharePoint Contract Templates library. Verify they appear with correct metadata, versioning is enabled, and Copilot can reference them when prompted with 'Summarize the subcontractor agreement template in Contract Templates.'
- SUBCONTRACTOR AGREEMENT GENERATION TEST: Using a real project in Procore, trigger the Subcontractor Agreement Drafter agent with complete project data for a known subcontractor. Verify the output contains: (1) correct project name/address, (2) correct subcontractor name/address, (3) correct contract sum, (4) state-specific lien waiver provisions for the project's state, (5) OSHA compliance clause, (6) insurance requirements with correct minimums, (7) DRAFT watermark. Time the generation—should complete in under 2 minutes.
- COMPLIANCE VALIDATOR TEST: Run the ContractComplianceValidator against a deliberately flawed document (missing insurance clause, placeholder text unfilled, no lien waiver provision). Verify it returns CRITICAL findings for each deficiency. Then run against a correctly generated document and verify it passes with no critical findings.
- STATE-SPECIFIC CLAUSE TEST: Generate subcontractor agreements for projects in California, Texas, and Florida. Verify each document includes the correct state-specific statutory lien waiver references (California Civil Code §8132-8138, Texas Property Code §53.101, Florida Statutes Chapter 713). Verify a non-statutory state (e.g., Colorado) does NOT include statutory form references.
- PREVAILING WAGE TEST: Generate a subcontractor agreement for a public works project marked as prevailing wage in Procore. Verify the document includes Davis-Bacon Act references, certified payroll requirements, prevailing wage determination references, and DOL reporting obligations. Verify these clauses are ABSENT from non-prevailing-wage project agreements.
- CHANGE ORDER GENERATION TEST: Create a test change event in Procore with known values ($15,000 addition, 5 days schedule impact). Trigger the Change Order Generator. Verify: (1) correct original contract sum, (2) correct previous change total, (3) accurate new contract sum calculation, (4) schedule impact stated correctly, (5) reference to triggering RFI/ASI, (6) signature blocks for all required parties.
- CLOSEOUT REPORT GENERATION TEST: Select a project near completion in Procore. Trigger the Closeout Report Builder. Verify the report includes: (1) accurate financial summary matching Procore budget, (2) list of all subcontractors with payment status, (3) punch list item counts matching Procore, (4) RFI summary, (5) complete closeout checklist with all standard items, (6) all data matches Procore—no fabricated numbers.
- ZAPIER WORKFLOW END-TO-END TEST: Create a new commitment in Procore. Verify within 5 minutes: (1) Zapier trigger fires, (2) OpenAI API is called, (3) Word document appears in correct SharePoint library, (4) metadata is correctly populated, (5) Teams notification is sent to Contract Review channel with link to document, (6) document opens correctly in Word Online and desktop Word.
- DOCUSIGN ROUTING TEST: Take an approved (human-reviewed) subcontractor agreement and send via DocuSign. Verify: (1) envelope creates successfully with correct template, (2) subcontractor receives signing email, (3) signing completes on desktop and mobile, (4) executed document auto-files to SharePoint with status updated to 'Executed', (5) all signature fields, dates, and initials captured correctly.
- LEGALON REVIEW TEST: Upload an AI-generated subcontractor agreement to LegalOn. Verify: (1) construction playbook activates, (2) risk flags are identified and ranked by severity, (3) specific clause recommendations are provided, (4) turnaround time is under 5 minutes for analysis. Compare LegalOn findings against the Compliance Validator output—they should identify similar issues.
- MOBILE/FIELD ACCESS TEST: From a Surface Pro tablet on cellular connection (simulating field conditions), access SharePoint document library, open an AI-generated change order, make an edit, save, and send for signature via DocuSign. Verify entire workflow completes successfully on 5 Mbps connection within 10 minutes.
- ROLE-BASED ACCESS TEST: Log in as three different test users (Admin, Reviewer, Field User). Verify: (1) Admin can generate, edit, and send all document types, (2) Reviewer can view and approve but not generate, (3) Field User can view documents and sign but not edit templates. Verify unauthorized actions are blocked.
- CONCURRENT GENERATION TEST: Trigger 5 subcontractor agreements simultaneously (for 5 different subs on the same project). Verify all 5 generate correctly without data crossover, OpenAI API handles concurrent requests, and all 5 appear in SharePoint with correct metadata for each respective subcontractor.
- BACKUP AND RECOVERY TEST: Delete a generated document from SharePoint. Verify it can be recovered from the SharePoint recycle bin with version history intact. Verify M365 retention policies retain all contract documents for the configured retention period (minimum 7 years recommended for construction).
Client Handoff
Client Handoff Deliverables and Training
Documentation Package:
Training Sessions (recorded for future onboarding):
- Session 1 (2 hours): Power users — full system walkthrough, document generation, LegalOn review, DocuSign routing
- Session 2 (1 hour): Reviewers — how to review AI drafts, approval workflow, mobile access
- Session 3 (1 hour): Field users — change order initiation, mobile signatures, closeout documentation
- Session 4 (30 min): Executive overview — system capabilities, compliance assurances, ROI metrics
Success Criteria Review (30-day check-in):
Transition Items:
- Transfer API key management documentation and rotation schedule
- Confirm client admin can add/remove Copilot licenses
- Verify client has admin access to Zapier workflows for monitoring
- Provide 30-day hypercare period with daily check-ins tapering to weekly
- Schedule 90-day comprehensive review meeting
Maintenance
Ongoing Maintenance Responsibilities
Weekly (MSP Technician - 1 hour/week):
- Monitor Zapier workflow success/failure rates via Zapier dashboard; investigate any failed Zaps
- Check OpenAI API usage dashboard to ensure costs are within budget ($50-200/month expected range)
- Review SharePoint document library for any documents stuck in 'AI Generated' status for more than 5 business days (indicates adoption issues)
- Monitor Teams 'AI Contract Support' channel for user questions/issues
Monthly (MSP Technician - 2-3 hours/month):
- Review compliance validator logs: identify patterns in critical findings that indicate prompt refinement needed
- Check for Microsoft 365 Copilot updates that may affect agent behavior; test agents after major Copilot updates
- Review DocuSign envelope usage vs. allocation (100 envelopes/user/year cap on Business Pro)
- Update Procore API access token if using OAuth2 refresh flow; verify API connectivity
- Generate usage report for client: documents generated by type, average generation time, compliance pass rate
- Verify backup/retention policies are functioning: spot-check that documents from 30 days ago are recoverable
Quarterly (MSP Senior Tech + Client PM - 4-6 hours/quarter):
- State Compliance Clause Review: Check for legislative updates in all states where client operates. Update lien waiver forms, retainage limits, prompt payment provisions, and anti-indemnity statutes. This is a billable compliance maintenance service ($1,000-$2,500/quarter).
- Prompt Optimization: Review AI output quality with client PMs. Identify recurring edits they make to AI drafts—these indicate prompt improvements needed. Refine system prompts for each document type.
- Template Updates: Check AIA and ConsensusDocs for updated form editions. Update SharePoint template library accordingly.
- LegalOn/Spellbook Updates: Verify construction playbooks are current; enable any new features released by vendor.
- Security Review: Audit user access permissions, review Entra ID sign-in logs for anomalies, verify MFA compliance, rotate API keys.
Semi-Annually (MSP Account Manager + Client Executive - 2 hours):
- Business Review: Present ROI analysis (time saved, documents generated, error reduction), discuss expansion opportunities (new document types, new office locations, additional integrations)
- License True-Up: Review Copilot seat count vs. active users; adjust licensing as team grows/shrinks
- Technology Roadmap: Brief client on upcoming AI capabilities (new models, enhanced Copilot features, new construction-specific platforms)
Trigger-Based Maintenance:
- New State Operations: When client starts projects in a new state, add state-specific clauses to the compliance library (2-4 hours, coordinate with attorney)
- API Breaking Changes: Procore, OpenAI, or Microsoft Graph API version updates may require code changes. Monitor vendor changelogs and test in sandbox before production update.
- Legal/Regulatory Changes: Davis-Bacon prevailing wage updates, new state privacy laws, OSHA regulation changes—update templates and compliance validator accordingly
- Copilot Model Updates: When Microsoft updates the underlying model powering Copilot, re-test all agents for quality regression
SLA Considerations:
- Response Time: 4-hour response for document generation pipeline failures (business hours); next-business-day for non-critical issues
- Uptime Target: 99.5% availability for the document generation pipeline during business hours (dependent on cloud service uptime)
- Escalation Path: L1 (MSP helpdesk - connectivity/access issues) → L2 (MSP senior tech - workflow/integration issues) → L3 (MSP AI specialist - prompt engineering/compliance issues) → External (client attorney for legal review, vendor support for platform issues)
- Data Retention: All AI-generated documents retained in SharePoint for minimum 10 years (covering typical construction statute of repose). AI generation logs retained for 3 years.
Alternatives
...
Copilot-Only Simplified Deployment
Eliminate the custom API integration, Zapier automation, and LegalOn/Spellbook platforms. Deploy only Microsoft 365 Copilot with pre-built Word templates and SharePoint document libraries. Users manually invoke Copilot in Word to draft documents from templates, with no automated data pipeline from Procore. Documents are manually routed for signature via DocuSign or PandaDoc.
Document Crunch + PandaDoc Stack
Replace the Microsoft Copilot + LegalOn combination with Document Crunch (CrunchAI) for AI contract review and PandaDoc for document generation and e-signatures. Document Crunch specializes in construction-specific AI analysis of contracts, specifications, and drawings. PandaDoc handles template-based document generation with built-in e-signatures and CRM integration. This approach uses two purpose-built tools instead of a general platform plus add-on.
Enterprise CLM with Ironclad
Deploy Ironclad as a full Contract Lifecycle Management platform, replacing the patchwork of Copilot + LegalOn + DocuSign + Zapier with a single enterprise platform that handles contract generation, AI review, workflow routing, e-signatures, and obligation tracking. Ironclad was named a 2025 Gartner Magic Quadrant Leader for CLM.
On-Premise LLM with Ollama for Data Sovereignty
Instead of cloud-based OpenAI or Azure OpenAI, deploy a local large language model server using Ollama with an open-source model (Llama 3.1 70B or Mixtral 8x22B). All contract data stays on-premise—nothing is sent to external AI services. Combines with the same SharePoint, DocuSign, and Procore integrations but routes AI generation through the local server.
On-premise LLM via Ollama is not recommended as the primary approach for most SMB contractors due to quality and maintenance tradeoffs.
Manual Templates with AI-Assisted Review Only
Skip AI document generation entirely. Instead, use traditional Word/PDF templates (AIA forms) filled out manually, and deploy AI only for contract review—using LegalOn or Document Crunch to analyze completed contracts for risk, missing clauses, and compliance issues before execution. This is an 'AI as safety net' approach rather than 'AI as drafter.'
Want early access to the full toolkit?