{
  "name": "02c — Opportunity Research (70B)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/15 * * * *"
            }
          ]
        }
      },
      "id": "wf02c-0001",
      "name": "Every 15 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM leads.leads WHERE state = 'qualified' AND research_artifact IS NULL ORDER BY updated_at ASC LIMIT 3",
        "additionalFields": {}
      },
      "id": "wf02c-0002",
      "name": "Fetch Qualified No Research",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "wf02c-0003",
      "name": "Split Leads",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT value FROM leads.system_state WHERE key = 'groq_rpm_window'",
        "additionalFields": {}
      },
      "id": "wf02c-0004",
      "name": "Read RPM Window",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const limit = parseInt($env.GROQ_RPM_LIMIT || '25');\nconst now = Date.now();\nconst windowMs = 60000;\nconst raw = $json.value || '[]';\nlet timestamps = JSON.parse(raw);\ntimestamps = timestamps.filter(t => now - t < windowMs);\n\nif (timestamps.length >= limit) {\n  return [{ json: { _throttled: true } }];\n}\ntimestamps.push(now);\nreturn [{ json: { _throttled: false, timestamps, lead: $('Split Leads').item.json } }];"
      },
      "id": "wf02c-0005",
      "name": "Check Throttle",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "t",
              "leftValue": "={{ $json._throttled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "wf02c-0006",
      "name": "Is Throttled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {},
      "id": "wf02c-0007",
      "name": "Skip (throttled)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1560,
        180
      ]
    },
    {
      "parameters": {
        "jsCode": "const lead = $json.lead;\nconst rawScrape = lead.raw_scrape_data || {};\nconst texts = Object.values(rawScrape.raw_text_by_path || {});\nconst combined = texts.join('\\n').substring(0, 12000);\n\nconst systemPrompt = `You are a business analyst. Study this company and identify their single most painful manual workflow.\n\nCompany: ${lead.company_name}\nDomain: ${lead.domain}\nTeam size: ${lead.visible_team_size || 'unknown'} staff\nCountry: ${lead.country}\n\nWebsite content:\n${combined}\n\nThink broadly — this could be any business type: hotel, logistics company, law firm, dental practice, real estate agency, accounting firm, event venue, construction company, restaurant, etc.\n\nIdentify the ONE manual workflow that costs the most staff time. Look for:\n- Customer communication loops (follow-ups, confirmations, status updates)\n- Scheduling and booking handling\n- Document and data collection\n- Reporting and notifications\n- Anything described on the site that implies a human is doing it manually\n\nReturn ONLY a valid JSON object, no other text:\n{\n  \"industry_context\": \"1-2 sentences: what companies this size and type typically struggle with operationally\",\n  \"the_specific_workflow\": \"The one manual workflow that costs the most time at this specific company — be concrete and specific\",\n  \"evidence_from_their_site\": \"What on their site suggests this workflow is manual\",\n  \"time_cost_estimate\": \"Realistic estimate: X-Y hours per week across how many people\",\n  \"what_good_looks_like\": \"The automated version in 1-2 concrete sentences — specific actions, not vague outcomes\",\n  \"why_now\": \"Why fixing this makes business sense for them right now\",\n  \"curiosity_question\": \"One genuine question you could ask them about this workflow. Should sound like natural professional curiosity — not a sales opener. Under 25 words. No I noticed, no I saw.\",\n  \"one_pattern_match\": null\n}`;\n\nreturn [{ json: { groq_system: systemPrompt, groq_user: 'Analyze the company and return the JSON object only.', lead } }];"
      },
      "id": "wf02c-0008",
      "name": "Build 70B Research Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        420
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  model: 'llama-3.3-70b-versatile',\n  messages: [\n    { role: 'system', content: $json.groq_system },\n    { role: 'user', content: $json.groq_user }\n  ],\n  temperature: 0.2,\n  max_tokens: 1200,\n  response_format: { type: 'json_object' }\n}) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "wf02c-0009",
      "name": "Call Groq 70B",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1780,
        420
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $json;\nconst prev = $('Build 70B Research Prompt').item.json;\n\nlet artifact = {};\ntry {\n  artifact = JSON.parse(response.choices[0].message.content);\n} catch(e) {\n  artifact = { error: 'parse_failed', raw: response.choices?.[0]?.message?.content };\n}\n\n// Validate required fields\nconst required = ['industry_context','the_specific_workflow','evidence_from_their_site','time_cost_estimate','what_good_looks_like','why_now'];\nconst valid = required.every(k => artifact[k] && artifact[k].length > 0);\n\nreturn [{ json: { ...prev, research_artifact: artifact, artifact_valid: valid } }];"
      },
      "id": "wf02c-0010",
      "name": "Parse + Validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        420
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE leads.leads SET research_artifact = $1::jsonb, updated_at = NOW() WHERE id = $2",
        "additionalFields": {
          "queryParams": "={{ JSON.stringify([JSON.stringify($json.research_artifact), $json.lead.id]) }}"
        }
      },
      "id": "wf02c-0011",
      "name": "Store Research Artifact",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2220,
        420
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO leads.system_state (key, value) VALUES ('groq_rpm_window', $1) ON CONFLICT (key) DO UPDATE SET value = $1",
        "additionalFields": {
          "queryParams": "={{ JSON.stringify([JSON.stringify($('Check Throttle').item.json.timestamps)]) }}"
        }
      },
      "id": "wf02c-0012",
      "name": "Update RPM Counter",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2440,
        420
      ]
    }
  ],
  "connections": {
    "Every 15 Min": {
      "main": [
        [
          {
            "node": "Fetch Qualified No Research",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Qualified No Research": {
      "main": [
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Leads": {
      "main": [
        [],
        [
          {
            "node": "Read RPM Window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read RPM Window": {
      "main": [
        [
          {
            "node": "Check Throttle",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Throttle": {
      "main": [
        [
          {
            "node": "Is Throttled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Throttled?": {
      "main": [
        [
          {
            "node": "Skip (throttled)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build 70B Research Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build 70B Research Prompt": {
      "main": [
        [
          {
            "node": "Call Groq 70B",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Groq 70B": {
      "main": [
        [
          {
            "node": "Parse + Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse + Validate": {
      "main": [
        [
          {
            "node": "Store Research Artifact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Research Artifact": {
      "main": [
        [
          {
            "node": "Update RPM Counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update RPM Counter": {
      "main": [
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
