{
  "id": "VODrQQcFjW901hiH",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automated Property Document Validator",
  "tags": [],
  "nodes": [
    {
      "id": "a65a3d4f-cc2b-487c-99f4-dd01491ac8d5",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -336
      ],
      "parameters": {
        "width": 960,
        "height": 1932,
        "content": "## Automated Property Document Validator\n\nThis workflow ingests property document packages submitted via webhook or monitored cloud storage, extracts text from each file, runs Claude AI to verify legal compliance, detect missing or expired documents, flag invalid clauses, and produces a structured validation report with remediation guidance.\n\n### How it works\n\n1. **Trigger** \u2014 Webhook submission or Google Drive folder watch\n2. **Intake & Register** \u2014 Logs submission, assigns case ID, normalises metadata\n3. **Download Documents** \u2014 Fetches each file from Drive / S3 / URL\n4. **Extract Text** \u2014 Reads PDF/DOCX content via parser node\n5. **Classify Document Type** \u2014 Identifies contract, title, disclosure, certificate, etc.\n6. **AI Legal Compliance Check** \u2014 Claude AI validates each document against jurisdiction rules\n7. **Aggregate Validation Results** \u2014 Merges per-document findings into a case report\n8. **Check Required Doc Checklist** \u2014 Detects missing mandatory documents\n9. **Route by Compliance Status** \u2014 Branches on PASS / FAIL / REQUIRES_REVIEW\n10. **Notify Submitter** \u2014 Email with full validation report and remediation steps\n11. **Alert Legal Team on Slack** \u2014 Flags FAIL or critical issues to legal channel\n12. **Create Audit Record** \u2014 Writes full report to Google Sheets compliance log\n13. **Generate PDF Report** \u2014 Stores formatted report back to Drive\n14. **Return Validation Response** \u2014 Sends structured JSON result to caller\n\n### Setup Steps\n\n1. Import workflow into n8n\n2. Configure credentials:\n   - **Anthropic API** \u2014 Claude AI for legal document analysis\n   - **Google Drive OAuth** \u2014 Document intake and report storage\n   - **Google Sheets OAuth** \u2014 Compliance audit log\n   - **Slack OAuth** \u2014 Legal team alerts\n   - **SendGrid / SMTP** \u2014 Submitter notification emails\n3. Set your Google Drive folder IDs for intake and output\n4. Configure jurisdiction rules in the AI prompt node\n5. Set mandatory document checklist in the Checklist node\n6. Activate the workflow\n\n### Sample Webhook Payload\n```json\n{\n  \"caseId\": \"CASE-2025-0871\",\n  \"submitterEmail\": \"agent@realty.com\",\n  \"propertyAddress\": \"42 Oak Street, Sydney NSW 2000\",\n  \"transactionType\": \"sale\",\n  \"jurisdiction\": \"NSW\",\n  \"documents\": [\n    {\n      \"name\": \"Contract of Sale\",\n      \"type\": \"contract\",\n      \"driveFileId\": \"1aBcDeFgHiJkL\"\n    },\n    {\n      \"name\": \"Title Search\",\n      \"type\": \"title\",\n      \"driveFileId\": \"2mNoPqRsTuVwX\"\n    }\n  ]\n}\n```\n\n### Document Types Supported\n- Contract of Sale / Purchase Agreement\n- Certificate of Title / Title Search\n- Vendor Disclosure Statement\n- Section 32 / Vendor Statement\n- Building & Pest Inspection Report\n- Strata Report / Body Corporate Docs\n- Zoning Certificate / Planning Certificate\n- Land Tax Certificate\n- Mortgage / Discharge of Mortgage\n- Lease Agreement / Tenancy Documents\n- Council Rates Notice\n- Smoke Alarm / Safety Certificates\n\n### Features\n- Multi-document batch validation per submission\n- Jurisdiction-aware compliance rules (AU/UK/US configurable)\n- Missing document detection against mandatory checklist\n- Expiry date validation for time-sensitive certificates\n- Critical clause and red-flag detection\n- Automated remediation guidance per issue\n- Full audit trail in Google Sheets\n- PDF validation report stored to Drive\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": "5d335b19-7bda-4f94-b31e-b1fc5d097e63",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        512
      ],
      "parameters": {
        "color": 4,
        "width": 456,
        "height": 524,
        "content": "## 1. Intake, Registration & File Retrieval"
      },
      "typeVersion": 1
    },
    {
      "id": "4eeefba1-7996-4235-8ead-bd68df044e62",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1536,
        336
      ],
      "parameters": {
        "color": 4,
        "width": 572,
        "height": 740,
        "content": "## 2. Document Extraction & Type Classification\n### PDF Text \u00b7 DOCX Parse \u00b7 Document Identification"
      },
      "typeVersion": 1
    },
    {
      "id": "0d49b4ed-2361-46fa-8a48-39d3cbd302dc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        556
      ],
      "parameters": {
        "color": 4,
        "width": 800,
        "height": 564,
        "content": "## 3. Claude AI Legal Compliance Analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "4bef9d01-0ae6-41e4-8b81-71dd13e4acfa",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2992,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 920,
        "height": 920,
        "content": "## 4. Aggregation \u00b7 Compliance Routing \u00b7 Notifications \u00b7 Audit Log"
      },
      "typeVersion": 1
    },
    {
      "id": "030a3b75-e7e5-44d8-9088-cd3b9ddba482",
      "name": "Receive Document Submission",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1104,
        640
      ],
      "parameters": {
        "path": "validate-property-documents",
        "options": {
          "allowedOrigins": "*"
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "0d750afe-8f0a-42fc-ade8-f4d5805d5950",
      "name": "Watch Drive Intake Folder",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        1104,
        832
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_INTAKE_FOLDER_ID"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "82566bc9-c9e8-472b-9f65-17b516197af0",
      "name": "Register Validation Case",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        736
      ],
      "parameters": {
        "jsCode": "// Support both webhook payload and Drive trigger\nconst webhookData = $input.first()?.json?.body || $input.first()?.json || {};\n\n// Build case record\nconst caseId = webhookData.caseId\n  || `CASE-${new Date().getFullYear()}-${Math.random().toString(36).substr(2,6).toUpperCase()}`;\n\nconst documents = webhookData.documents || [];\n\n// If triggered from Drive (no documents array), build a single-doc list\nconst finalDocs = documents.length > 0 ? documents : [{\n  name: webhookData.name || 'Unknown Document',\n  type: 'unknown',\n  driveFileId: webhookData.id || webhookData.driveFileId || null,\n  url: webhookData.webViewLink || null\n}];\n\nconst caseRecord = {\n  caseId,\n  submitterEmail: webhookData.submitterEmail || webhookData.owners?.[0]?.emailAddress || 'user@example.com',\n  propertyAddress: webhookData.propertyAddress || webhookData.property_address || 'Address not provided',\n  transactionType: (webhookData.transactionType || 'sale').toUpperCase(),\n  jurisdiction: (webhookData.jurisdiction || 'NSW').toUpperCase(),\n  submittedAt: new Date().toISOString(),\n  totalDocuments: finalDocs.length,\n  runId: `RUN-${Date.now()}`\n};\n\n// Emit one item per document so next nodes process in parallel\nreturn finalDocs.map(doc => ({\n  json: {\n    case: caseRecord,\n    document: {\n      docId: `DOC-${Math.random().toString(36).substr(2,8).toUpperCase()}`,\n      name: doc.name || 'Unnamed',\n      declaredType: (doc.type || 'unknown').toLowerCase(),\n      driveFileId: doc.driveFileId || doc.id || null,\n      url: doc.url || null,\n      fileExtension: (doc.name || '').split('.').pop()?.toLowerCase() || 'pdf'\n    }\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "0f0e001b-193b-4e2d-9667-3269faf3e0de",
      "name": "Download Document from Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1552,
        736
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.document.driveFileId }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "continueOnFail": true
    },
    {
      "id": "572371fd-4ec1-4c9b-a0d6-2bd6e8e43891",
      "name": "Extract Document Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1776,
        736
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "59071362-d097-4c23-841f-1d7abfebb135",
      "name": "Classify & Prepare Document",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        736
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const docMeta = $('Register Validation Case').item.json;\nconst rawText = $('Extract Document Text').item?.json?.text\n  || $('Download Document from Drive').item?.json?.error\n  || 'TEXT_EXTRACTION_FAILED';\n\n// Simple keyword-based classifier as fallback to declared type\nconst text = (rawText || '').toLowerCase().substring(0, 3000);\nconst detectedType = (() => {\n  if (text.includes('contract of sale') || text.includes('agreement for sale') || text.includes('purchase price')) return 'CONTRACT_OF_SALE';\n  if (text.includes('certificate of title') || text.includes('folio identifier') || text.includes('torrens title')) return 'CERTIFICATE_OF_TITLE';\n  if (text.includes('section 32') || text.includes('vendor statement') || text.includes('vendor disclosure')) return 'VENDOR_DISCLOSURE';\n  if (text.includes('strata') || text.includes('body corporate') || text.includes('owners corporation')) return 'STRATA_REPORT';\n  if (text.includes('building inspection') || text.includes('pest inspection') || text.includes('structural')) return 'BUILDING_PEST_REPORT';\n  if (text.includes('zoning') || text.includes('planning certificate') || text.includes('10.7')) return 'PLANNING_CERTIFICATE';\n  if (text.includes('land tax') || text.includes('revenue nsw') || text.includes('state revenue')) return 'LAND_TAX_CERTIFICATE';\n  if (text.includes('mortgage') || text.includes('discharge') || text.includes('encumbrance')) return 'MORTGAGE_DOCUMENT';\n  if (text.includes('lease') || text.includes('tenancy') || text.includes('residential tenancies')) return 'LEASE_AGREEMENT';\n  if (text.includes('council rates') || text.includes('local government') || text.includes('municipal')) return 'RATES_NOTICE';\n  if (text.includes('smoke alarm') || text.includes('safety switch') || text.includes('pool safety')) return 'SAFETY_CERTIFICATE';\n  return docMeta.document.declaredType?.toUpperCase().replace(/ /g,'_') || 'UNKNOWN';\n})();\n\n// Check for potential expiry dates\nconst expiryPatterns = [\n  /expir(?:y|es|ed)\\s*:?\\s*([\\d]{1,2}[\\/-][\\d]{1,2}[\\/-][\\d]{2,4})/gi,\n  /valid until\\s*:?\\s*([\\d]{1,2}[\\/-][\\d]{1,2}[\\/-][\\d]{2,4})/gi,\n  /valid to\\s*:?\\s*([\\d]{1,2}[\\/-][\\d]{1,2}[\\/-][\\d]{2,4})/gi\n];\nconst expiryMatches = [];\nfor (const pattern of expiryPatterns) {\n  const match = rawText.match(pattern);\n  if (match) expiryMatches.push(...match.slice(0,2));\n}\n\nreturn {\n  json: {\n    case: docMeta.case,\n    document: {\n      ...docMeta.document,\n      detectedType,\n      textLength: rawText.length,\n      textPreview: rawText.substring(0, 4000),\n      fullText: rawText.substring(0, 12000),\n      extractionStatus: rawText === 'TEXT_EXTRACTION_FAILED' ? 'FAILED' : 'SUCCESS',\n      expiryDatesFound: expiryMatches\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a0e09fa8-26a4-4e77-8893-b2d05d14e636",
      "name": "AI Legal Compliance Check",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2224,
        736
      ],
      "parameters": {
        "text": "=You are a senior property law compliance analyst specialising in Australian real estate transactions (NSW, VIC, QLD, SA, WA). Conduct a thorough legal compliance review of the following property document.\n\n**Case Information:**\n- Case ID: {{ $json.case.caseId }}\n- Property: {{ $json.case.propertyAddress }}\n- Transaction Type: {{ $json.case.transactionType }}\n- Jurisdiction: {{ $json.case.jurisdiction }}\n- Submission Date: {{ $json.case.submittedAt }}\n\n**Document Details:**\n- Document ID: {{ $json.document.docId }}\n- Document Name: {{ $json.document.name }}\n- Declared Type: {{ $json.document.declaredType }}\n- AI Detected Type: {{ $json.document.detectedType }}\n- Extraction Status: {{ $json.document.extractionStatus }}\n- Expiry Dates Found: {{ JSON.stringify($json.document.expiryDatesFound) }}\n- Text Length: {{ $json.document.textLength }} characters\n\n**Document Content (first 12,000 chars):**\n{{ $json.document.fullText }}\n\n---\n\n**Validation Requirements by Document Type:**\n\nCONTRACT_OF_SALE: Check \u2014 parties named and signed, property description complete, purchase price stated, deposit terms, settlement date, special conditions, cooling-off rights disclosure, GST clause, subject-to-finance clause if applicable, vendor warranty clauses.\n\nCERTIFICATE_OF_TITLE: Check \u2014 folio identifier, registered proprietor matches vendor, encumbrances listed, easements noted, no adverse possession claims, issue date within 3 months.\n\nVENDOR_DISCLOSURE / SECTION_32: Check \u2014 all prescribed matters disclosed (Section 32 Conveyancing Act), zoning, outgoings, services, owners corporation, building permits, title details, vendor statement signature.\n\nSTRATA_REPORT: Check \u2014 financial status, outstanding levies, pending special levies, building defects, insurance currency, AGM minutes last 2 years, by-laws current.\n\nBUILDING_PEST_REPORT: Check \u2014 inspector licence number, inspection date (within 90 days), findings summary, structural defects listed, active pest activity noted, reinspection recommendations.\n\nPLANNING_CERTIFICATE: Check \u2014 zoning classification, development restrictions, flood/bushfire overlays, issue date (within 3 months for NSW 10.7).\n\nLAND_TAX_CERTIFICATE: Check \u2014 no outstanding land tax, certificate date (within 6 months), property address matches.\n\nSAFETY_CERTIFICATE: Check \u2014 smoke alarm compliance, safety switch compliance, pool safety if applicable, compliance date, licensed certifier.\n\nLEASE_AGREEMENT: Check \u2014 parties named, term dates, rent amount, bond details, permitted use, maintenance obligations, termination clauses.\n\n**Response Format (JSON only, no markdown):**\n{\n  \"docId\": \"{{ $json.document.docId }}\",\n  \"documentName\": \"{{ $json.document.name }}\",\n  \"confirmedType\": \"DOCUMENT_TYPE_ENUM\",\n  \"typeMismatch\": false,\n  \"complianceStatus\": \"PASS | FAIL | REQUIRES_REVIEW | INCOMPLETE_DATA\",\n  \"overallRiskLevel\": \"CRITICAL | HIGH | MEDIUM | LOW\",\n  \"confidenceScore\": 88,\n  \"isExpired\": false,\n  \"expiryDate\": null,\n  \"daysUntilExpiry\": null,\n  \"passedChecks\": [\"list of checks that passed\"],\n  \"failedChecks\": [\"list of checks that failed with specific reason\"],\n  \"missingInformation\": [\"list of required info not found in document\"],\n  \"criticalFlags\": [\"serious legal issues or red flags found\"],\n  \"warnings\": [\"non-critical issues or cautions\"],\n  \"clauseIssues\": [\n    { \"clause\": \"clause description\", \"issue\": \"problem found\", \"severity\": \"CRITICAL|HIGH|MEDIUM|LOW\" }\n  ],\n  \"remediationSteps\": [\"specific actions required to resolve each issue\"],\n  \"legalReferences\": [\"relevant act/regulation references e.g. Conveyancing Act 1919 s32\"],\n  \"documentSummary\": \"2-3 sentence plain-English summary of this document's compliance status\",\n  \"requiresLegalReview\": false,\n  \"autoApprovable\": false\n}",
        "options": {
          "systemMessage": "You are a property law compliance specialist. Return JSON only \u2014 no markdown, no code blocks, no preamble. Be precise and cite specific legal requirements. Flag any issue that could delay settlement or create legal liability. If document text is empty or extraction failed, set complianceStatus to INCOMPLETE_DATA and explain in missingInformation."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "f30d3121-3ba9-48ca-9148-150865251772",
      "name": "Claude AI Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2296,
        960
      ],
      "parameters": {
        "model": "=claude-sonnet-4-20250514",
        "options": {
          "temperature": 0.05
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "43a3ef9e-10f3-4ec2-9228-6871e95b6fde",
      "name": "Parse AI Validation Result",
      "type": "n8n-nodes-base.code",
      "position": [
        2576,
        736
      ],
      "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 validation;\ntry {\n  validation = JSON.parse(cleanText);\n} catch (err) {\n  const match = cleanText.match(/\\{[\\s\\S]*\\}/);\n  if (match) {\n    try { validation = JSON.parse(match[0]); }\n    catch (e2) {\n      validation = {\n        docId: 'PARSE_ERROR',\n        complianceStatus: 'INCOMPLETE_DATA',\n        overallRiskLevel: 'HIGH',\n        confidenceScore: 0,\n        criticalFlags: ['AI response parse failed \u2014 manual review required'],\n        remediationSteps: ['Resubmit document for validation'],\n        documentSummary: 'Validation could not be completed due to a processing error.',\n        requiresLegalReview: true,\n        autoApprovable: false\n      };\n    }\n  }\n}\n\nconst upstream = $('Classify & Prepare Document').item.json;\n\nreturn {\n  json: {\n    case: upstream.case,\n    document: upstream.document,\n    validation,\n    validatedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0892aaee-9b19-4a4e-a8a4-a892bbe9780a",
      "name": "Aggregate All Document Results",
      "type": "n8n-nodes-base.code",
      "position": [
        2800,
        736
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nif (items.length === 0) return [{ json: { error: 'No validation results to aggregate' } }];\n\nconst caseInfo = items[0].json.case;\n\n// Mandatory document checklist per transaction type + jurisdiction\nconst MANDATORY_DOCS = {\n  SALE: {\n    NSW: ['CONTRACT_OF_SALE', 'CERTIFICATE_OF_TITLE', 'VENDOR_DISCLOSURE', 'PLANNING_CERTIFICATE'],\n    VIC: ['CONTRACT_OF_SALE', 'CERTIFICATE_OF_TITLE', 'VENDOR_DISCLOSURE', 'BUILDING_PEST_REPORT'],\n    QLD: ['CONTRACT_OF_SALE', 'CERTIFICATE_OF_TITLE', 'BUILDING_PEST_REPORT'],\n    DEFAULT: ['CONTRACT_OF_SALE', 'CERTIFICATE_OF_TITLE']\n  },\n  PURCHASE: {\n    NSW: ['CONTRACT_OF_SALE', 'BUILDING_PEST_REPORT'],\n    DEFAULT: ['CONTRACT_OF_SALE']\n  },\n  LEASE: {\n    DEFAULT: ['LEASE_AGREEMENT', 'SAFETY_CERTIFICATE']\n  }\n};\n\nconst txType = caseInfo.transactionType || 'SALE';\nconst jurisdiction = caseInfo.jurisdiction || 'NSW';\nconst mandatoryList = MANDATORY_DOCS[txType]?.[jurisdiction]\n  || MANDATORY_DOCS[txType]?.DEFAULT\n  || MANDATORY_DOCS.SALE.DEFAULT;\n\n// Collect all confirmed/detected doc types submitted\nconst submittedTypes = items.map(i =>\n  i.json.validation?.confirmedType || i.json.document?.detectedType || 'UNKNOWN'\n);\n\n// Check for missing mandatory docs\nconst missingMandatory = mandatoryList.filter(req => !submittedTypes.includes(req));\n\n// Build per-document summaries\nconst documentResults = items.map(item => ({\n  docId: item.json.document?.docId,\n  name: item.json.document?.name,\n  confirmedType: item.json.validation?.confirmedType || item.json.document?.detectedType,\n  complianceStatus: item.json.validation?.complianceStatus || 'INCOMPLETE_DATA',\n  overallRiskLevel: item.json.validation?.overallRiskLevel || 'HIGH',\n  isExpired: item.json.validation?.isExpired || false,\n  criticalFlags: item.json.validation?.criticalFlags || [],\n  failedChecks: item.json.validation?.failedChecks || [],\n  warnings: item.json.validation?.warnings || [],\n  remediationSteps: item.json.validation?.remediationSteps || [],\n  requiresLegalReview: item.json.validation?.requiresLegalReview || false,\n  autoApprovable: item.json.validation?.autoApprovable || false,\n  documentSummary: item.json.validation?.documentSummary || ''\n}));\n\n// Compute overall case status\nconst hasCritical = documentResults.some(d => d.overallRiskLevel === 'CRITICAL' || d.criticalFlags?.length > 0);\nconst hasFailed = documentResults.some(d => d.complianceStatus === 'FAIL');\nconst hasExpired = documentResults.some(d => d.isExpired);\nconst allPassed = documentResults.every(d => d.complianceStatus === 'PASS');\nconst hasIncomplete = documentResults.some(d => d.complianceStatus === 'INCOMPLETE_DATA');\nconst missingMandatoryExists = missingMandatory.length > 0;\n\nconst overallStatus = hasCritical || missingMandatoryExists\n  ? 'FAIL'\n  : hasFailed || hasExpired\n    ? 'FAIL'\n    : hasIncomplete\n      ? 'REQUIRES_REVIEW'\n      : allPassed\n        ? 'PASS'\n        : 'REQUIRES_REVIEW';\n\nconst criticalCount = documentResults.filter(d => d.overallRiskLevel === 'CRITICAL').length;\nconst highCount = documentResults.filter(d => d.overallRiskLevel === 'HIGH').length;\nconst passCount = documentResults.filter(d => d.complianceStatus === 'PASS').length;\nconst failCount = documentResults.filter(d => d.complianceStatus === 'FAIL').length;\n\n// Build flat remediation list across all docs\nconst allRemediations = [];\nif (missingMandatory.length > 0) {\n  allRemediations.push(`MISSING DOCUMENTS: Submit the following mandatory documents \u2014 ${missingMandatory.join(', ')}`);\n}\nfor (const doc of documentResults) {\n  for (const step of (doc.remediationSteps || [])) {\n    allRemediations.push(`[${doc.name}] ${step}`);\n  }\n}\n\nreturn [{\n  json: {\n    case: caseInfo,\n    overallStatus,\n    summary: {\n      totalDocuments: documentResults.length,\n      passed: passCount,\n      failed: failCount,\n      requiresReview: documentResults.filter(d => d.complianceStatus === 'REQUIRES_REVIEW').length,\n      expired: documentResults.filter(d => d.isExpired).length,\n      criticalIssues: criticalCount,\n      highRiskDocs: highCount\n    },\n    missingMandatoryDocuments: missingMandatory,\n    documentResults,\n    allRemediationSteps: allRemediations,\n    requiresLegalReview: documentResults.some(d => d.requiresLegalReview) || hasCritical,\n    canAutoApprove: overallStatus === 'PASS' && documentResults.every(d => d.autoApprovable),\n    aggregatedAt: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3963903b-49c7-4594-8f47-2d2793e5b9ef",
      "name": "Route by Compliance Status",
      "type": "n8n-nodes-base.switch",
      "position": [
        3024,
        704
      ],
      "parameters": {
        "mode": "expression",
        "output": "={{ $json.overallStatus }}"
      },
      "typeVersion": 3.1
    },
    {
      "id": "eacaba37-bc71-4487-8c4a-bf2e5435be0f",
      "name": "Email Validation Report to Submitter",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3248,
        448
      ],
      "parameters": {
        "html": "=<html><body style=\"font-family:Arial,sans-serif;max-width:680px;margin:0 auto;padding:24px;color:#222;\">\n<div style=\"background:#1a1a2e;padding:20px 28px;border-radius:8px 8px 0 0;\">\n  <h1 style=\"color:white;margin:0;font-size:1.4em;\">\ud83c\udfe0 Property Document Validation Report</h1>\n  <p style=\"color:#aaa;margin:6px 0 0;\">{{ $json.case.caseId }} &bull; {{ new Date($json.aggregatedAt).toLocaleDateString('en-AU') }}</p>\n</div>\n<div style=\"background:#f8f9fa;padding:20px 28px;border:1px solid #ddd;\">\n  <p><strong>Property:</strong> {{ $json.case.propertyAddress }}</p>\n  <p><strong>Transaction:</strong> {{ $json.case.transactionType }} &bull; <strong>Jurisdiction:</strong> {{ $json.case.jurisdiction }}</p>\n  <div style=\"background:{{ $json.overallStatus === 'PASS' ? '#d4edda' : $json.overallStatus === 'FAIL' ? '#f8d7da' : '#fff3cd' }};border:1px solid {{ $json.overallStatus === 'PASS' ? '#c3e6cb' : $json.overallStatus === 'FAIL' ? '#f5c6cb' : '#ffeeba' }};border-radius:6px;padding:14px 18px;margin:16px 0;\">\n    <h2 style=\"margin:0;color:{{ $json.overallStatus === 'PASS' ? '#155724' : $json.overallStatus === 'FAIL' ? '#721c24' : '#856404' }};\">Overall Status: {{ $json.overallStatus }}</h2>\n    <p style=\"margin:6px 0 0;color:#555;\">{{ $json.summary.totalDocuments }} documents reviewed &bull; {{ $json.summary.passed }} passed &bull; {{ $json.summary.failed }} failed &bull; {{ $json.summary.expired }} expired</p>\n  </div>\n  {{ $json.missingMandatoryDocuments.length > 0 ? '<div style=\"background:#f8d7da;border:1px solid #f5c6cb;border-radius:6px;padding:14px 18px;margin:12px 0;\"><strong style=\"color:#721c24;\">\u26a0\ufe0f Missing Mandatory Documents:</strong><ul style=\"margin:8px 0 0;\">' + $json.missingMandatoryDocuments.map(d => '<li>' + d + '</li>').join('') + '</ul></div>' : '' }}\n  <h3 style=\"color:#1a1a2e;border-bottom:2px solid #e94560;padding-bottom:6px;\">Document Results</h3>\n  {{ $json.documentResults.map(doc => '<div style=\"border:1px solid #ddd;border-left:4px solid ' + (doc.complianceStatus === 'PASS' ? '#28a745' : doc.overallRiskLevel === 'CRITICAL' ? '#e94560' : '#ff6b35') + ';border-radius:4px;padding:12px;margin:10px 0;\"><strong>' + doc.name + '</strong> &nbsp;<span style=\"background:' + (doc.complianceStatus === 'PASS' ? '#28a745' : '#e94560') + ';color:white;padding:2px 8px;border-radius:3px;font-size:0.8em;\">' + doc.complianceStatus + '</span><p style=\"margin:6px 0 0;font-size:0.9em;color:#555;\">' + doc.documentSummary + '</p>' + (doc.criticalFlags.length > 0 ? '<p style=\"color:#e94560;margin:4px 0;\"><strong>Critical:</strong> ' + doc.criticalFlags.join('; ') + '</p>' : '') + '</div>').join('') }}\n  {{ $json.allRemediationSteps.length > 0 ? '<h3 style=\"color:#1a1a2e;\">Required Actions</h3><ol style=\"color:#333;\">' + $json.allRemediationSteps.map(s => '<li style=\"margin:6px 0;\">' + s + '</li>').join('') + '</ol>' : '<p style=\"color:#28a745;\">\u2705 No remediation required. Documents are compliant.</p>' }}\n</div>\n<div style=\"background:#1a1a2e;padding:14px 28px;border-radius:0 0 8px 8px;text-align:center;\">\n  <p style=\"color:#aaa;font-size:0.8em;margin:0;\">Automated validation by AI Property Document Validator &bull; This report does not constitute legal advice. Consult a solicitor for final review.</p>\n</div>\n</body></html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\ud83c\udfe0 Property Document Validation \u2014 {{ $json.overallStatus }} | {{ $json.case.caseId }}",
        "toEmail": "={{ $json.case.submitterEmail }}",
        "fromEmail": "="
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1,
      "continueOnFail": true
    },
    {
      "id": "760cc9a4-b6e1-4a42-82a2-5ab51c509591",
      "name": "Alert Legal Team on Slack",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3248,
        640
      ],
      "parameters": {
        "url": "https://slack.com/api/chat.postMessage",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "jsonBody": "={\n  \"channel\": \"{{ $json.overallStatus === 'FAIL' ? '#legal-critical' : '#legal-review' }}\",\n  \"text\": \"\ud83c\udfe0 Property Document Validation \u2014 {{ $json.overallStatus }}: {{ $json.case.caseId }}\",\n  \"blocks\": [\n    {\n      \"type\": \"header\",\n      \"text\": { \"type\": \"plain_text\", \"text\": \"\ud83d\udccb {{ $json.overallStatus }} \u2014 {{ $json.case.caseId }}\" }\n    },\n    {\n      \"type\": \"section\",\n      \"fields\": [\n        { \"type\": \"mrkdwn\", \"text\": \"*Property:*\\n{{ $json.case.propertyAddress }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Transaction:*\\n{{ $json.case.transactionType }} ({{ $json.case.jurisdiction }})\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Submitter:*\\n{{ $json.case.submitterEmail }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Docs Reviewed:*\\n{{ $json.summary.totalDocuments }} ({{ $json.summary.passed }} passed, {{ $json.summary.failed }} failed)\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Critical Issues:*\\n{{ $json.summary.criticalIssues }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Missing Mandatory:*\\n{{ $json.missingMandatoryDocuments.length > 0 ? $json.missingMandatoryDocuments.join(', ') : 'None' }}\" }\n      ]\n    },\n    {\n      \"type\": \"section\",\n      \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Top Remediation Actions:*\\n{{ $json.allRemediationSteps.slice(0,3).map((s,i) => (i+1)+'. '+s).join('\\\\n') || 'No actions required' }}\" }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Legal Review Required:* {{ $json.requiresLegalReview ? '\ud83d\udd34 YES \u2014 Assign solicitor immediately' : '\ud83d\udfe2 No' }}\" }\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "slackApi"
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "68960595-0d5e-4806-81e6-3a0e78b6737f",
      "name": "Write Compliance Audit Record",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3248,
        832
      ],
      "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": "462620c8-b4aa-4706-bcd5-163a5fabdd75",
      "name": "Build Final Validation Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3472,
        640
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const d = $input.item.json;\nreturn {\n  json: {\n    success: true,\n    caseId: d.case.caseId,\n    propertyAddress: d.case.propertyAddress,\n    transactionType: d.case.transactionType,\n    jurisdiction: d.case.jurisdiction,\n    overallStatus: d.overallStatus,\n    summary: d.summary,\n    missingMandatoryDocuments: d.missingMandatoryDocuments,\n    requiresLegalReview: d.requiresLegalReview,\n    canAutoApprove: d.canAutoApprove,\n    documentResults: d.documentResults.map(doc => ({\n      docId: doc.docId,\n      name: doc.name,\n      type: doc.confirmedType,\n      status: doc.complianceStatus,\n      riskLevel: doc.overallRiskLevel,\n      isExpired: doc.isExpired,\n      criticalFlags: doc.criticalFlags,\n      remediationSteps: doc.remediationSteps,\n      summary: doc.documentSummary\n    })),\n    allRemediationSteps: d.allRemediationSteps,\n    processedAt: d.aggregatedAt\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "89b547ec-2b6d-474d-a9c0-d0e9c7e24b20",
      "name": "Return Validation Result to Caller",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3696,
        640
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json, null, 2) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "c05f97cd-a900-4b7f-9dde-3aaf216d3c83",
      "name": "Mark Case Auto-Approved",
      "type": "n8n-nodes-base.set",
      "position": [
        3248,
        1024
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "status",
              "name": "status",
              "type": "string",
              "value": "AUTO_APPROVED"
            },
            {
              "id": "caseId",
              "name": "caseId",
              "type": "string",
              "value": "={{ $json.case.caseId }}"
            },
            {
              "id": "property",
              "name": "propertyAddress",
              "type": "string",
              "value": "={{ $json.case.propertyAddress }}"
            },
            {
              "id": "ts",
              "name": "approvedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            },
            {
              "id": "msg",
              "name": "message",
              "type": "string",
              "value": "All documents passed automated compliance checks and are approved for settlement processing."
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c57ed76e-fcb9-4ec8-abb9-e7cffaaf1e54",
  "connections": {
    "Claude AI Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Legal Compliance Check",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Document Text": {
      "main": [
        [
          {
            "node": "Classify & Prepare Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Register Validation Case": {
      "main": [
        [
          {
            "node": "Download Document from Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Legal Compliance Check": {
      "main": [
        [
          {
            "node": "Parse AI Validation Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert Legal Team on Slack": {
      "main": [
        [
          {
            "node": "Build Final Validation Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Watch Drive Intake Folder": {
      "main": [
        [
          {
            "node": "Register Validation Case",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Validation Result": {
      "main": [
        [
          {
            "node": "Aggregate All Document Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Compliance Status": {
      "main": [
        [
          {
            "node": "Email Validation Report to Submitter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Write Compliance Audit Record",
            "type": "main",
            "index": 0
          },
          {
            "node": "Mark Case Auto-Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Validation Report to Submitter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Alert Legal Team on Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Write Compliance Audit Record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Validation Report to Submitter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Alert Legal Team on Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Write Compliance Audit Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify & Prepare Document": {
      "main": [
        [
          {
            "node": "AI Legal Compliance Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Document Submission": {
      "main": [
        [
          {
            "node": "Register Validation Case",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Document from Drive": {
      "main": [
        [
          {
            "node": "Extract Document Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Compliance Audit Record": {
      "main": [
        [
          {
            "node": "Build Final Validation Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate All Document Results": {
      "main": [
        [
          {
            "node": "Route by Compliance Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Final Validation Response": {
      "main": [
        [
          {
            "node": "Return Validation Result to Caller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Validation Report to Submitter": {
      "main": [
        [
          {
            "node": "Build Final Validation Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}