{
  "name": "ICP Analysis",
  "nodes": [
    {
      "parameters": {
        "jsCode": "return {\"person_ids\": $input.first().json.data.map(p => p.person_id.toString())};\n"
      },
      "id": "4e08f731-4647-4e45-aa29-ff73d3e09bf9",
      "name": "Extract Person IDs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2144,
        32
      ]
    },
    {
      "parameters": {
        "jsCode": "const personIds = $input.first().json.person_ids.map(item => item);\nconst batchSize = 1000;\nconst batches = [];\n\nfor (let i = 0; i < personIds.length; i += batchSize) {\n  batches.push({ batch: personIds.slice(i, i + batchSize) });\n}\n\nreturn batches.map(b => ({ json: b }));"
      },
      "id": "4bdba660-b2ba-465f-955d-3cddd611c8bd",
      "name": "Batch Person IDs (1000 max)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2368,
        32
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Workflow Configuration').first().json.mcpUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json,text/event-stream"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"jsonrpc\": \"2.0\", \"method\": \"tools/call\", \"params\": {\"name\": \"get_person\", \"arguments\": {\"person_ids\": $input.item.json.batch, \"domains\": $('Workflow Configuration').first().json.enrichmentDomains, \"format\": \"json\"} }, \"id\": 2} }}",
        "options": {}
      },
      "id": "268a3799-babd-4278-9f23-cfb42c3b7189",
      "name": "Enrich Profiles",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2592,
        32
      ],
      "credentials": {
        "httpBasicAuth": {
          "id": "nLm1GC2IsA6KgnYy",
          "name": "Watt Prod"
        },
        "httpHeaderAuth": {
          "id": "zpQJZJm7FtYZwdfy",
          "name": "Watt Prod Headers"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const persons = $input.first().json.profiles || [];\nconst clusterCounts = {};\n\nconst domains = [\n  'affinity',\n  'content',\n  'demographic',\n  'employment',\n  'political'\n];\n\n// Count cluster occurrences across all persons\nfor (const person of persons) {\n  for (const column of domains) {\n    if (person.domains[column] && person.domains[column] != '{}') {\n      try {\n        const domainData = person.domains[column];   \n        const keys = Object.keys(domainData);\n        for (const cluster_value of keys) {\n          const cluster = domainData[cluster_value];\n          if (!cluster || !cluster.cluster_id || cluster.value === false) continue;\n          const key = `${column}:${cluster.cluster_id}:${cluster.value}`;\n          clusterCounts[key] = (clusterCounts[key] || 0) + 1;\n        }\n      } catch (error) {\n        console.log(error);\n      }\n    }\n  }\n}\n\n// Calculate top clusters by frequency\nconst totalPersons = persons.length;\nconst topClusters = Object.entries(clusterCounts)\n  .map(([key, count]) => {\n    const [domain, cluster_id, cluster_value] = key.split(':');\n    return {\n    cluster_id,\n    domain,\n    cluster_value,\n    count,\n    percentage: (count / totalPersons * 100).toFixed(2)\n  }})\n  .sort((a, b) => b.count - a.count)\n  .slice(0, 50);\n\n// Extract cluster IDs for boolean expression\nconst clusterIds = topClusters.map(c => c.cluster_id);\nconst topClusterCounts = topClusters.map(({cluster_id, count}) => ({cluster_id, count}))\nconst booleanExpression = clusterIds.slice(0, 10).join(\" AND \");\n\nreturn [{\n  json: {\n    topClusters,\n    topClusterCounts,\n    //booleanExpression,\n    //clusterIds\n  }\n}];"
      },
      "id": "d18990ad-49e6-4b4b-936d-ed49e8bf7071",
      "name": "Analyze Clusters",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3024,
        32
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "icp-analysis",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "f23885f0-af25-4721-9d26-038dedc1739c",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -464,
        -320
      ],
      "webhookId": "8d354013-597b-4de7-a308-2c653703eff5"
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "74595a07-20db-43a6-a166-06df41474e00",
      "name": "Extract CSV Data",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        -16,
        -224
      ]
    },
    {
      "parameters": {
        "jsCode": "// Take first 5 rows for schema detection\nreturn items.slice(0, 5).map(item => ({ json: item.json }));"
      },
      "id": "1897dea1-b9e6-49d9-aefb-d91a2cdb2845",
      "name": "Get First 5 Rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        -224
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ JSON.stringify($input.all()) }}",
        "hasOutputParser": true,
        "batching": {
          "batchSize": 10
        }
      },
      "id": "854d8c3a-9270-4140-ac09-dc65626f09ba",
      "name": "Detect CSV Schema",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        448,
        -224
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "\nconst csvData = $('Extract CSV Data').all()\nconst columns = $('Detect CSV Schema').first().json.output\n\nconst multiIdentifiers = [];\n\n// Extract emails if column detected\nif (columns.email_column && columns.email_column !== \"null\") {\n  const emails = csvData\n    .map(row => (row.json[columns.email_column] || '').trim().replace(/^\\n|\\n$/g, ''))\n    .filter(v => v);\n  multiIdentifiers.push({\n    id_type: \"email\",\n    hash_type: \"plaintext\",\n    values: emails\n  });\n}\n\n// Extract phones if column detected\nif (columns.phone_column && columns.phone_column !== \"null\") {\n  const phones = csvData\n    .map(row => (row[columns.phone_column] || '').trim())\n    .filter(v => v);\n  multiIdentifiers.push({\n    id_type: \"phone\",\n    hash_type: \"plaintext\",\n    values: phones\n  });\n}\n\n// Extract addresses if column detected\nif (columns.address_column && columns.address_column !== \"null\") {\n  const addresses = csvData\n    .map(row => (row[columns.address_column] || '').trim())\n    .filter(v => v);\n  multiIdentifiers.push({\n    id_type: \"address\",\n    hash_type: \"plaintext\",\n    values: addresses\n  });\n}\n\nreturn [{ json: { multi_identifiers: multiIdentifiers } }];"
      },
      "id": "b986891c-ab5f-4442-916f-42d4552f1c7b",
      "name": "Build Multi-Identifiers",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1024,
        32
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Workflow Configuration').first().json.mcpUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json,text/event-stream"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"jsonrpc\": \"2.0\", \"method\": \"tools/call\", \"params\": {\"name\": \"resolve_identities\", \"arguments\": { \"multi_identifiers\": $input.first().json.multi_identifiers, \"format\": \"json\"} }, \"id\": 1 } }}",
        "options": {}
      },
      "id": "e67a6001-c574-4a5c-98ae-a43596f56b3a",
      "name": "Download Resolved IDs",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1248,
        32
      ],
      "credentials": {
        "httpBasicAuth": {
          "id": "nLm1GC2IsA6KgnYy",
          "name": "Watt Prod"
        },
        "httpHeaderAuth": {
          "id": "zpQJZJm7FtYZwdfy",
          "name": "Watt Prod Headers"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const resource = JSON.parse($input.first().json.data.replace(/^[^{]*/, '')).result.content.find(item => item.type == 'resource').resource;\n\nif (resource.uri) {\n  return [{ json: { downloadUrl: resource.uri } }];\n}\n\nthrow new Error(\"No download URL found in response\");"
      },
      "id": "ce66fe0c-bb63-425d-999c-f71463d28bf8",
      "name": "Extract Download URL",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1472,
        32
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.downloadUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "bc6fc4a6-8996-405f-b60b-91fb4b651d66",
      "name": "Fetch Resolved IDs File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1696,
        32
      ]
    },
    {
      "parameters": {
        "operation": "fromJson",
        "options": {}
      },
      "id": "333dc51e-76aa-4087-862d-2f2f0a1e53b3",
      "name": "Parse Resolved IDs JSON",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        1920,
        32
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        448,
        -16
      ],
      "id": "28a1881b-39e4-43fb-815b-c901a7f9a781",
      "name": "Anthropic Chat Model",
      "credentials": {
        "anthropicApi": {
          "id": "GnTla76TJmv3i4sp",
          "name": "Anthropic account"
        }
      }
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "{\n  \"mcpUrl\": \"https://api.wattdata.ai/mcp\",\n  \"enrichmentDomains\": [\n    \"address\",\n    \"affinity\",\n    \"content\",\n    \"demographic\",\n    \"email\",\n    \"employment\",\n    \"political\"\n  ]\n}\n",
        "includeOtherFields": true,
        "options": {}
      },
      "id": "69b302cb-8749-4299-a409-2120225287e9",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -240,
        -224
      ]
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n\t\"email_column\": \"email_column_name\",\n\t\"phone_column\": \"phone_column_name\",\n    \"address_column\": \"address_column_name\"\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        592,
        -16
      ],
      "id": "dd8a4a5b-4d8d-44de-a243-e94bdd3d28bf",
      "name": "Structured Output Parser"
    },
    {
      "parameters": {
        "jsCode": "return {json: {profiles: $input.all().flatMap(i => JSON.parse(JSON.parse(i.json.data.replace(/^[^{]*/, '')).result.content.find(item => item.type == 'text').text).profiles)}};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2816,
        32
      ],
      "id": "8b8c6aa4-6656-4e8d-a9c4-c9d973ec23f5",
      "name": "Extract Enriched Profiles"
    },
    {
      "parameters": {
        "formTitle": "Upload CSV File",
        "formFields": {
          "values": [
            {
              "fieldLabel": "data",
              "fieldType": "file",
              "acceptFileTypes": ".csv",
              "requiredField": true
            },
            {
              "fieldLabel": "Prompt",
              "fieldType": "textarea",
              "placeholder": "Enter any ICP refinement text here"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.3,
      "position": [
        -464,
        -112
      ],
      "id": "4826077e-6183-4793-896a-a857e77ff03a",
      "name": "On form submission",
      "webhookId": "8f57f827-cd0d-4ef1-ad2b-2567e4baa0e0"
    },
    {
      "parameters": {
        "endpointUrl": "={{ $('Workflow Configuration').first().json.mcpUrl }}",
        "authentication": "headerAuth",
        "include": "selected",
        "includeTools": [
          "find_persons"
        ],
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.mcpClientTool",
      "typeVersion": 1.2,
      "position": [
        3376,
        256
      ],
      "id": "08ef92fc-056e-45d2-9637-57ca15252547",
      "name": "MCP Client",
      "credentials": {
        "httpHeaderAuth": {
          "id": "zpQJZJm7FtYZwdfy",
          "name": "Watt Prod Headers"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "claude-sonnet-4-5-20250929",
          "mode": "list",
          "cachedResultName": "claude-sonnet-4-5-20250929"
        },
        "messages": {
          "values": [
            {
              "content": "=We are performing an ICP analysis on the input person clusters. You have as input a list of trait cluster IDs and user counts per cluster, indicating how prevalent each trait is in the input set of people.\n\nPerform and output an extensive Markdown-formatted ICP analysis for the identified clusters. Identify one or more persona segments that provide high signal within this audience. The analysis should describe the clusters rather than referencing cluster IDs.\n\nDesign a SINGLE boolean expression using cluster IDs to filter an audience. Then call `find_persons` ONCE with that universal expression, requesting JSON output. Do not provide a geographical filter. Respect the tool API when you do this - don't add any parameters that the tool does not support. Your mission is to form an intelligent audience segmentation focused on conjunctions (AND) as much as possible, with disjunctions (OR) and negations (AND NOT) where necessary to capture independent persona segments.\n\nWhen you respond, respond ONLY in JSON.\n\nInput clusters:\n\n{{ JSON.stringify($input.first().json.topClusterCounts) }}"
            }
          ]
        },
        "options": {
          "system": "You're an expert marketing analyst. You perform ICP analysis for users and output the detailed analysis. You call the input `find_person` tool with a boolean expression on segmentation clusters.  You output only JSON, with a markdown-formatted ICP analysis as well as a URL from the find_persons tool call containing the resulting ICP audience. Output format: {'booleanExpression': '<boolean expression>', 'downloadUrl': '<download URL from find persons tool>', 'icp_analysis': '<markdown ICP analysis>'}",
          "maxTokens": 8096,
          "maxToolsIterations": 5
        }
      },
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        3232,
        32
      ],
      "id": "1c8cd00b-a19f-4e5c-9712-c451de3054a9",
      "name": "Message a model",
      "credentials": {
        "anthropicApi": {
          "id": "GnTla76TJmv3i4sp",
          "name": "Anthropic account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const content = $input.first().json.content.find(t => t.type === 'text').text.replace(/^[^{]*/, '');\nconst resource = JSON.parse(content.slice(0, content.lastIndexOf('}')+1));\n\nif (resource.downloadUrl) {\n  return [{ json: { downloadUrl: resource.downloadUrl, expression: resource.booleanExpression, icpAnalysis: resource.icp_analysis } }];\n}\n\nthrow new Error(\"No download URL found in response\");"
      },
      "id": "89204b58-faeb-495c-8069-a2924785273f",
      "name": "Extract Audience Download URL",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3568,
        32
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Extract Person IDs": {
      "main": [
        [
          {
            "node": "Batch Person IDs (1000 max)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Person IDs (1000 max)": {
      "main": [
        [
          {
            "node": "Enrich Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich Profiles": {
      "main": [
        [
          {
            "node": "Extract Enriched Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Clusters": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract CSV Data": {
      "main": [
        [
          {
            "node": "Get First 5 Rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Multi-Identifiers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get First 5 Rows": {
      "main": [
        [
          {
            "node": "Detect CSV Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect CSV Schema": {
      "main": [
        [
          {
            "node": "Build Multi-Identifiers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Multi-Identifiers": {
      "main": [
        [
          {
            "node": "Download Resolved IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Resolved IDs": {
      "main": [
        [
          {
            "node": "Extract Download URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Download URL": {
      "main": [
        [
          {
            "node": "Fetch Resolved IDs File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Resolved IDs File": {
      "main": [
        [
          {
            "node": "Parse Resolved IDs JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Resolved IDs JSON": {
      "main": [
        [
          {
            "node": "Extract Person IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Detect CSV Schema",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Extract CSV Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Detect CSV Schema",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Extract Enriched Profiles": {
      "main": [
        [
          {
            "node": "Analyze Clusters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MCP Client": {
      "ai_tool": [
        [
          {
            "node": "Message a model",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Extract Audience Download URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Audience Download URL": {
      "main": [
        []
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c1b4e6ea-a5fc-4559-9998-ba3fdd3fcd90",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "52954ab2cd4667121483e0bbdb39298a1c46a09d94059069576e5491800b0906"
  },
  "id": "W3lZJBXzlZOfID0q",
  "tags": []
}