{
  "name": "Extract title deed data and score risk factors with AI",
  "nodes": [
    {
      "parameters": {
        "content": "## \ud83c\udfe0 Title Deed Risk Scorer\n\n### What this workflow does\n1. Watches Google Drive folder for title documents\n2. Extracts grantor, grantee, property description, liens\n3. AI evaluates chain of title gaps and risks\n4. Generates risk score with explanatory notes\n5. Logs findings to Google Sheets\n6. Sends Slack alert with risk assessment\n\n### Setup steps\n1. Get PDF Vector API key from pdfvector.com/api-keys\n2. Create a Google Drive folder for title documents\n3. Create a Google Sheet with these columns:\n   Property | Grantor | Grantee | Recording Date | Liens | Encumbrances | Risk Score | Risk Factors | Processed Date\n4. Update the folder ID and sheet ID in the nodes\n5. Connect your Slack workspace\n\n### Perfect for\n- Title companies\n- Real estate attorneys\n- Escrow officers",
        "height": 520,
        "width": 400,
        "color": 5
      },
      "id": "sticky-main",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        60,
        60
      ]
    },
    {
      "parameters": {
        "content": "## \ud83d\udd00 Parallel Processing\n\nExtract and Risk Analysis run at the\nsame time from the same PDF",
        "height": 100,
        "width": 260
      },
      "id": "sticky-parallel",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        840,
        100
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "value": "YOUR_FOLDER_ID",
          "mode": "list",
          "cachedResultName": "Title Documents"
        },
        "event": "fileCreated",
        "options": {}
      },
      "id": "gdrive-trigger",
      "name": "New Title Document",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "typeVersion": 1,
      "position": [
        480,
        300
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "gdrive-download",
      "name": "Download Document",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        680,
        300
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "extract",
        "inputType": "file",
        "prompt": "Extract title deed/document data as flat fields. documentType, recordingDate (YYYY-MM-DD), recordingNumber, county, state, grantorName, grantorType (individual/entity), granteeName, granteeType (individual/entity), propertyAddress, legalDescription, parcelNumber, acreage (number), lotNumber, blockNumber, subdivision, deedType (warranty/quitclaim/grant/sheriff/trustee), considerationAmount (number), transferTax (number), liensCount (number), liensDescription, easementsList (semicolon-separated easements), restrictionsList (semicolon-separated restrictions/covenants), encumbrancesList (semicolon-separated encumbrances), exceptionsList (semicolon-separated title exceptions), priorDocumentsList (semicolon-separated prior deeds/documents), notaryName, notaryExpiration, titleInsuranceRequired (true/false), cloudOnTitle (true/false), cloudDescription, riskScore (number 0-100), riskLevel (Low/Medium/High/Critical).",
        "schema": "{\"type\": \"object\", \"additionalProperties\": false, \"properties\": {\"documentType\": {\"type\": \"string\"}, \"recordingDate\": {\"type\": \"string\"}, \"recordingNumber\": {\"type\": \"string\"}, \"county\": {\"type\": \"string\"}, \"state\": {\"type\": \"string\"}, \"grantorName\": {\"type\": \"string\"}, \"grantorType\": {\"type\": \"string\"}, \"granteeName\": {\"type\": \"string\"}, \"granteeType\": {\"type\": \"string\"}, \"propertyAddress\": {\"type\": \"string\"}, \"legalDescription\": {\"type\": \"string\"}, \"parcelNumber\": {\"type\": \"string\"}, \"acreage\": {\"type\": \"number\"}, \"lotNumber\": {\"type\": \"string\"}, \"blockNumber\": {\"type\": \"string\"}, \"subdivision\": {\"type\": \"string\"}, \"deedType\": {\"type\": \"string\"}, \"considerationAmount\": {\"type\": \"number\"}, \"transferTax\": {\"type\": \"number\"}, \"liensCount\": {\"type\": \"number\"}, \"liensDescription\": {\"type\": \"string\"}, \"easementsList\": {\"type\": \"string\"}, \"restrictionsList\": {\"type\": \"string\"}, \"encumbrancesList\": {\"type\": \"string\"}, \"exceptionsList\": {\"type\": \"string\"}, \"priorDocumentsList\": {\"type\": \"string\"}, \"notaryName\": {\"type\": \"string\"}, \"notaryExpiration\": {\"type\": \"string\"}, \"titleInsuranceRequired\": {\"type\": \"boolean\"}, \"cloudOnTitle\": {\"type\": \"boolean\"}, \"cloudDescription\": {\"type\": \"string\"}, \"riskScore\": {\"type\": \"number\"}, \"riskLevel\": {\"type\": \"string\"}}}"
      },
      "id": "pdfvector-extract",
      "name": "Extract Title Data",
      "type": "n8n-nodes-pdfvector.pdfVector",
      "typeVersion": 2,
      "position": [
        900,
        220
      ],
      "credentials": {
        "pdfVectorApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "ask",
        "inputType": "file",
        "question": "Analyze this title document in 5 sentences: 1) Overall title status (Clear/Clouded/At Risk), 2) Most critical defect or risk found, 3) Key lien or encumbrance that needs resolution, 4) Title insurability rating (Insurable/Requires Review/Not Insurable), 5) Single most important action before closing."
      },
      "id": "pdfvector-analyze",
      "name": "Risk Analysis",
      "type": "n8n-nodes-pdfvector.pdfVector",
      "typeVersion": 2,
      "position": [
        900,
        380
      ],
      "credentials": {
        "pdfVectorApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1120,
        300
      ],
      "id": "merge-results",
      "name": "Merge Results"
    },
    {
      "parameters": {
        "jsCode": "const allInputs = $input.all();\nconst data = (allInputs[0]?.json?.data || allInputs[0]?.json) || {};\nconst riskAnalysis = allInputs[1]?.json?.answer || allInputs[1]?.json?.markdown || '';\nconst fileName = $('New Title Document').item.json.name;\nconst fileId   = $('New Title Document').item.json.id;\n\nconst c = v => parseFloat(String(v||0).replace(/[^0-9.]/g,'')) || 0;\nconst exceptions = (data.exceptionsList || '').split(';').filter(Boolean);\nconst liens      = (data.liensDescription || '').split(';').filter(Boolean);\nconst easements  = (data.easementsList || '').split(';').filter(Boolean);\n\nconst riskScore = parseInt(data.riskScore) || 0;\nconst riskLevel = data.riskLevel ||\n  (riskScore >= 70 ? 'High' : riskScore >= 40 ? 'Medium' : 'Low');\n\nreturn [{ json: {\n  documentType:       data.documentType       || 'N/A',\n  recordingDate:      data.recordingDate      || 'N/A',\n  recordingNumber:    data.recordingNumber    || 'N/A',\n  county:             data.county             || 'N/A',\n  state:              data.state              || 'N/A',\n  grantorName:        data.grantorName        || 'N/A',\n  granteeName:        data.granteeName        || 'N/A',\n  propertyAddress:    data.propertyAddress    || 'N/A',\n  parcelNumber:       data.parcelNumber       || 'N/A',\n  deedType:           data.deedType           || 'N/A',\n  considerationAmount: c(data.considerationAmount),\n  liensCount:         parseInt(data.liensCount) || liens.length,\n  liensDescription:   data.liensDescription   || 'None',\n  easementsList:      data.easementsList      || 'None',\n  exceptionsList:     data.exceptionsList     || 'None',\n  exceptionsCount:    exceptions.length,\n  cloudOnTitle:       data.cloudOnTitle       || false,\n  cloudDescription:   data.cloudDescription   || 'None',\n  riskScore, riskLevel, riskAnalysis,\n  fileName, fileId,\n  processedAt: new Date().toISOString(),\n  fileId: fileId,\n  grantorNames: data.grantorName || \"N/A\",\n  granteeNames: data.granteeName || \"N/A\",\n  recordingInfo: (data.recordingNumber || \"N/A\") + \" | \" + (data.county || \"N/A\") + \", \" + (data.state || \"N/A\"),\n  consideration: \"$\" + c(data.considerationAmount).toLocaleString(),\n  lienCount: parseInt(data.liensCount) || liens.length,\n  lienSummary: data.liensDescription || \"None\",\n  easementCount: easements.length,\n  easementSummary: data.easementsList || \"None\",\n  encumbranceSummary: data.encumbrancesList || \"None\",\n  riskLevel: riskLevel,\n  riskAnalysis: riskAnalysis,\n  criticalIssues: data.cloudDescription || \"None\",\n  highIssues: \"N/A\",\n  totalLienAmount: 0,\n  insurability: data.cloudOnTitle ? \"Requires Review\" : \"Insurable\",\n}}];"
      },
      "id": "format-data",
      "name": "Score Risk",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "list",
          "cachedResultName": "Title Search Log"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Titles"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Property": "={{ $json.propertyAddress }}",
            "Parcel": "={{ $json.parcelNumber }}",
            "Grantor": "={{ $json.grantorName }}",
            "Grantee": "={{ $json.granteeName }}",
            "Recording Date": "={{ $json.recordingDate }}",
            "Liens": "={{ $json.liensCount }}",
            "Lien Amount": "={{ $json.totalLienAmount }}",
            "Risk Score": "={{ $json.riskScore }}",
            "Risk Level": "={{ $json.riskLevel }}",
            "Insurability": "={{ $json.insurability }}",
            "Processed Date": "={{ $json.processedAt.split('T')[0] }}",
            "Deed Type": "={{ $json.deedType }}",
            "Exceptions": "={{ $json.exceptionsCount }}",
            "Cloud on Title": "={{ $json.cloudOnTitle ? 'Yes' : 'No' }}",
            "Document Link": "=https://drive.google.com/file/d/{{ $json.fileId }}/view",
            "Added Date": "={{ $json.processedAt.split('T')[0] }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Property",
              "displayName": "Property",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Parcel",
              "displayName": "Parcel",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Grantor",
              "displayName": "Grantor",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Grantee",
              "displayName": "Grantee",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Recording Date",
              "displayName": "Recording Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Liens",
              "displayName": "Liens",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Lien Amount",
              "displayName": "Lien Amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Risk Score",
              "displayName": "Risk Score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Risk Level",
              "displayName": "Risk Level",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Insurability",
              "displayName": "Insurability",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed Date",
              "displayName": "Processed Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "sheets-log",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        1520,
        300
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "YOUR_SLACK_CHANNEL_ID",
          "mode": "list",
          "cachedResultName": "title-search"
        },
        "text": "=\ud83c\udfe0 *Title Deed Risk Assessment Complete*\n\n*Document:* {{ $('Score Risk').item.json.documentType }}\n*Property:* {{ $('Score Risk').item.json.propertyAddress }}\n*Parcel:* {{ $('Score Risk').item.json.parcelNumber }}\n\n\ud83d\udc65 *Parties:*\n\u2022 Grantor: {{ $('Score Risk').item.json.grantorNames }}\n\u2022 Grantee: {{ $('Score Risk').item.json.granteeNames }}\n\n\ud83d\udcc5 *Recording:*\n{{ $('Score Risk').item.json.recordingInfo }}\n\u2022 Consideration: ${{ $('Score Risk').item.json.consideration }}\n\n\u26a0\ufe0f *Risk Assessment:*\n\u2022 Score: {{ $('Score Risk').item.json.riskScore }}/100\n\u2022 Level: *{{ $('Score Risk').item.json.riskLevel }}*\n\u2022 Insurability: {{ $('Score Risk').item.json.insurability }}\n\u2022 Critical Issues: {{ $('Score Risk').item.json.criticalIssues }}\n\u2022 High Issues: {{ $('Score Risk').item.json.highIssues }}\n\n\ud83d\udd17 *Liens ({{ $('Score Risk').item.json.lienCount }}):*\n{{ $('Score Risk').item.json.lienSummary }}\nTotal: ${{ $('Score Risk').item.json.totalLienAmount }}\n\n\ud83d\udccb *Easements ({{ $('Score Risk').item.json.easementCount }}):*\n{{ $('Score Risk').item.json.easementSummary }}\n\n\ud83d\udcdd *Encumbrances:*\n{{ $('Score Risk').item.json.encumbranceSummary }}\n\n\ud83d\udcc4 *Risk Analysis:*\n{{ $('Score Risk').item.json.riskAnalysis }}\n\n\ud83d\udd17 <https://drive.google.com/file/d/{{ $('Score Risk').item.json.fileId }}/view|View Document>",
        "otherOptions": {}
      },
      "id": "slack-notify",
      "name": "Send to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1720,
        300
      ],
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "New Title Document": {
      "main": [
        [
          {
            "node": "Download Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Document": {
      "main": [
        [
          {
            "node": "Extract Title Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Risk Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Title Data": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Risk Analysis": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Results": {
      "main": [
        [
          {
            "node": "Score Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Risk": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Sheets": {
      "main": [
        [
          {
            "node": "Send to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}