About n8n-photo-report
n8n Photo Report Automation is an n8n workflow for teams that organize construction-site photos and produce reports. It links Claude Vision analysis with categorization, scheduled reporting, and email distribution.
Key features
- 01Receive photos through a webhook
- 02Analyze images with Claude Vision
- 03Categorize construction activities
- 04Generate scheduled photo reports
- 05Send reports by email
Use cases
- 01Compile daily site photos
- 02Identify safety concerns in photos
- 03File images by construction activity
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ datadrivenconstruction/ ddc_skills_for_ai_agents_in_construction --skill n8n-photo-reportAfter installing, type/n8n-photo-reportin Claude Code to confirm it loaded.
Bundled filesclaw.jsoninstructions.md
Details
From the project READMEn8n Photo Report Automation
Business Case
Site photos require organization, analysis, and reporting. This workflow automates photo collection, AI analysis, and report generation.
Workflow Overview
[Photo Upload] → [AI Analysis] → [Categorization] → [Report Generation] → [Distribution]
n8n Workflow Configuration
1. Photo Input Triggers
{
"nodes": [
{
"name": "Photo Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"httpMethod": "POST",
"path": "photo-upload",
"options": {
"binaryData": true
}
}
},
{
"name": "Watch Dropbox Folder",
"type": "n8n-nodes-base.dropbox",
"parameters": {
"operation": "listFolder",
"path": "/SitePhotos/{{$today}}"
}
}
]
}
2. AI Image Analysis
{
"name": "Analyze with Claude Vision",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"headers": {
"x-api-key": "={{$env.ANTHROPIC_API_KEY}}",
"anthropic-version": "2023-06-01"
},
"body": {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "={{$binary.data.toString('base64')}}"
}
},
{
"type": "text",
"text": "Analyze this construction site photo. Identify: 1) Work activity visible, 2) Approximate completion status, 3) Any safety concerns, 4) Weather conditions. Return JSON format."
}
]
}]
}
}
}
3. Categorize and Store
{
"nodes": [
{
"name": "Parse AI Response",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const response = JSON.parse($json.content[0].text);\n\nreturn [{\n json: {\n filename: $('Photo Webhook').first().json.filename,\n timestamp: new Date().toISOString(),\n activity: response.work_activity,\n completion: response.completion_status,\n safety_issues: response.safety_concerns,\n weather: response.weather,\n category: response.work_activity.includes('concrete') ? 'CONCRETE' :\n response.work_activity.includes('steel') ? 'STEEL' :\n response.work_activity.includes('mep') ? 'MEP' : 'GENERAL'\n }\n}];"
}
},
{
"name": "Store in Airtable",
"type": "n8n-nodes-base.airtable",
"parameters": {
"operation": "create",
"table": "Site Photos",
"fields": {
"Filename": "={{$json.filename}}",
"Date": "={{$json.timestamp}}",
"Activity": "={{$json.activity}}",
"Category": "={{$json.category}}",
"Completion": "={{$json.completion}}",
"Safety Issues": "={{$json.safety_issues}}"
}
}
}
]
}
4. Generate Photo Report
{
"nodes": [
{
"name": "Schedule Report",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {"interval": [{"field": "cronExpression", "expression": "0 18 * * 1-5"}]}
}
},
{
"name": "Get Today Photos",
"type": "n8n-nodes-base.airtable",
"parameters": {
"operation": "list",
"table": "Site Photos",
"filterByFormula": "IS_SAME({Date}, TODAY(), 'day')"
}
},
{
"name": "Generate Report",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const photos = $input.all();\n\nconst byCategory = {};\nlet safetyIssues = [];\n\nphotos.forEach(p => {\n const cat = p.json.fields.Category;\n if (!byCategory[cat]) byCategory[cat] = [];\n byCategory[cat].push(p.json.fields);\n \n if (p.json.fields['Safety Issues'] && p.json.fields['Safety Issues'] !== 'None') {\n safetyIssues.push({\n photo: p.json.fields.Filename,\n issue: p.json.fields['Safety Issues']\n });\n }\n});\n\nreturn [{\n json: {\n date: new Date().toISOString().split('T')[0],\n total_photos: photos.length,\n by_category: byCategory,\n safety_issues: safetyIssues,\n safety_count: safetyIssues.length\n }\n}];"
}
}
]
}
5. Distribution
{
"name": "Send Report Email",
"type": "n8n-nodes-base.emailSend",
"parameters": {
"toEmail": "={{$env.PHOTO_REPORT_RECIPIENTS}}",
"subject": "Site Photo Report - {{$json.date}} ({{$json.total_photos}} photos)",
"html": "<h2>Daily Photo Report</h2><p>Total Photos: {{$json.total_photos}}</p><h3>Safety Issues: {{$json.safety_count}}</h3>{{#if $json.safety_issues.length}}<ul>{{#each $json.safety_issues}}<li>{{photo}}: {{issue}}</li>{{/each}}</ul>{{/if}}"
}
}
Python Helper
import requests
import base64
def upload_photo_to_workflow(image_path: str, webhook_url: str, metadata: dict):
"""Upload photo to n8n workflow."""
with open(image_path, 'rb') as f:
image_data = base64.b64encode(f.read()).decode()
payload = {
'filename': image_path.split('/')[-1],
'image_data': image_data,
'project_id': metadata.get('project_id'),
'location': metadata.get('location'),
'captured_by': metadata.get('captured_by')
}
response = requests.post(webhook_url, json=payload)
return response.json()
def batch_upload_photos(photo_paths: list, webhook_url: str, project_id: str):
"""Batch upload multiple photos."""
results = []
for path in photo_paths:
result = upload_photo_to_workflow(path, webhook_url, {'project_id': project_id})
results.append(result)
return results
Quick Start
- Import workflow to n8n
- Configure API keys:
ANTHROPIC_API_KEYfor Claude Vision- Airtable credentials
- Email configuration
- Create Airtable base with "Site Photos" table
- Test with sample photo upload
Resources
- n8n Documentation: https://docs.n8n.io
- Claude Vision API: https://docs.anthropic.com
- DDC Book: Chapter 4.2 - Workflow Automation
Comments
Markdown supported