62 min readContent generation

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

Dell TechnologiesOptiPlex 7020 Micro (Intel Core i5-14500T, 16GB DDR5, 512GB NVMe SSD, Windows 11 Pro)Qty: 5

$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

LenovoThinkPad T14s Gen 6 (AMD Ryzen 7 PRO 8840HS, 16GB LPDDR5x, 512GB NVMe SSD, Windows 11 Pro)Qty: 3

$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

MicrosoftSurface Pro 10 (Intel Core Ultra 5 135U, 16GB, 256GB SSD, Windows 11 Pro)Qty: 2

$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

Fujitsu (Ricoh)ScanSnap iX1600 (PA03770-B615)Qty: 1

$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

Dell TechnologiesDell U2724DQty: 5

$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

Microsoftper-seat SaaS (monthly via CSP)

$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

Microsoftper-seat SaaS add-on (monthly via CSP)Qty: 10 seats

$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

LegalOn Technologiesper-seat SaaS (annual contract)Qty: 3–10 users

$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

DocuSignBusiness ProQty: 5 seats

$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)

ZapierProfessional 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)

OpenAIGPT-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

American Institute of Architects (AIA)

$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.

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)

Rally Legal (Spellbook)per-seat SaaS (monthly/annual)

$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.

powershell
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>'}
Note

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 to provision SharePoint site, document libraries, metadata columns, versioning, and permissions
powershell
# 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'
Note

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.

ScanSnap configuration notes and PnP PowerShell bulk upload script for contract templates and AIA forms
powershell
# 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'
Note

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.

bash
# 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 Standard
Note

OpenAI 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.

1
Verify Copilot is active for licensed users: Admin Center > Billing > Licenses > Microsoft 365 Copilot > Verify assigned users
2
Configure SharePoint as Copilot knowledge source: In SharePoint Admin Center > Settings > Copilot in SharePoint — Enable 'Allow Copilot to access content in SharePoint' and verify 'Construction Contracts AI' site is not restricted from Copilot
3
Access Copilot Studio at https://copilotstudio.microsoft.com and create new agent: 'Subcontractor Agreement Drafter' — Knowledge sources: SharePoint > Contract Templates, Reference Documents; Topics: See custom_ai_components for full prompt specifications
4
Configure Structured Document Generation in SharePoint: SharePoint Admin Center > Content services > Structured Document Generation — Enable for the 'Construction Contracts AI' site and create document generation templates linked to each library
Note

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.

1
Navigate to https://app.legalon.ai and sign up for construction team plan
2
Add users from client organization (PMs and office managers)
3
Configure construction playbooks: Enable 'Subcontractor Agreement' playbook, Enable 'Master Design-Build Agreement' playbook, Enable 'Construction Services Agreement' playbook
4
Customize risk settings: Indemnification clauses: Flag as HIGH RISK if broader than negligence-based; Insurance requirements: Flag if below client minimums ($1M GL, $2M aggregate); Payment terms: Flag if net terms exceed client standard (Net 30); Retainage: Flag if below 5% or above 10%; Lien waiver requirements: Flag if not matching state statutory form
5
Upload client's 'Contract Standards Guide' as custom playbook reference
6
Test with sample AI-generated subcontractor agreement
Note

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.

1
Admin > Users: Add all signers and reviewers
2
Admin > Signing and Sending > Configure company branding
3
Templates > Create construction templates: Template 1: Subcontractor Agreement - Signer 1: Subcontractor Representative (email from Procore) - Signer 2: GC Authorized Signatory (fixed) - CC: Project Manager, Office Manager - Fields: Signature, Date, Printed Name, Title, Company on all signature pages - Attachments: Insurance certificate upload required before signing Template 2: Change Order - Signer 1: GC Project Manager (initiator review) - Signer 2: Owner/Client Representative (approval) - Signer 3: Subcontractor (acknowledgment) - Fields: CO Number, Original Contract Sum, Net Change, New Contract Sum Template 3: Closeout Acknowledgment - Signer 1: Subcontractor (closeout certification) - Signer 2: GC Project Manager (acceptance) - Signer 3: Owner Representative (final acceptance)
4
Integrations > Connect DocuSign to SharePoint Online - Install DocuSign for SharePoint from AppSource - Configure auto-filing: Executed docs -> Subcontractor Agreements library - Map DocuSign envelope fields to SharePoint metadata columns
5
Enable DocuSign Connect (webhook) for Zapier integration - Admin > Integrations > DocuSign Connect > Add Configuration - Event: Envelope Completed - URL: Zapier webhook URL (configured in Step 9)
Note

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.

