{
  "name": "Comet LeadGen Pipeline",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 19 * * 2,3,4"
            }
          ]
        }
      },
      "name": "Cron \u2014 Tu/We/Th 19:00 UTC",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "id": "node-cron-01"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "comet-trigger",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Webhook Trigger (manual)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        240,
        460
      ],
      "id": "node-webhook-01"
    },
    {
      "parameters": {
        "functionCode": "// Step 1 \u2014 Merge trigger, validate payload\nconst data = $input.item.json;\nconst icp = data.icp || 'usa_dental_medspa';\nconst dryRun = data.dry_run || false;\n\nconst validIcps = ['usa_dental_medspa', 'pl_logistyka_wielkopolska'];\nif (!validIcps.includes(icp)) {\n  throw new Error(`Unknown ICP: ${icp}. Valid: ${validIcps.join(', ')}`);\n}\n\nreturn [{\n  json: {\n    project_id: `CP-${new Date().toISOString().replace(/[^0-9]/g, '').slice(0,14)}`,\n    icp,\n    dry_run: dryRun,\n    started_at: new Date().toISOString(),\n    status: 'AWAITING_SCRAPE'\n  }\n}];"
      },
      "name": "Step 1 \u2014 Init Pipeline",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        460,
        380
      ],
      "id": "node-step1"
    },
    {
      "parameters": {
        "url": "={{$env['SCRAPIO_BASE_URL']}}/companies/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env['SCRAPIO_API_KEY']}}"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "industry",
              "value": "={{$json.icp === 'usa_dental_medspa' ? 'dental healthcare medspa' : 'logistics transportation'}}"
            },
            {
              "name": "location",
              "value": "={{$json.icp === 'usa_dental_medspa' ? 'USA' : 'Wielkopolska Poland'}}"
            },
            {
              "name": "limit",
              "value": "=50"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "name": "Step 2 \u2014 Scrap.io Search",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        680,
        380
      ],
      "id": "node-step2"
    },
    {
      "parameters": {
        "functionCode": "// Step 3 \u2014 Build search params + filter Scrap.io results\nconst state = $('Step 1 \u2014 Init Pipeline').item.json;\nconst scrapio = $input.item.json;\n\nconst companies = scrapio.companies || [];\n\n// Map do naszego formatu\nconst rawLeads = companies.map((c, i) => ({\n  id: `${state.project_id}-${String(i+1).padStart(3,'0')}`,\n  name: c.name || '',\n  address: c.address || '',\n  phone: c.phone || null,\n  website: c.website || null,\n  email: c.email || null,\n  employee_count: c.employee_count || null,\n  linkedin_url: c.linkedin_url || null,\n  source: 'scrapio'\n})).filter(l => l.name);\n\nreturn [{\n  json: {\n    ...state,\n    raw_leads: rawLeads,\n    raw_leads_count: rawLeads.length,\n    status: 'AWAITING_LLM_PARSE'\n  }\n}];"
      },
      "name": "Step 3 \u2014 Build Search Params",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        900,
        380
      ],
      "id": "node-step3"
    },
    {
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env['OPENAI_API_KEY']}}"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "gpt-4o-mini"
            },
            {
              "name": "messages",
              "value": "=[{\"role\":\"system\",\"content\":\"You are a B2B lead qualifier. Return valid JSON only.\"},{\"role\":\"user\",\"content\":\"Qualify these leads and score A/B/C fit for AI automation services. Return JSON array with fields: name, fit_score, confidence (0-100), approach_angle, hours_saved_monthly, disqualify (bool). Leads: \" + JSON.stringify($json.raw_leads.slice(0,10))}]"
            },
            {
              "name": "response_format",
              "value": "={\"type\":\"json_object\"}"
            },
            {
              "name": "temperature",
              "value": "=0.1"
            }
          ]
        },
        "options": {
          "timeout": 60000
        }
      },
      "name": "Step 5 \u2014 LLM Parser (OpenAI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        1120,
        380
      ],
      "id": "node-step5-llm"
    },
    {
      "parameters": {
        "functionCode": "// Step 5b \u2014 Parse LLM response + triage\nconst state = $('Step 3 \u2014 Build Search Params').item.json;\nconst llmResp = $input.item.json;\n\nlet parsedLeads = [];\ntry {\n  const content = llmResp.choices[0].message.content;\n  const data = JSON.parse(content);\n  parsedLeads = data.leads || data.results || [];\n} catch(e) {\n  console.error('LLM parse error:', e.message);\n}\n\n// Triage\nconst enrichedLeads = parsedLeads.filter(l => \n  !l.disqualify && \n  ['A','B'].includes(l.fit_score) && \n  (l.confidence || 0) >= 40\n);\n\nconst failedLeads = parsedLeads.filter(l => l.disqualify);\n\nreturn [{\n  json: {\n    ...state,\n    parsed_leads: parsedLeads,\n    enriched_leads: enrichedLeads,\n    failed_leads: failedLeads,\n    enriched_count: enrichedLeads.length,\n    status: 'AWAITING_CRM_EXPORT'\n  }\n}];"
      },
      "name": "Step 6 \u2014 Triage + Exception Check",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1340,
        380
      ],
      "id": "node-step6"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json.enriched_count}}",
              "operation": "larger",
              "value2": 0
            }
          ]
        }
      },
      "name": "Has Qualified Leads?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1560,
        380
      ],
      "id": "node-if-leads"
    },
    {
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/batch/create",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env['HUBSPOT_API_KEY']}}"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "inputs",
              "value": "={{$json.enriched_leads.map(l => ({properties: {company: l.name, address: l._address || '', phone: l._phone || '', lead_fit_score: l.fit_score, approach_angle: l.approach_angle || '', hs_lead_status: 'NEW'}}))}}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "name": "Step 7a \u2014 HubSpot Export",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        1780,
        300
      ],
      "id": "node-step7-hubspot"
    },
    {
      "parameters": {
        "functionCode": "// Step 8 \u2014 Generate final report + Slack notification\nconst state = $('Step 6 \u2014 Triage + Exception Check').item.json;\nconst hubspotResult = $input.item.json;\n\nconst exportedCount = (hubspotResult.results || []).length;\nconst topLeads = (state.enriched_leads || []).slice(0,3).map(l => l.name).join(', ');\n\nconst report = {\n  project_id: state.project_id,\n  icp: state.icp,\n  status: 'DONE',\n  raw_count: state.raw_leads_count,\n  parsed_count: (state.parsed_leads || []).length,\n  exported_count: exportedCount,\n  top_leads: topLeads,\n  generated_at: new Date().toISOString()\n};\n\nreturn [{json: report}];"
      },
      "name": "Step 8 \u2014 Report Generator",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        2000,
        300
      ],
      "id": "node-step8"
    },
    {
      "parameters": {
        "url": "={{$env['SLACK_WEBHOOK_URL']}}",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=\u2705 *Comet Pipeline {{$json.project_id}}* \u2014 DONE\n\ud83d\udcca Scraped: {{$json.raw_count}} | Exported: {{$json.exported_count}}\n\ud83c\udfc6 Top: {{$json.top_leads}}\n\ud83d\udccc ICP: `{{$json.icp}}`"
            }
          ]
        },
        "options": {}
      },
      "name": "Slack Notification",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        2220,
        300
      ],
      "id": "node-slack"
    },
    {
      "parameters": {
        "functionCode": "// No qualified leads \u2014 log and stop\nconst state = $json;\nconsole.log('No qualified leads for project:', state.project_id);\nreturn [{json: {...state, status: 'NO_LEADS'}}];"
      },
      "name": "Log \u2014 No Leads",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1780,
        480
      ],
      "id": "node-no-leads"
    }
  ],
  "connections": {
    "Cron \u2014 Tu/We/Th 19:00 UTC": {
      "main": [
        [
          {
            "node": "Step 1 \u2014 Init Pipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger (manual)": {
      "main": [
        [
          {
            "node": "Step 1 \u2014 Init Pipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 1 \u2014 Init Pipeline": {
      "main": [
        [
          {
            "node": "Step 2 \u2014 Scrap.io Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 2 \u2014 Scrap.io Search": {
      "main": [
        [
          {
            "node": "Step 3 \u2014 Build Search Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 3 \u2014 Build Search Params": {
      "main": [
        [
          {
            "node": "Step 5 \u2014 LLM Parser (OpenAI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 5 \u2014 LLM Parser (OpenAI)": {
      "main": [
        [
          {
            "node": "Step 6 \u2014 Triage + Exception Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 6 \u2014 Triage + Exception Check": {
      "main": [
        [
          {
            "node": "Has Qualified Leads?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Qualified Leads?": {
      "main": [
        [
          {
            "node": "Step 7a \u2014 HubSpot Export",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log \u2014 No Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 7a \u2014 HubSpot Export": {
      "main": [
        [
          {
            "node": "Step 8 \u2014 Report Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step 8 \u2014 Report Generator": {
      "main": [
        [
          {
            "node": "Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": "",
    "timezone": "UTC"
  },
  "tags": [
    "leadgen",
    "comet",
    "b2b",
    "n8n"
  ],
  "versionId": "1.0.0"
}