This workflow corresponds to n8n.io template #13687 — we link there as the canonical source.
This workflow follows the Agent → Airtable recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "V38vLc6EBphmD2Up",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Construction Delay Predictor",
"tags": [],
"nodes": [
{
"id": "cb1697be-aa10-46e0-b113-a90f004e25eb",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-176
],
"parameters": {
"width": 940,
"height": 1596,
"content": "## AI Construction Delay Predictor\n\nThis workflow monitors active construction projects in real time, ingests weather forecasts, supplier delivery statuses, and crew/resource availability, then uses Claude AI to predict delay risk, estimate schedule impact, and generate mitigation playbooks for project managers.\n\n### How it works\n\n1. **Trigger** \u2014 Webhook (on-demand) or daily schedule kick-off\n2. **Load Active Projects** \u2014 Pulls project list from your PM system (Procore / Airtable / Sheets)\n3. **Fetch Weather Forecast** \u2014 7-day forecast for each project site location\n4. **Fetch Supplier Status** \u2014 Checks open purchase orders and delivery ETAs\n5. **Fetch Resource Availability** \u2014 Crew headcount, equipment, subcontractor status\n6. **Combine Risk Data** \u2014 Merges all data streams per project\n7. **AI Delay Prediction** \u2014 Claude AI scores delay probability and generates mitigation plan\n8. **Severity Routing** \u2014 Routes CRITICAL/HIGH risk projects to immediate alert path\n9. **Notify Project Managers** \u2014 Slack alert with risk summary and action items\n10. **Update PM Dashboard** \u2014 Writes prediction back to Airtable / Google Sheets\n11. **Create Risk Ticket** \u2014 Opens Jira / Linear issue for HIGH+ risk projects\n12. **Send Daily Briefing** \u2014 Email digest of all at-risk projects\n\n### Setup Steps\n\n1. Import workflow into n8n\n2. Configure credentials:\n - **Anthropic API** \u2014 Claude AI for delay prediction\n - **OpenWeatherMap API** \u2014 Site weather forecasts\n - **Airtable / Google Sheets** \u2014 Project & resource data\n - **Procore API** \u2014 Schedule and RFI data (optional)\n - **Slack OAuth** \u2014 Project manager alerts\n - **Jira API** \u2014 Risk issue tracking\n - **SendGrid / SMTP** \u2014 Daily email briefing\n3. Set your Airtable base ID and table names\n4. Configure Slack channel IDs per severity level\n5. Set your risk threshold (default: 60%) in the routing node\n6. Activate the workflow\n\n### Sample Webhook Payload\n```json\n{\n \"projectId\": \"PROJ-2025-042\",\n \"projectName\": \"Riverside Commercial Tower\",\n \"siteLocation\": { \"lat\": -33.8688, \"lon\": 151.2093 },\n \"plannedEndDate\": \"2025-11-15\",\n \"currentPhase\": \"Structure\",\n \"forceRefresh\": true\n}\n```\n\n### AI Prediction Criteria (Claude)\n- **Weather Risk** \u2014 Rain days, wind, temperature extremes blocking site work\n- **Supplier Risk** \u2014 Lead time slippage, back-orders, sole-source dependencies\n- **Resource Risk** \u2014 Labour shortages, equipment breakdown, subcontractor delays\n- **Schedule Slack** \u2014 Float remaining vs. risk exposure\n- **Phase Complexity** \u2014 Current phase sensitivity to external delays\n- **Historical Patterns** \u2014 Similar project delay patterns\n\n### Features\n- Multi-source real-time risk ingestion\n- AI-powered delay probability scoring (0\u2013100%)\n- Automated severity routing and escalation\n- Mitigation playbook generation per risk type\n- Google Sheets / Airtable dashboard sync\n- Daily briefing email and Slack digest\n\n---\n\n**Explore More Automation:** \n[Contact us](https://www.oneclickitsolution.com/contact-us/) to design AI-powered lead nurturing, content engagement, and multi-platform reply workflows tailored to your growth strategy."
},
"typeVersion": 1
},
{
"id": "af8bcd5e-2496-4ddc-9645-7ca1d5abeb9e",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
966,
424
],
"parameters": {
"color": 4,
"width": 644,
"height": 472,
"content": "## 1. Trigger & Project Loading"
},
"typeVersion": 1
},
{
"id": "7932842b-f76f-4c5e-a839-a9ab4100ee56",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
272
],
"parameters": {
"color": 4,
"width": 664,
"height": 720,
"content": "## 2. Risk Data Enrichment\n### Weather \u00b7 Suppliers \u00b7 Resources"
},
"typeVersion": 1
},
{
"id": "b04ec2eb-835a-4a8a-8665-83e143918d59",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2368,
416
],
"parameters": {
"color": 4,
"width": 700,
"height": 592,
"content": "## 3. Claude AI Delay Prediction & Risk Scoring"
},
"typeVersion": 1
},
{
"id": "270a0b77-5814-4f13-8335-4ab6767dc0c0",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3136,
176
],
"parameters": {
"color": 4,
"width": 1380,
"height": 880,
"content": "## 4. Severity Routing \u00b7 Alerts \u00b7 Dashboard Update \u00b7 Ticketing"
},
"typeVersion": 1
},
{
"id": "7c543a52-b9b1-4e95-87a0-4d5e299a36a9",
"name": "Receive Project Alert Request",
"type": "n8n-nodes-base.webhook",
"position": [
1056,
528
],
"parameters": {
"path": "predict-construction-delay",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "90daf66a-4a5b-4acc-bce2-dcd45c21e6f1",
"name": "Morning Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1056,
720
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 6 * * 1-6"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "290c5e77-ec9c-4631-b0a9-1e9435f049b3",
"name": "Load Active Projects",
"type": "n8n-nodes-base.airtable",
"position": [
1280,
624
],
"parameters": {
"base": {
"__rl": true,
"mode": "id",
"value": "="
},
"table": {
"__rl": true,
"mode": "id",
"value": "="
},
"options": {},
"operation": "search",
"filterByFormula": "AND({Status} = 'Active', {Phase} != 'Completed')"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1,
"continueOnFail": true
},
{
"id": "f2036a94-800c-49c2-af2c-1e19e650594a",
"name": "Normalize Project Records",
"type": "n8n-nodes-base.code",
"position": [
1504,
624
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const webhookBody = $('Receive Project Alert Request')?.first()?.json?.body || {};\nconst record = $input.item.json.fields || $input.item.json;\n\n// Support both webhook single-project and Airtable batch modes\nconst project = {\n projectId: webhookBody.projectId || record['Project ID'] || `PROJ-${Date.now()}`,\n projectName: webhookBody.projectName || record['Project Name'] || 'Unnamed Project',\n siteLat: webhookBody.siteLocation?.lat || parseFloat(record['Site Lat']) || -33.8688,\n siteLon: webhookBody.siteLocation?.lon || parseFloat(record['Site Lon']) || 151.2093,\n plannedEndDate: webhookBody.plannedEndDate || record['Planned End Date'] || null,\n currentPhase: webhookBody.currentPhase || record['Current Phase'] || 'Unknown',\n scheduleFloatDays: parseInt(record['Schedule Float Days']) || 10,\n projectManagerEmail: record['Project Manager Email'] || 'user@example.com',\n contractor: record['Contractor'] || 'Unknown',\n budget: record['Budget'] || null,\n jobRunId: `RUN-${Date.now()}-${Math.random().toString(36).substr(2, 6).toUpperCase()}`,\n assessedAt: new Date().toISOString()\n};\n\n// Validate critical fields\nif (!project.siteLat || !project.siteLon) {\n throw new Error(`Project ${project.projectId} missing site coordinates`);\n}\n\nreturn { json: { project } };"
},
"typeVersion": 2
},
{
"id": "27c50738-a988-4f9e-9feb-d6ea709e6657",
"name": "Fetch 7-Day Site Weather",
"type": "n8n-nodes-base.httpRequest",
"position": [
1728,
432
],
"parameters": {
"url": "=https://api.openweathermap.org/data/3.0/onecall",
"options": {
"timeout": 10000
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "lat",
"value": "={{ $json.project.siteLat }}"
},
{
"name": "lon",
"value": "={{ $json.project.siteLon }}"
},
{
"name": "exclude",
"value": "current,minutely,hourly,alerts"
},
{
"name": "units",
"value": "metric"
},
{
"name": "appid",
"value": "YOUR_OPENWEATHERMAP_API_KEY"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "a15fc2dd-adaf-4991-a61a-8f988edb14f9",
"name": "Fetch Supplier Delivery Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
1728,
624
],
"parameters": {
"url": "=https://api.procore.com/rest/v1.0/projects/{{ $json.project.projectId }}/purchase_order_contracts",
"options": {
"timeout": 12000
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Procore-Company-Id",
"value": "YOUR_PROCORE_COMPANY_ID"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "f6d90c99-7cb5-4d06-aaaa-467cd06f743e",
"name": "Fetch Crew & Resource Availability",
"type": "n8n-nodes-base.airtable",
"position": [
1728,
816
],
"parameters": {
"base": {
"__rl": true,
"mode": "id",
"value": "="
},
"table": {
"__rl": true,
"mode": "id",
"value": "="
},
"options": {
"fields": [
"Resource Type",
"Assigned Count",
"Available Count",
"Status",
"Next Available Date",
"Notes"
]
},
"operation": "search",
"filterByFormula": "={Project ID} = '{{ $json.project.projectId }}'"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1,
"continueOnFail": true
},
{
"id": "4c881d9b-f371-48a8-9dc2-4289513a4966",
"name": "Merge Risk Data Streams",
"type": "n8n-nodes-base.merge",
"position": [
1952,
624
],
"parameters": {
"mode": "mergeByPosition"
},
"typeVersion": 3
},
{
"id": "d84aac7a-24d4-4213-9ea2-5f80456fece7",
"name": "Compile Project Risk Profile",
"type": "n8n-nodes-base.code",
"position": [
2176,
624
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const project = $('Normalize Project Records').item.json.project;\nconst weatherRaw = $('Fetch 7-Day Site Weather').item.json;\nconst supplierRaw = $('Fetch Supplier Delivery Status').item.json;\nconst resourceRaw = $('Fetch Crew & Resource Availability').item.json;\n\n// \u2500\u2500 WEATHER ANALYSIS \u2500\u2500\nlet weatherRisk = { rainDays: 0, highWindDays: 0, extremeHeatDays: 0, frostDays: 0, workableDays: 7, forecastSummary: 'Data unavailable', riskLevel: 'UNKNOWN' };\ntry {\n const daily = weatherRaw?.daily || [];\n if (daily.length > 0) {\n const rainDays = daily.filter(d => (d.rain || 0) > 5 || (d.pop || 0) > 0.6).length;\n const highWindDays = daily.filter(d => (d.wind_speed || 0) > 15).length;\n const extremeHeatDays = daily.filter(d => (d.temp?.max || 0) > 37).length;\n const frostDays = daily.filter(d => (d.temp?.min || 20) < 2).length;\n const blockedDays = new Set([\n ...daily.filter(d => (d.rain || 0) > 5 || (d.pop || 0) > 0.6).map((_, i) => i),\n ...daily.filter(d => (d.wind_speed || 0) > 15).map((_, i) => i)\n ]).size;\n const workableDays = Math.max(0, 7 - blockedDays);\n const descriptions = [...new Set(daily.slice(0,7).map(d => d.weather?.[0]?.description || ''))];\n weatherRisk = {\n rainDays, highWindDays, extremeHeatDays, frostDays, workableDays,\n forecastSummary: descriptions.join(', ') || 'Partly cloudy',\n riskLevel: rainDays >= 4 ? 'HIGH' : rainDays >= 2 || highWindDays >= 2 ? 'MEDIUM' : 'LOW'\n };\n } else {\n // Synthetic demo fallback\n const rainDays = Math.floor(Math.random() * 4);\n const highWindDays = Math.floor(Math.random() * 2);\n weatherRisk = {\n rainDays, highWindDays, extremeHeatDays: Math.floor(Math.random() * 2),\n frostDays: 0, workableDays: 7 - rainDays - highWindDays,\n forecastSummary: rainDays > 2 ? 'Heavy rain expected mid-week' : 'Mostly clear with isolated showers',\n riskLevel: rainDays >= 4 ? 'HIGH' : rainDays >= 2 ? 'MEDIUM' : 'LOW'\n };\n }\n} catch(e) { console.log('Weather parse error:', e.message); }\n\n// \u2500\u2500 SUPPLIER ANALYSIS \u2500\u2500\nlet supplierRisk = { openOrders: 0, delayedOrders: 0, criticalItems: [], avgLeadSlipDays: 0, riskLevel: 'UNKNOWN' };\ntry {\n const orders = Array.isArray(supplierRaw) ? supplierRaw\n : supplierRaw?.data || supplierRaw?.purchase_orders || [];\n if (orders.length > 0) {\n const delayed = orders.filter(o => o.status === 'delayed' || o.days_late > 0);\n const criticalItems = delayed.filter(o => o.is_critical || o.critical_path).map(o => o.description || o.title);\n const avgSlip = delayed.length > 0\n ? Math.round(delayed.reduce((sum, o) => sum + (o.days_late || 0), 0) / delayed.length)\n : 0;\n supplierRisk = {\n openOrders: orders.length, delayedOrders: delayed.length,\n criticalItems: criticalItems.slice(0, 5), avgLeadSlipDays: avgSlip,\n riskLevel: criticalItems.length > 0 ? 'HIGH' : delayed.length > 2 ? 'MEDIUM' : 'LOW'\n };\n } else {\n // Synthetic\n const delayedOrders = Math.floor(Math.random() * 4);\n const criticalItems = delayedOrders > 1 ? ['Structural Steel (Grade 350)', 'Concrete Pumps'] : [];\n supplierRisk = {\n openOrders: 8 + Math.floor(Math.random() * 10),\n delayedOrders, criticalItems,\n avgLeadSlipDays: delayedOrders > 0 ? Math.floor(Math.random() * 14) + 3 : 0,\n riskLevel: criticalItems.length > 0 ? 'HIGH' : delayedOrders > 1 ? 'MEDIUM' : 'LOW'\n };\n }\n} catch(e) { console.log('Supplier parse error:', e.message); }\n\n// \u2500\u2500 RESOURCE ANALYSIS \u2500\u2500\nlet resourceRisk = { totalRoles: 0, understaffedRoles: 0, equipmentIssues: 0, subcontractorRisks: 0, capacityPct: 100, riskLevel: 'UNKNOWN' };\ntry {\n const resources = Array.isArray(resourceRaw) ? resourceRaw\n : resourceRaw?.records?.map(r => r.fields) || [];\n if (resources.length > 0) {\n const understaffed = resources.filter(r =>\n parseInt(r['Available Count'] || r.available_count || 0) < parseInt(r['Assigned Count'] || r.assigned_count || 1)\n );\n const equipmentIssues = resources.filter(r => (r['Resource Type'] || '').toLowerCase().includes('equipment') && r['Status'] === 'Unavailable').length;\n const subRisks = resources.filter(r => (r['Resource Type'] || '').toLowerCase().includes('sub') && r['Status'] !== 'Confirmed').length;\n const totalCapacity = resources.reduce((s, r) => s + parseInt(r['Assigned Count'] || 1), 0);\n const availCapacity = resources.reduce((s, r) => s + parseInt(r['Available Count'] || 0), 0);\n const capacityPct = totalCapacity > 0 ? Math.round((availCapacity / totalCapacity) * 100) : 100;\n resourceRisk = {\n totalRoles: resources.length, understaffedRoles: understaffed.length,\n equipmentIssues, subcontractorRisks: subRisks, capacityPct,\n riskLevel: understaffed.length > 2 || equipmentIssues > 1 ? 'HIGH' : understaffed.length > 0 ? 'MEDIUM' : 'LOW'\n };\n } else {\n // Synthetic\n const capacityPct = 60 + Math.floor(Math.random() * 40);\n resourceRisk = {\n totalRoles: 6, understaffedRoles: capacityPct < 75 ? 2 : 0,\n equipmentIssues: Math.random() > 0.7 ? 1 : 0,\n subcontractorRisks: Math.random() > 0.6 ? 1 : 0,\n capacityPct,\n riskLevel: capacityPct < 70 ? 'HIGH' : capacityPct < 85 ? 'MEDIUM' : 'LOW'\n };\n }\n} catch(e) { console.log('Resource parse error:', e.message); }\n\n// \u2500\u2500 SCHEDULE CONTEXT \u2500\u2500\nconst today = new Date();\nconst endDate = project.plannedEndDate ? new Date(project.plannedEndDate) : null;\nconst daysRemaining = endDate ? Math.round((endDate - today) / (1000 * 60 * 60 * 24)) : null;\nconst floatRatio = daysRemaining && project.scheduleFloatDays\n ? parseFloat((project.scheduleFloatDays / daysRemaining * 100).toFixed(1)) : null;\n\nreturn {\n json: {\n project,\n riskProfile: {\n weather: weatherRisk,\n supplier: supplierRisk,\n resource: resourceRisk,\n schedule: {\n daysRemaining,\n floatDays: project.scheduleFloatDays,\n floatRatioPct: floatRatio,\n currentPhase: project.currentPhase,\n isNearDeadline: daysRemaining !== null && daysRemaining < 60\n },\n profileBuiltAt: new Date().toISOString()\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "bc7186c7-7ee3-4683-94e4-e580851403dd",
"name": "Predict Delays with Claude AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2400,
624
],
"parameters": {
"text": "=You are a senior construction project risk analyst (PMP, PMI-RMP certified) with 20+ years of experience on large commercial, civil and residential projects. Analyse the following project risk profile and predict delay probability with a full mitigation playbook.\n\n**Project Overview:**\n- Project ID: {{ $json.project.projectId }}\n- Name: {{ $json.project.projectName }}\n- Current Phase: {{ $json.project.currentPhase }}\n- Planned Completion: {{ $json.project.plannedEndDate || 'Not specified' }}\n- Days Remaining: {{ $json.riskProfile.schedule.daysRemaining ?? 'N/A' }}\n- Schedule Float: {{ $json.project.scheduleFloatDays }} days\n- Float Ratio: {{ $json.riskProfile.schedule.floatRatioPct }}% of remaining duration\n- Near Deadline Warning: {{ $json.riskProfile.schedule.isNearDeadline }}\n\n**Weather Risk (Next 7 Days):**\n- Rain Days (>5mm or >60% PoP): {{ $json.riskProfile.weather.rainDays }}\n- High Wind Days (>15 m/s): {{ $json.riskProfile.weather.highWindDays }}\n- Extreme Heat Days (>37\u00b0C): {{ $json.riskProfile.weather.extremeHeatDays }}\n- Frost Days (<2\u00b0C): {{ $json.riskProfile.weather.frostDays }}\n- Workable Days This Week: {{ $json.riskProfile.weather.workableDays }}/7\n- Forecast Summary: {{ $json.riskProfile.weather.forecastSummary }}\n- Weather Risk Level: {{ $json.riskProfile.weather.riskLevel }}\n\n**Supplier & Procurement Risk:**\n- Open Purchase Orders: {{ $json.riskProfile.supplier.openOrders }}\n- Delayed Orders: {{ $json.riskProfile.supplier.delayedOrders }}\n- Critical Path Items Delayed: {{ JSON.stringify($json.riskProfile.supplier.criticalItems) }}\n- Average Lead Time Slip: {{ $json.riskProfile.supplier.avgLeadSlipDays }} days\n- Supplier Risk Level: {{ $json.riskProfile.supplier.riskLevel }}\n\n**Crew & Resource Availability:**\n- Total Resource Roles: {{ $json.riskProfile.resource.totalRoles }}\n- Understaffed Roles: {{ $json.riskProfile.resource.understaffedRoles }}\n- Equipment Issues: {{ $json.riskProfile.resource.equipmentIssues }}\n- Subcontractor Risks: {{ $json.riskProfile.resource.subcontractorRisks }}\n- Current Capacity: {{ $json.riskProfile.resource.capacityPct }}%\n- Resource Risk Level: {{ $json.riskProfile.resource.riskLevel }}\n\n**Severity Classification:**\n- CRITICAL (80\u2013100%): Immediate delay certain without intervention \u2014 stop-work risk, major supply failure or unsafe conditions\n- HIGH (60\u201379%): Significant delay likely \u2014 multiple risk factors converging, float nearly exhausted\n- MEDIUM (35\u201359%): Delay possible \u2014 one or two risk factors elevated, float provides buffer\n- LOW (0\u201334%): Delay unlikely \u2014 minor risks within normal tolerance\n\n**Response Format (JSON only, no markdown):**\n{\n \"delayProbabilityPct\": 72,\n \"severity\": \"CRITICAL | HIGH | MEDIUM | LOW\",\n \"confidenceLevel\": \"HIGH | MEDIUM | LOW\",\n \"estimatedDelayDays\": 14,\n \"delayDaysRange\": \"10\u201321 days\",\n \"revisedCompletionDate\": \"2025-12-01\",\n \"primaryDelayDrivers\": [\"ordered list of top delay causes\"],\n \"riskBreakdown\": {\n \"weatherContribution\": 30,\n \"supplierContribution\": 45,\n \"resourceContribution\": 25\n },\n \"criticalPathImpact\": \"brief description of which critical path activities are threatened\",\n \"mitigationActions\": [\n { \"action\": \"specific action\", \"owner\": \"role\", \"deadline\": \"timeframe\", \"impactIfDone\": \"reduces delay by X days\" }\n ],\n \"contingencyOptions\": [\"alternative approach 1\", \"alternative approach 2\"],\n \"escalateToManagement\": true,\n \"safetyFlags\": [\"any safety concerns from weather or resource gaps\"],\n \"budgetImpactEstimate\": \"$X\u2013$Y additional cost range\",\n \"recommendedActions\": [\"top 3 immediate actions for PM\"],\n \"weeklyForecastImpact\": \"plain-English summary of what to expect this week\",\n \"executiveSummary\": \"2-3 sentence summary for senior stakeholders\"\n}",
"options": {
"systemMessage": "You are a construction delay risk expert. Return JSON only \u2014 no markdown, no code blocks, no extra text. All numeric fields must be numbers not strings. Be precise and actionable. Prioritise safety and legal (liquidated damages) implications in your recommendations."
},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "914d1aa2-65c7-49a3-bf8f-6fb317850c24",
"name": "Claude AI Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
2472,
848
],
"parameters": {
"model": "=claude-sonnet-4-20250514",
"options": {
"temperature": 0.1
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "0c429c77-338b-40f0-94e9-ef97ce3ab259",
"name": "Parse AI Delay Prediction",
"type": "n8n-nodes-base.code",
"position": [
2752,
624
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const aiResponse = $input.item.json;\nlet aiText = aiResponse.response || aiResponse.output || aiResponse.text || '';\n\nif (aiResponse.content && Array.isArray(aiResponse.content)) {\n aiText = aiResponse.content[0]?.text || '';\n}\n\nconst cleanText = aiText\n .replace(/```json\\s*/g, '')\n .replace(/```\\s*/g, '')\n .trim();\n\nlet prediction;\ntry {\n prediction = JSON.parse(cleanText);\n} catch (err) {\n const match = cleanText.match(/\\{[\\s\\S]*\\}/);\n if (match) { prediction = JSON.parse(match[0]); }\n else { throw new Error(`AI parse failed: ${err.message}. Raw: ${cleanText.substring(0, 300)}`); }\n}\n\nconst upstream = $('Compile Project Risk Profile').item.json;\n\n// Determine auto-alert flag\nconst autoAlert = ['CRITICAL', 'HIGH'].includes(prediction.severity);\nconst priorityLabel = { 'CRITICAL': 'P1_STOP_WORK', 'HIGH': 'P2_URGENT', 'MEDIUM': 'P3_MONITOR', 'LOW': 'P4_WATCH' }[prediction.severity] || 'P3_MONITOR';\nconst jiraPriority = { 'CRITICAL': 'Highest', 'HIGH': 'High', 'MEDIUM': 'Medium', 'LOW': 'Low' }[prediction.severity] || 'Medium';\n\nreturn {\n json: {\n project: upstream.project,\n riskProfile: upstream.riskProfile,\n prediction,\n responseDecision: {\n autoAlert,\n priorityLabel,\n jiraPriority,\n createTicket: ['CRITICAL', 'HIGH'].includes(prediction.severity),\n requiresEscalation: prediction.escalateToManagement || prediction.severity === 'CRITICAL',\n slackChannel: prediction.severity === 'CRITICAL' ? '#construction-critical' : prediction.severity === 'HIGH' ? '#construction-alerts' : '#construction-monitoring'\n },\n evaluatedAt: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "63f013ea-e7d5-4e74-ba3e-37bc90556bd7",
"name": "Route by Delay Severity",
"type": "n8n-nodes-base.switch",
"position": [
2976,
592
],
"parameters": {
"mode": "expression",
"output": "={{ $json.prediction.severity }}"
},
"typeVersion": 3.1
},
{
"id": "34509e73-e94c-4767-8ad6-fc08cc547c54",
"name": "Send Delay Alert to Slack",
"type": "n8n-nodes-base.httpRequest",
"position": [
3200,
480
],
"parameters": {
"url": "https://slack.com/api/chat.postMessage",
"method": "POST",
"options": {
"timeout": 10000
},
"jsonBody": "={\n \"channel\": \"{{ $json.responseDecision.slackChannel }}\",\n \"text\": \"\u26a0\ufe0f *{{ $json.prediction.severity }} Delay Risk: {{ $json.project.projectName }}*\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"\ud83c\udfd7\ufe0f {{ $json.prediction.severity }} Delay Risk \u2014 {{ $json.project.projectName }}\" }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n { \"type\": \"mrkdwn\", \"text\": \"*Project ID:*\\n{{ $json.project.projectId }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Phase:*\\n{{ $json.project.currentPhase }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Delay Probability:*\\n{{ $json.prediction.delayProbabilityPct }}%\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Est. Delay:*\\n{{ $json.prediction.delayDaysRange }} days\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Revised End Date:*\\n{{ $json.prediction.revisedCompletionDate }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Budget Impact:*\\n{{ $json.prediction.budgetImpactEstimate }}\" }\n ]\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Executive Summary:*\\n{{ $json.prediction.executiveSummary }}\" }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Top Drivers:*\\n{{ $json.prediction.primaryDelayDrivers.slice(0,3).map((d,i) => `${i+1}. ${d}`).join('\\\\n') }}\" }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Immediate Actions:*\\n{{ $json.prediction.recommendedActions.map((a,i) => `${i+1}. ${a}`).join('\\\\n') }}\" }\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "slackApi"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "44b3d6e9-e62c-4287-8b1a-1d46270d64db",
"name": "Open Risk Ticket in Jira",
"type": "n8n-nodes-base.httpRequest",
"position": [
3424,
480
],
"parameters": {
"url": "https://YOUR_JIRA_DOMAIN.atlassian.net/rest/api/3/issue",
"method": "POST",
"options": {
"timeout": 15000
},
"jsonBody": "={\n \"fields\": {\n \"project\": { \"key\": \"CONST\" },\n \"issuetype\": { \"name\": \"Risk\" },\n \"summary\": \"[{{ $json.prediction.severity }}] Delay Risk {{ $json.prediction.delayProbabilityPct }}% \u2014 {{ $json.project.projectName }}\",\n \"priority\": { \"name\": \"{{ $json.responseDecision.jiraPriority }}\" },\n \"description\": {\n \"type\": \"doc\", \"version\": 1,\n \"content\": [\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Project: {{ $json.project.projectName }} ({{ $json.project.projectId }})\" }] },\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Delay Probability: {{ $json.prediction.delayProbabilityPct }}% | Est. Delay: {{ $json.prediction.delayDaysRange }} days\" }] },\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Revised Completion: {{ $json.prediction.revisedCompletionDate }}\" }] },\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Budget Impact: {{ $json.prediction.budgetImpactEstimate }}\" }] },\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Critical Path: {{ $json.prediction.criticalPathImpact }}\" }] },\n { \"type\": \"paragraph\", \"content\": [{ \"type\": \"text\", \"text\": \"Summary: {{ $json.prediction.executiveSummary }}\" }] }\n ]\n },\n \"labels\": [\"construction-delay\", \"{{ $json.prediction.severity.toLowerCase() }}-risk\", \"{{ $json.project.currentPhase.toLowerCase().replace(/ /g, '-') }}\"]\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "jiraSoftwareCloudApi"
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "77ef4aab-20c9-4c29-b892-1a4462c0c51e",
"name": "Update Project Risk Dashboard",
"type": "n8n-nodes-base.googleSheets",
"position": [
3648,
552
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "=YOUR_SHEET_TAB_ID"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=YOUR_GOOGLE_SHEET_ID"
},
"authentication": "serviceAccount"
},
"credentials": {
"googleApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.5,
"continueOnFail": true
},
{
"id": "af5d1e93-d10a-4fcd-933b-855480007d6d",
"name": "Build Daily Risk Briefing",
"type": "n8n-nodes-base.code",
"position": [
3872,
552
],
"parameters": {
"jsCode": "const items = $input.all();\nconst sortedProjects = items\n .map(i => i.json)\n .sort((a, b) => (b.prediction?.delayProbabilityPct || 0) - (a.prediction?.delayProbabilityPct || 0));\n\nconst critical = sortedProjects.filter(p => p.prediction?.severity === 'CRITICAL').length;\nconst high = sortedProjects.filter(p => p.prediction?.severity === 'HIGH').length;\nconst medium = sortedProjects.filter(p => p.prediction?.severity === 'MEDIUM').length;\nconst low = sortedProjects.filter(p => p.prediction?.severity === 'LOW').length;\n\nconst today = new Date().toLocaleDateString('en-AU', { weekday:'long', year:'numeric', month:'long', day:'numeric' });\n\nlet htmlBody = `\n<html><body style=\"font-family:Arial,sans-serif;max-width:700px;margin:0 auto;padding:20px;\">\n<h1 style=\"color:#1a1a2e;border-bottom:3px solid #e94560;padding-bottom:10px;\">\ud83c\udfd7\ufe0f Construction Delay Risk Briefing</h1>\n<p style=\"color:#555;\">${today}</p>\n\n<div style=\"display:flex;gap:12px;margin:20px 0;\">\n <div style=\"background:#e94560;color:white;padding:15px 25px;border-radius:8px;text-align:center;\"><strong style=\"font-size:2em;\">${critical}</strong><br/>CRITICAL</div>\n <div style=\"background:#ff6b35;color:white;padding:15px 25px;border-radius:8px;text-align:center;\"><strong style=\"font-size:2em;\">${high}</strong><br/>HIGH</div>\n <div style=\"background:#ffa500;color:white;padding:15px 25px;border-radius:8px;text-align:center;\"><strong style=\"font-size:2em;\">${medium}</strong><br/>MEDIUM</div>\n <div style=\"background:#28a745;color:white;padding:15px 25px;border-radius:8px;text-align:center;\"><strong style=\"font-size:2em;\">${low}</strong><br/>LOW</div>\n</div>\n\n<h2 style=\"color:#1a1a2e;\">Project Risk Summary</h2>\n`;\n\nfor (const p of sortedProjects.slice(0, 10)) {\n const pred = p.prediction || {};\n const sevColor = { CRITICAL:'#e94560', HIGH:'#ff6b35', MEDIUM:'#ffa500', LOW:'#28a745' }[pred.severity] || '#999';\n htmlBody += `\n<div style=\"border:1px solid #ddd;border-left:5px solid ${sevColor};border-radius:6px;padding:15px;margin:12px 0;\">\n <h3 style=\"margin:0 0 8px;color:#1a1a2e;\">${p.project?.projectName || 'Unknown'} <span style=\"background:${sevColor};color:white;padding:2px 8px;border-radius:4px;font-size:0.75em;\">${pred.severity}</span></h3>\n <p style=\"margin:4px 0;color:#555;\"><strong>Phase:</strong> ${p.project?.currentPhase} | <strong>Delay Probability:</strong> ${pred.delayProbabilityPct}% | <strong>Est. Delay:</strong> ${pred.delayDaysRange} days</p>\n <p style=\"margin:4px 0;color:#555;\"><strong>Revised End Date:</strong> ${pred.revisedCompletionDate} | <strong>Budget Impact:</strong> ${pred.budgetImpactEstimate}</p>\n <p style=\"margin:8px 0 4px;color:#333;\">${pred.executiveSummary}</p>\n ${pred.safetyFlags?.length > 0 ? `<p style=\"color:#e94560;margin:4px 0;\">\u26a0\ufe0f Safety: ${pred.safetyFlags.join(', ')}</p>` : ''}\n <p style=\"margin:8px 0 0;color:#1a1a2e;\"><strong>Top Action:</strong> ${pred.recommendedActions?.[0] || 'See full report'}</p>\n</div>`;\n}\n\nhtmlBody += `\n<hr style=\"margin:30px 0;border:1px solid #eee;\"/>\n<p style=\"color:#999;font-size:0.85em;\">Generated by AI Construction Delay Predictor • ${new Date().toISOString()}</p>\n</body></html>`;\n\nreturn [{\n json: {\n reportDate: today,\n totalProjects: sortedProjects.length,\n summary: { critical, high, medium, low },\n topRiskProjects: sortedProjects.slice(0,5).map(p => ({\n name: p.project?.projectName,\n delayPct: p.prediction?.delayProbabilityPct,\n severity: p.prediction?.severity,\n estimatedDelay: p.prediction?.delayDaysRange\n })),\n htmlBody,\n generatedAt: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "025fb9f1-a762-4558-88a7-cf3e3e3622be",
"name": "Send Daily Email Briefing",
"type": "n8n-nodes-base.emailSend",
"position": [
4096,
552
],
"parameters": {
"html": "={{ $json.htmlBody }}",
"options": {
"appendAttribution": false
},
"subject": "=\ud83c\udfd7\ufe0f Construction Delay Risk Briefing \u2014 {{ $json.reportDate }} ({{ $json.summary.critical }} Critical, {{ $json.summary.high }} High)",
"toEmail": "user@example.com",
"fromEmail": "="
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1,
"continueOnFail": true
},
{
"id": "26bb13c8-2f8f-48ff-b2ed-235815f9dc52",
"name": "Return Prediction to Caller",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4320,
552
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json, null, 2) }}"
},
"typeVersion": 1
},
{
"id": "0d831847-968f-4550-a564-3b80ae5e9859",
"name": "Log Low Risk \u2014 No Escalation",
"type": "n8n-nodes-base.set",
"position": [
3200,
768
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "low-msg",
"name": "status",
"type": "string",
"value": "LOW_RISK_LOGGED"
},
{
"id": "low-proj",
"name": "projectName",
"type": "string",
"value": "={{ $json.project.projectName }}"
},
{
"id": "low-pct",
"name": "delayProbabilityPct",
"type": "number",
"value": "={{ $json.prediction.delayProbabilityPct }}"
},
{
"id": "low-ts",
"name": "loggedAt",
"type": "string",
"value": "={{ new Date().toISOString() }}"
}
]
}
},
"typeVersion": 3.4
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d161e63a-b56a-4eac-a485-3534b4357817",
"connections": {
"Claude AI Model": {
"ai_languageModel": [
[
{
"node": "Predict Delays with Claude AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Load Active Projects": {
"main": [
[
{
"node": "Normalize Project Records",
"type": "main",
"index": 0
}
]
]
},
"Merge Risk Data Streams": {
"main": [
[
{
"node": "Compile Project Risk Profile",
"type": "main",
"index": 0
}
]
]
},
"Route by Delay Severity": {
"main": [
[
{
"node": "Send Delay Alert to Slack",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Delay Alert to Slack",
"type": "main",
"index": 0
}
],
[],
[
{
"node": "Log Low Risk \u2014 No Escalation",
"type": "main",
"index": 0
}
]
]
},
"Fetch 7-Day Site Weather": {
"main": [
[
{
"node": "Merge Risk Data Streams",
"type": "main",
"index": 0
}
]
]
},
"Morning Schedule Trigger": {
"main": [
[
{
"node": "Load Active Projects",
"type": "main",
"index": 0
}
]
]
},
"Open Risk Ticket in Jira": {
"main": [
[
{
"node": "Update Project Risk Dashboard",
"type": "main",
"index": 0
}
]
]
},
"Build Daily Risk Briefing": {
"main": [
[
{
"node": "Send Daily Email Briefing",
"type": "main",
"index": 0
}
]
]
},
"Normalize Project Records": {
"main": [
[
{
"node": "Fetch 7-Day Site Weather",
"type": "main",
"index": 0
},
{
"node": "Fetch Supplier Delivery Status",
"type": "main",
"index": 0
},
{
"node": "Fetch Crew & Resource Availability",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Delay Prediction": {
"main": [
[
{
"node": "Route by Delay Severity",
"type": "main",
"index": 0
}
]
]
},
"Send Daily Email Briefing": {
"main": [
[
{
"node": "Return Prediction to Caller",
"type": "main",
"index": 0
}
]
]
},
"Send Delay Alert to Slack": {
"main": [
[
{
"node": "Open Risk Ticket in Jira",
"type": "main",
"index": 0
}
]
]
},
"Compile Project Risk Profile": {
"main": [
[
{
"node": "Predict Delays with Claude AI",
"type": "main",
"index": 0
}
]
]
},
"Predict Delays with Claude AI": {
"main": [
[
{
"node": "Parse AI Delay Prediction",
"type": "main",
"index": 0
}
]
]
},
"Receive Project Alert Request": {
"main": [
[
{
"node": "Load Active Projects",
"type": "main",
"index": 0
}
]
]
},
"Update Project Risk Dashboard": {
"main": [
[
{
"node": "Build Daily Risk Briefing",
"type": "main",
"index": 0
}
]
]
},
"Fetch Supplier Delivery Status": {
"main": [
[
{
"node": "Merge Risk Data Streams",
"type": "main",
"index": 1
}
]
]
},
"Fetch Crew & Resource Availability": {
"main": [
[
{
"node": "Merge Risk Data Streams",
"type": "main",
"index": 1
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
airtableTokenApianthropicApigoogleApijiraSoftwareCloudApislackApismtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow monitors active construction projects in real time, ingests weather forecasts, supplier delivery statuses, and crew/resource availability, then uses Claude AI to predict delay risk, estimate schedule impact, and generate mitigation playbooks for project managers.…
Source: https://n8n.io/workflows/13687/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow continuously monitors CVE databases, threat intelligence feeds, and public security advisories to surface emerging zero-day threats, correlates them against your registered infrastructur
This workflow ingests student profiles from a form submission or CRM, loads the active scholarship catalogue, uses Claude AI to score each student's eligibility against every available scholarship, fi
Automatically transforms your travel photos and notes into beautiful journals, highlight reels, and review drafts using Claude's vision and language capabilities. Trip Completion Trigger - Webhook or
This workflow provides real-time detection of ransomware encryption patterns using Claude AI, with automated system isolation and incident response. File System Monitoring - Continuously monitors file
This workflow provides personalized travel destination recommendations by analyzing past trip history, user preferences, travel behavior patterns, and current trends. It uses Claude AI to generate int