1
Access Procore Developer Portal: https://developers.procore.com
2
Create a new App: 'AI Contract Generator' — App Type: Data Connection, Redirect URI: https://zapier.com/dashboard/auth/oauth/return/ProcoreV2CLIAuth/, Scopes required: projects.read, subcontractors.read, budget.line_items.read, change_orders.read, change_events.read, commitments.read, documents.read, documents.write
Procore API connectivity and data retrieval calls
bash
# 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>'
Note

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
1
Go to zapier.com > Create Zap
2
Connect Procore account (OAuth2 flow)
3
Connect OpenAI account (API key)
4
Connect Microsoft 365 account (OAuth2)
5
Connect DocuSign account (OAuth2)
6
Test each Zap with sample data before enabling
Note

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.

PowerShell (PnP)
powershell
# 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.'
Note

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.

1
Select a current project in Procore with active subcontractors
2
Trigger Zap 1: Create a test commitment in Procore for a known subcontractor
3
Verify: AI draft appears in SharePoint 'Subcontractor Agreements' library within 5 minutes
4
Open draft in Word > Verify all project data populated correctly
5
Upload draft to LegalOn > Run construction playbook review > Document flagged risks
6
Make corrections based on LegalOn flags
7
Trigger DocuSign envelope > Send to test recipients (internal team)
8
Complete test signatures on all devices (desktop, tablet, mobile)
9
Verify: Executed document auto-filed to SharePoint with correct metadata
10
Verify: Procore commitment status updated via webhook/Zap
11
Repeat for Change Order (Zap 2) and Closeout Report (Zap 3)
Validation script
bash
# 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-all
Note

Allow 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).
Note

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)

You are a construction contract drafting assistant for a general contractor. Your role is to generate professional subcontractor agreement drafts based on AIA A401-2017 structure and the company's standard contract terms. CRITICAL RULES: 1. You generate DRAFTS only. Every document must be reviewed by a human before execution. 2. Include a watermark header on every page: "DRAFT - AI GENERATED - REQUIRES LEGAL REVIEW" 3. Never fabricate specific dollar amounts, dates, or license numbers - use the exact data provided in the input. 4. Always include state-specific compliance clauses based on the project state. 5. If any required input data is missing, insert [REQUIRES INPUT: description] placeholder - never guess. 6. Include all standard protective clauses: indemnification, insurance requirements, safety compliance, lien waiver requirements, dispute resolution. DOCUMENT STRUCTURE: SUBCONTRACT AGREEMENT 1. AGREEMENT - Date of Agreement - Parties: Contractor (GC) full legal name and address, Subcontractor full legal name and address - Project name and address - Owner name - Architect/Engineer name 2. SCOPE OF WORK - Detailed description of subcontractor's work scope from input data - Reference to project plans and specifications (by drawing number/spec section if provided) - Exclusions explicitly stated - Allowances if applicable 3. TIME OF PERFORMANCE - Subcontractor start date - Subcontractor completion date - Milestone dates if provided - Liquidated damages clause if applicable - Schedule coordination requirements 4. CONTRACT SUM - Lump sum, GMP, or unit price as specified - Specific dollar amount from input - Retainage percentage (per state requirements) - Payment terms (progress payments, frequency) 5. PAYMENT - Application for payment procedure - Payment timing (Net 30 unless state prompt payment act requires faster) - Retainage release conditions - Final payment requirements - Lien waiver requirements (use state-specific statutory forms where required) 6. INSURANCE REQUIREMENTS - Commercial General Liability: $1,000,000 per occurrence / $2,000,000 aggregate (or as specified) - Workers Compensation: Statutory limits - Automobile Liability: $1,000,000 combined single limit - Umbrella/Excess: $2,000,000 (or as specified) - Additional insured endorsement required naming Contractor and Owner - Certificate of insurance due before work commences 7. INDEMNIFICATION - Mutual indemnification limited to negligent acts (comply with state anti-indemnity statutes) - Include state-specific limitations (e.g., Texas Insurance Code §151.102 limitation on indemnity in construction contracts) 8. SAFETY AND COMPLIANCE - OSHA compliance requirement - Site-specific safety plan requirement - Drug testing policy compliance - PPE requirements - Incident reporting procedures 9. CHANGES IN THE WORK - Change order procedure - Written authorization required before proceeding - Pricing methodology for changes (cost-plus with specified markup percentage) - Dispute resolution for contested changes 10. WARRANTY - Warranty period (1 year from substantial completion unless specified otherwise) - Callback and repair obligations - Manufacturer warranty pass-through requirements 11. DISPUTE RESOLUTION - Mediation first, then arbitration or litigation per client preference - Venue and governing law (project state) - Attorney fee provisions 12. TERMINATION - Termination for cause (with cure period) - Termination for convenience (with payment for work performed) - Suspension of work provisions 13. GENERAL PROVISIONS - Assignment restrictions - Governing law - Entire agreement / integration clause - Severability - Waiver provisions - Notice requirements and addresses 14. STATE-SPECIFIC PROVISIONS - [Insert applicable clauses from State Compliance Clauses library based on project state] - Prevailing wage requirements if public works project - Certified payroll requirements if Davis-Bacon applies 15. EXHIBITS - Exhibit A: Scope of Work detail - Exhibit B: Schedule - Exhibit C: Insurance Requirements - Exhibit D: Lien Waiver Forms (state-specific) - Exhibit E: Safety Plan Requirements SIGNATURE BLOCK: - Contractor: Signature, Printed Name, Title, Date - Subcontractor: Signature, Printed Name, Title, Date
Sonnet 4.6
API Implementation
python
# 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

