{
  "name": "Funnel Leak Detective",
  "nodes": [
    {
      "id": "829d7d59-495b-4ed6-b49d-1ff8477e5b46",
      "name": "Weekly Funnel Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [0, 300],
      "parameters": {
        "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * 1" }] }
      }
    },
    {
      "id": "a729bfea-0aac-4908-b4fe-87b0edf6e2e6",
      "name": "Fetch Recent Opportunities",
      "type": "n8n-nodes-base.salesforce",
      "typeVersion": 1.1,
      "position": [224, 300],
      "parameters": {
        "resource": "search",
        "operation": "query",
        "query": "SELECT Id, Name, Amount, StageName, CloseDate, Account.Name, Description FROM Opportunity WHERE StageName IN ('Closed Won','Closed Lost') AND Account.Name IN ('Solstice Underwriters','Brackenridge Manufacturing','Ashworth & Cole LLP','Cinderbrook Health Partners','Calloway Retail Group','Pinegate University','Thornbury Financial','Redwell Logistics','Harborstone Insurance','Fernhollow Health Systems','Kestrel Bay Capital','Oakmere Legal Partners')"
      },
      "credentials": { "salesforceOAuth2Api": { "id": "553Tae1kPe5Wpthq", "name": "Salesforce account" } }
    },
    {
      "id": "3ddf43ba-a1db-4d0f-8201-1bca29e28d2b",
      "name": "Compute Weekly Conversion + Stat Test",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [448, 300],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all().map(i => i.json);\n\nfunction weeksAgoFor(closeDateStr) {\n  const diffDays = Math.floor((Date.now() - new Date(closeDateStr).getTime()) / 86400000);\n  return Math.floor(diffDays / 7);\n}\n\nfunction erf(x) {\n  const sign = x < 0 ? -1 : 1;\n  x = Math.abs(x);\n  const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741, a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;\n  const t = 1 / (1 + p * x);\n  const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);\n  return sign * y;\n}\nfunction normalCdf(z) { return 0.5 * (1 + erf(z / Math.SQRT2)); }\n\nlet wonCurrent = 0, totalCurrent = 0, wonBase = 0, totalBase = 0;\nconst lostInWindow = [];\n\nfor (const opp of items) {\n  const wa = weeksAgoFor(opp.CloseDate);\n  if (wa > 7) continue;\n  const won = opp.StageName === 'Closed Won';\n  if (wa <= 1) {\n    totalCurrent++;\n    if (won) wonCurrent++;\n    else lostInWindow.push({ dealName: opp.Name, accountName: opp.Account ? opp.Account.Name : '', amount: opp.Amount, description: opp.Description });\n  } else {\n    totalBase++;\n    if (won) wonBase++;\n  }\n}\n\nconst pCurrent = totalCurrent > 0 ? wonCurrent / totalCurrent : 0;\nconst pBase = totalBase > 0 ? wonBase / totalBase : 0;\nconst pooled = (wonCurrent + wonBase) / (totalCurrent + totalBase);\nconst se = Math.sqrt(pooled * (1 - pooled) * (1 / totalCurrent + 1 / totalBase));\nconst z = se > 0 ? (pCurrent - pBase) / se : 0;\nconst pValue = 2 * (1 - normalCdf(Math.abs(z)));\nconst significant = pValue < 0.05 && pCurrent < pBase;\n\nconst dealSummaryText = lostInWindow.map(d => '- ' + d.dealName + ' (' + d.accountName + '), $' + d.amount + ': ' + d.description).join('\\n');\n\nconst today = new Date();\nconst windowStart = new Date(today.getTime() - 13 * 86400000).toISOString().slice(0, 10);\nconst windowEnd = today.toISOString().slice(0, 10);\n\nreturn [{\n  json: {\n    significant,\n    currentWinRate: Math.round(pCurrent * 1000) / 1000,\n    baselineWinRate: Math.round(pBase * 1000) / 1000,\n    dealsInWindow: totalCurrent,\n    dealsLostInWindow: totalCurrent - wonCurrent,\n    zScore: Math.round(z * 100) / 100,\n    pValue: Math.round(pValue * 10000) / 10000,\n    windowLabel: windowStart + ' to ' + windowEnd,\n    dealSummaryText: dealSummaryText || 'No lost deals in the current window.'\n  }\n}];"
      }
    },
    {
      "id": "e298220f-9e47-41c6-8bc5-6f9795e0c250",
      "name": "Significant Drop?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [672, 300],
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [{ "leftValue": "={{ $json.significant }}", "operator": { "type": "boolean", "operation": "equals" }, "rightValue": true }],
          "combinator": "and"
        }
      }
    },
    {
      "id": "db613a2a-fbbd-432a-a9b5-317e94d28f50",
      "name": "Root Cause Hypothesis Drafter",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [896, 200],
      "parameters": {
        "promptType": "define",
        "text": "=A statistical check flagged a real drop in Salesforce win rate.\n\nBaseline win rate (trailing 6 weeks): {{ $json.baselineWinRate * 100 }}%\nCurrent window win rate (last 2 weeks): {{ $json.currentWinRate * 100 }}%\nz-score: {{ $json.zScore }}, p-value: {{ $json.pValue }}\nDeals in current window: {{ $json.dealsInWindow }} ({{ $json.dealsLostInWindow }} lost)\n\nLoss-reason notes from the Closed Lost deals in the current window:\n{{ $json.dealSummaryText }}\n\nDraft the root-cause hypothesis and recommended action.",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are a GTM analytics assistant for Fortavault, Inc., a fictional data protection and security platform (cloud backup, ransomware recovery, zero-trust access, compliance reporting). Products: Fortavault Backup Cloud, Fortavault Shield (ransomware detection + one-click rollback), Fortavault Access (zero-trust), Fortavault Insights (compliance reporting). Key competitors: Veyron Data, Ashcombe Systems, Cyphertide, Northgate Cloud Security, Ridgeline Compliance.\n\nA statistical check has flagged a real drop in Salesforce win rate for opportunities closing in the last 2 weeks compared to the trailing 6-week baseline. You are given the aggregate stats and the actual loss-reason notes from the Closed Lost opportunities in the flagged window. Read the notes for a real pattern - do not guess generically. If multiple notes cite the same specific cause, name it explicitly and quantify how many of the losses it explains. Write a plain-English root-cause hypothesis (3-4 sentences) a RevOps leader could act on, plus a specific, concrete recommended next action.\n\nRespond with a JSON object with exactly two fields: hypothesis (string) and recommendedAction (string)."
        }
      }
    },
    {
      "id": "73035033-b142-44b3-9fbe-0651fc548eee",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [896, 552],
      "parameters": {
        "model": { "__rl": true, "mode": "list", "value": "claude-sonnet-4-6", "cachedResultName": "Claude Sonnet 4.6" },
        "options": { "temperature": 0.4 }
      },
      "credentials": { "anthropicApi": { "id": "3d3ZiTi7BwnkFwqU", "name": "Anthropic account" } }
    },
    {
      "id": "fe571a56-f02a-463e-9cf2-fb5c78f964a4",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [1088, 552],
      "parameters": {
        "jsonSchemaExample": "{ \"hypothesis\": \"3-4 sentence plain-English root cause hypothesis\", \"recommendedAction\": \"one specific next action\" }",
        "autoFix": false
      }
    },
    {
      "id": "3ced29d2-51b6-4864-8d7e-72255a7da777",
      "name": "Post Findings to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [1120, 200],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": { "__rl": true, "mode": "id", "value": "C0BQ3G96HL6" },
        "text": "=📉 *Funnel leak detected* ({{ $('Compute Weekly Conversion + Stat Test').item.json.windowLabel }})\n\nWin rate: {{ Math.round($('Compute Weekly Conversion + Stat Test').item.json.baselineWinRate * 100) }}% baseline -> {{ Math.round($('Compute Weekly Conversion + Stat Test').item.json.currentWinRate * 100) }}% this window (z = {{ $('Compute Weekly Conversion + Stat Test').item.json.zScore }}, p = {{ $('Compute Weekly Conversion + Stat Test').item.json.pValue }})\n{{ $('Compute Weekly Conversion + Stat Test').item.json.dealsLostInWindow }} of {{ $('Compute Weekly Conversion + Stat Test').item.json.dealsInWindow }} deals in the window were lost.\n\n*Root cause hypothesis:*\n{{ $json.output.hypothesis }}\n\n*Recommended action:*\n{{ $json.output.recommendedAction }}",
        "otherOptions": {}
      },
      "webhookId": "fba48201-b622-4ad5-8c89-af680e4dfce0",
      "credentials": { "slackApi": { "id": "mv7TIi8kucFPqPg6", "name": "Slack account" } }
    },
    {
      "id": "5007b564-643e-4e49-8106-1fe1bd6e44a8",
      "name": "Log Funnel Leak",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [1344, 200],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": { "__rl": true, "mode": "id", "value": "0vpYgWnWs0MRwXM0", "cachedResultName": "Funnel Leak Log" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "windowLabel": "={{ $('Compute Weekly Conversion + Stat Test').item.json.windowLabel }}",
            "currentWinRate": "={{ $('Compute Weekly Conversion + Stat Test').item.json.currentWinRate }}",
            "baselineWinRate": "={{ $('Compute Weekly Conversion + Stat Test').item.json.baselineWinRate }}",
            "dealsInWindow": "={{ $('Compute Weekly Conversion + Stat Test').item.json.dealsInWindow }}",
            "dealsLostInWindow": "={{ $('Compute Weekly Conversion + Stat Test').item.json.dealsLostInWindow }}",
            "zScore": "={{ $('Compute Weekly Conversion + Stat Test').item.json.zScore }}",
            "pValue": "={{ $('Compute Weekly Conversion + Stat Test').item.json.pValue }}",
            "hypothesis": "={{ $('Root Cause Hypothesis Drafter').item.json.output.hypothesis }}",
            "recommendedAction": "={{ $('Root Cause Hypothesis Drafter').item.json.output.recommendedAction }}",
            "flaggedAt": "={{ $now.toISO() }}"
          }
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Weekly Funnel Check": { "main": [[{ "node": "Fetch Recent Opportunities", "type": "main", "index": 0 }]] },
    "Fetch Recent Opportunities": { "main": [[{ "node": "Compute Weekly Conversion + Stat Test", "type": "main", "index": 0 }]] },
    "Compute Weekly Conversion + Stat Test": { "main": [[{ "node": "Significant Drop?", "type": "main", "index": 0 }]] },
    "Significant Drop?": { "main": [[{ "node": "Root Cause Hypothesis Drafter", "type": "main", "index": 0 }]] },
    "Root Cause Hypothesis Drafter": { "main": [[{ "node": "Post Findings to Slack", "type": "main", "index": 0 }]] },
    "Anthropic Chat Model": { "ai_languageModel": [[{ "node": "Root Cause Hypothesis Drafter", "type": "ai_languageModel", "index": 0 }]] },
    "Structured Output Parser": { "ai_outputParser": [[{ "node": "Root Cause Hypothesis Drafter", "type": "ai_outputParser", "index": 0 }]] },
    "Post Findings to Slack": { "main": [[{ "node": "Log Funnel Leak", "type": "main", "index": 0 }]] }
  },
  "pinData": {},
  "meta": {
    "instanceId": "fortavault-gtm-sprint-day5"
  }
}