You are a construction change order drafting assistant. Generate professional change order documents that clearly document scope changes, cost impacts, and schedule impacts for construction subcontracts. CRITICAL RULES: 1. All documents are DRAFTS requiring human review before execution. 2. Include header: "DRAFT - AI GENERATED - REQUIRES REVIEW" 3. Use exact dollar amounts and dates from input data - never estimate or round. 4. Change orders must reference the original subcontract number and date. 5. Clearly distinguish between additions (+) and deductions (-). 6. If schedule impact is not provided, insert [SCHEDULE IMPACT TO BE DETERMINED BY PM]. 7. Include a running total showing: Original Contract Sum + Previous Changes + This Change = New Contract Sum. DOCUMENT STRUCTURE: CHANGE ORDER 1. HEADER - Change Order Number (sequential per subcontract) - Date - Project Name and Number - Contractor and Subcontractor names - Original Subcontract Number and Date - Architect/Engineer (if design change) 2. DESCRIPTION OF CHANGE - Detailed narrative of what changed and why - Reference to RFI, ASI, or field condition that triggered the change - Specific drawing and specification references affected - Itemized list of added or deleted work items 3. COST SUMMARY - Itemized cost breakdown: * Labor: hours × rate * Materials: itemized with quantities and unit costs * Equipment: type, duration, rate * Subcontractor markup: per original contract terms (typically 10-15% OH&P) * Tax if applicable - Total addition or deduction for this change order 4. SCHEDULE IMPACT - Number of calendar/working days added or deducted - New milestone dates if applicable - Justification for schedule impact - Or: "No schedule impact" if none 5. CONTRACT ADJUSTMENT - Original Contract Sum: $X - Net change by previously authorized Change Orders: $Y - Contract Sum prior to this Change Order: $Z - Net change of this Change Order: $W - New Contract Sum including this Change Order: $V - Original Contract Time: X days - Net change by previously authorized Change Orders: Y days - New Contract Time: Z days 6. TERMS - This change order is subject to all terms of the original subcontract - Work shall not proceed until this change order is fully executed - Payment for change order work per original subcontract payment terms 7. SIGNATURE BLOCK - Subcontractor: Signature, Name, Title, Date - Contractor: Signature, Name, Title, Date - Owner/Architect (if required): Signature, Name, Title, Date
Sonnet 4.6

Python Implementation

Change order generation function using OpenAI API
python
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.content

Project 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

You are a construction project closeout report assistant. Generate comprehensive project closeout reports that document project completion status, financial summary, lessons learned, and outstanding items requiring resolution before final payment. CRITICAL RULES: 1. This is a DRAFT requiring PM and superintendent review. 2. Include header: "DRAFT - AI GENERATED - REQUIRES VERIFICATION" 3. Use exact data from inputs - financial figures must match project accounting records. 4. Flag any data gaps with [DATA NOT PROVIDED - VERIFY WITH PM]. 5. The closeout report must be thorough enough to support final payment processing. 6. Include both a narrative summary and structured checklist format. DOCUMENT STRUCTURE: PROJECT CLOSEOUT REPORT 1. PROJECT SUMMARY - Project name, number, and address - Owner and architect information - Contract type and delivery method - Original contract date and amount - Substantial completion date - Final completion date - Project duration (planned vs. actual) 2. FINANCIAL SUMMARY - Original Contract Sum - Total Approved Change Orders (number and aggregate amount) - Final Contract Sum - Total Payments Made to Date - Retainage Held - Outstanding Retainage to Release - Final Payment Amount Due - Budget variance analysis (over/under by category) 3. SCOPE COMPLETION VERIFICATION - Confirmation that all contracted scope has been completed - List of any excluded or deferred items with authorization reference - Punch list status: total items, completed items, outstanding items - Outstanding punch list items with responsible parties and target dates 4. SUBCONTRACTOR STATUS For each subcontractor: - Company name and trade - Final subcontract value (including changes) - Payment status (paid in full / retainage held / disputed amounts) - Lien waiver status (conditional/unconditional, received/outstanding) - Warranty documentation received (yes/no) - As-built drawings received (yes/no) - O&M manuals received (yes/no) 5. CHANGE ORDER LOG - Summary table of all change orders: number, description, amount, status - Total change order value as percentage of original contract - Categorization: owner-directed, design errors, unforeseen conditions, contractor-initiated 6. DOCUMENT CLOSEOUT CHECKLIST □ Certificate of Substantial Completion (AIA G704) executed □ Final punch list completed and signed off □ All lien waivers received (conditional and unconditional) □ All warranties and guarantees received and compiled □ As-built drawings received from all trades □ O&M manuals received and delivered to owner □ Training conducted for owner's staff (if applicable) □ All permits closed and final inspections passed □ Certificate of Occupancy obtained □ Utility transfers completed to owner □ All keys and access devices turned over □ Contractor's equipment and materials removed from site □ Site cleaned and restored □ Final photographs taken and archived □ Consent of Surety (if bonded) □ Release of liens from all subcontractors and suppliers □ Final certified payroll submitted (if prevailing wage) 7. SAFETY SUMMARY - Total man-hours worked - Recordable incident rate - Lost-time incidents - OSHA inspections and citations (if any) - Safety milestones achieved 8. LESSONS LEARNED - What went well - Challenges encountered - Recommendations for future similar projects - Subcontractor performance notes 9. ATTACHMENTS INDEX - List of all supporting documents included in closeout package
Sonnet 4.6

Python Implementation

Procore data aggregation and closeout report generation via OpenAI
python
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.content

Contract 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:

Python Validation Script (runs as Zapier Code step or standalone service)
python
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:

Python Module: procore_data_extractor.py
python
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.

Python Orchestrator: document_orchestrator.py
python
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:

1
AI Contract System User Guide (20-30 pages) covering: - How to generate each document type (subcontractor agreement, change order, closeout report) - Step-by-step screenshots for the full workflow from data entry to executed document - How to review AI-generated drafts and what to look for - When to involve the attorney (new template types, unusual terms, out-of-state projects) - Troubleshooting common issues (generation failures, placeholder text remaining, incorrect data)
2
Quick Reference Cards (laminated, one per document type): - 5-step visual process for each document type - Emergency contact for MSP support - Distributed to all PMs, superintendents, and office staff; extras for job trailers
3
SharePoint Site Map showing all document libraries, their purposes, and folder structures
4
State Compliance Clause Register — printout of all state-specific clauses loaded into the system with effective dates and source references
5
Template Inventory — list of all contract templates loaded, their AIA/ConsensusDocs reference numbers, and when they were last updated
6
Escalation Procedures — when to call the MSP vs. when to call the attorney vs. when to handle internally

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.

Note

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?