{
  "name": "Event ROI Time Machine",
  "nodes": [
    {
      "id": "0276645f-7eaf-4f2b-8990-ac501eacb553",
      "name": "Weekly Event ROI Scan",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [0, 112],
      "parameters": {
        "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * 1" }] }
      }
    },
    {
      "id": "3ce6a123-d93c-4ba9-b280-a90202c24a0e",
      "name": "Fetch Closed Won Event Opportunities",
      "type": "n8n-nodes-base.salesforce",
      "typeVersion": 1.1,
      "position": [224, 112],
      "parameters": {
        "resource": "search",
        "operation": "query",
        "query": "SELECT Id, Name, Amount, CloseDate, StageName, Description, Account.Name FROM Opportunity WHERE StageName = 'Closed Won' AND Account.Name IN ('Meridian Fleet Systems','Alderway Insurance Group','Brackfield Municipal Services','Wexford Legal Group','Sablewood Retail Co','Ferrowyck Manufacturing','Callisto Financial Partners')"
      },
      "credentials": { "salesforceOAuth2Api": { "id": "553Tae1kPe5Wpthq", "name": "Salesforce account" } }
    },
    {
      "id": "38965197-8732-429b-8eb4-dd75d66e97f8",
      "name": "Compute Event ROI + Time-to-Close",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [448, 112],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all().map(i => i.json);\n\nconst eventCosts = {\n  'SecureOps Summit 2025': 18000,\n  'CloudGuard Conference 2025': 12000,\n  'DataProtect World 2026': 25000\n};\n\nconst eventMap = {};\n\nfor (const opp of items) {\n  const desc = opp.Description || '';\n  const m = desc.match(/Event Source:\\s*(.+?)\\s*\\|\\s*Badge Scan Date:\\s*(\\d{4}-\\d{2}-\\d{2})/);\n  if (!m) continue;\n  const eventName = m[1].trim();\n  const badgeScanDate = m[2].trim();\n  const closeDate = opp.CloseDate;\n  const daysToClose = Math.round((new Date(closeDate).getTime() - new Date(badgeScanDate).getTime()) / 86400000);\n  const amount = opp.Amount || 0;\n\n  if (!eventMap[eventName]) {\n    eventMap[eventName] = { eventName, badgeScanDate, cost: eventCosts[eventName] || 15000, dealCount: 0, totalRevenue: 0, daysList: [], deals: [] };\n  }\n  const e = eventMap[eventName];\n  e.dealCount++;\n  e.totalRevenue += amount;\n  e.daysList.push(daysToClose);\n  e.deals.push({ name: opp.Name, account: opp.Account ? opp.Account.Name : '', amount, daysToClose });\n}\n\nconst events = Object.values(eventMap).map(e => {\n  const avg = Math.round(e.daysList.reduce((a,b) => a+b, 0) / e.daysList.length);\n  const min = Math.min(...e.daysList);\n  const max = Math.max(...e.daysList);\n  const roiPct = Math.round(((e.totalRevenue - e.cost) / e.cost) * 1000) / 10;\n  const dealsText = e.deals.map(d => '   - ' + d.name + ' (' + d.account + '): $' + d.amount.toLocaleString() + ', closed ' + d.daysToClose + ' days after badge scan').join('\\n');\n  return {\n    eventName: e.eventName,\n    badgeScanDate: e.badgeScanDate,\n    eventCost: e.cost,\n    dealCount: e.dealCount,\n    totalRevenue: e.totalRevenue,\n    roiPct,\n    avgTimeToCloseDays: avg,\n    minTimeToCloseDays: min,\n    maxTimeToCloseDays: max,\n    dealsText\n  };\n});\n\nevents.sort((a,b) => new Date(a.badgeScanDate) - new Date(b.badgeScanDate));\n\nconst totalRevenue = events.reduce((s,e) => s + e.totalRevenue, 0);\nconst totalCost = events.reduce((s,e) => s + e.eventCost, 0);\nconst overallRoiPct = totalCost > 0 ? Math.round(((totalRevenue - totalCost) / totalCost) * 1000) / 10 : 0;\nconst dealCount = events.reduce((s,e) => s + e.dealCount, 0);\n\nconst eventsText = events.map(e => 'Event: ' + e.eventName + ' (badge scans began ' + e.badgeScanDate + ')\\n  Cost: $' + e.eventCost.toLocaleString() + '\\n  Closed Won deals: ' + e.dealCount + ', total revenue: $' + e.totalRevenue.toLocaleString() + '\\n  ROI: ' + e.roiPct + '%\\n  Time-to-close: avg ' + e.avgTimeToCloseDays + 'd (range ' + e.minTimeToCloseDays + '-' + e.maxTimeToCloseDays + 'd)\\n  Deals:\\n' + e.dealsText).join('\\n\\n');\n\nconst eventBreakdownText = events.map(e => '• *' + e.eventName + '*: ' + e.dealCount + ' deals, $' + e.totalRevenue.toLocaleString() + ' revenue vs $' + e.eventCost.toLocaleString() + ' cost → *' + e.roiPct + '% ROI*, avg ' + e.avgTimeToCloseDays + 'd to close (range ' + e.minTimeToCloseDays + '-' + e.maxTimeToCloseDays + 'd)').join('\\n');\n\nreturn [{\n  json: {\n    events,\n    eventsText,\n    eventBreakdownText,\n    totalRevenue,\n    totalCost,\n    overallRoiPct,\n    dealCount,\n    eventNames: events.map(e => e.eventName),\n    generatedAt: new Date().toISOString()\n  }\n}];"
      }
    },
    {
      "id": "8bd77552-881e-4ff2-8b5f-6a772ac10747",
      "name": "ROI Storyteller",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [672, 112],
      "parameters": {
        "promptType": "define",
        "text": "=Event ROI Time Machine retroactively ties Salesforce badge-scan leads to revenue as deals close months later. Here is the real, computed data across all Closed Won opportunities originally sourced from an event:\n\n{{ $json.eventsText }}\n\nOverall: {{ $json.dealCount }} closed-won deals across {{ $json.events.length }} events, ${{ $json.totalRevenue }} total revenue against ${{ $json.totalCost }} total event cost ({{ $json.overallRoiPct }}% blended ROI).\n\nDraft a plain-English summary of what actually paid off. Compare the events directly - which had the best ROI, which took the longest to pay off, and whether deal size or speed-to-close explains the difference. Do not just restate the numbers - explain what they mean for where to invest event budget next.\n\nProvide one insight per event for exactly these events (match spelling exactly): {{ $json.eventNames.join(', ') }}",
        "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). You are given real, computed Salesforce data tying event badge-scan leads to revenue realized months later - this is retroactive event ROI, not a same-day estimate. Read the numbers for a real pattern - do not guess generically. Respond with a JSON object with exactly three fields: headline (one sentence), narrative (4-6 sentences comparing the events and ending in a concrete recommendation for where to invest event budget next), and eventInsights (an array of objects, each with eventName and insight, exactly one entry per event listed, insight being one specific sentence grounded in that event's actual numbers)."
        }
      }
    },
    {
      "id": "6dde1217-8457-42c8-9deb-41f17aec2a8a",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [680, 344],
      "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": "e7f2b2e8-be97-4b03-aa46-b4cf7468196d",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [808, 344],
      "parameters": {
        "jsonSchemaExample": "{ \"headline\": \"one-sentence headline about what actually paid off\", \"narrative\": \"4-6 sentence plain-English comparison across events with a concrete recommendation\", \"eventInsights\": [ { \"eventName\": \"Event Name\", \"insight\": \"one specific sentence about this event ROI story\" } ] }"
      }
    },
    {
      "id": "83eac6ce-1bac-4ff3-b438-e965d9b90e0f",
      "name": "Merge ROI + Insights",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1024, 112],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const compute = $('Compute Event ROI + Time-to-Close').item.json;\nconst output = $json.output;\nconst insights = (output && output.eventInsights) || [];\n\nreturn compute.events.map(e => {\n  const found = insights.find(i => i.eventName === e.eventName);\n  return { json: {\n    eventName: e.eventName,\n    badgeScanDate: e.badgeScanDate,\n    dealCount: e.dealCount,\n    totalRevenue: e.totalRevenue,\n    eventCost: e.eventCost,\n    roiPct: e.roiPct,\n    avgTimeToCloseDays: e.avgTimeToCloseDays,\n    note: found ? found.insight : '',\n    loggedAt: new Date().toISOString()\n  }};\n});"
      }
    },
    {
      "id": "04711b49-7474-4470-a34d-adf3f4f25033",
      "name": "Log Event ROI",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [1248, 112],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": { "__rl": true, "mode": "id", "value": "5YzsW9rRHhz48Pm3", "cachedResultName": "Event ROI Log" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "eventName": "={{ $json.eventName }}",
            "badgeScanDate": "={{ $json.badgeScanDate }}",
            "dealCount": "={{ $json.dealCount }}",
            "totalRevenue": "={{ $json.totalRevenue }}",
            "eventCost": "={{ $json.eventCost }}",
            "roiPct": "={{ $json.roiPct }}",
            "avgTimeToCloseDays": "={{ $json.avgTimeToCloseDays }}",
            "note": "={{ $json.note }}",
            "loggedAt": "={{ $json.loggedAt }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "33cef0fb-2e7a-4284-81f6-5bf786afc97e",
      "name": "Post ROI Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [1472, 112],
      "executeOnce": true,
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": { "__rl": true, "mode": "id", "value": "C0BQ3G96HL6" },
        "text": "=🎟️ *Event ROI Time Machine* — {{ $('Compute Event ROI + Time-to-Close').item.json.dealCount }} closed-won deals across {{ $('Compute Event ROI + Time-to-Close').item.json.events.length }} events\n\n*{{ $('ROI Storyteller').item.json.output.headline }}*\n\n{{ $('ROI Storyteller').item.json.output.narrative }}\n\n*Event breakdown:*\n{{ $('Compute Event ROI + Time-to-Close').item.json.eventBreakdownText }}\n\nBlended: ${{ $('Compute Event ROI + Time-to-Close').item.json.totalRevenue }} revenue vs ${{ $('Compute Event ROI + Time-to-Close').item.json.totalCost }} cost ({{ $('Compute Event ROI + Time-to-Close').item.json.overallRoiPct }}% ROI)",
        "otherOptions": {}
      },
      "webhookId": "3f9f5fbb-1cbc-414b-bad4-c375a06a92c2",
      "credentials": { "slackApi": { "id": "mv7TIi8kucFPqPg6", "name": "Slack account" } }
    }
  ],
  "connections": {
    "Weekly Event ROI Scan": { "main": [[{ "node": "Fetch Closed Won Event Opportunities", "type": "main", "index": 0 }]] },
    "Fetch Closed Won Event Opportunities": { "main": [[{ "node": "Compute Event ROI + Time-to-Close", "type": "main", "index": 0 }]] },
    "Compute Event ROI + Time-to-Close": { "main": [[{ "node": "ROI Storyteller", "type": "main", "index": 0 }]] },
    "ROI Storyteller": { "main": [[{ "node": "Merge ROI + Insights", "type": "main", "index": 0 }]] },
    "Anthropic Chat Model": { "ai_languageModel": [[{ "node": "ROI Storyteller", "type": "ai_languageModel", "index": 0 }]] },
    "Structured Output Parser": { "ai_outputParser": [[{ "node": "ROI Storyteller", "type": "ai_outputParser", "index": 0 }]] },
    "Merge ROI + Insights": { "main": [[{ "node": "Log Event ROI", "type": "main", "index": 0 }]] },
    "Log Event ROI": { "main": [[{ "node": "Post ROI Summary to Slack", "type": "main", "index": 0 }]] }
  },
  "pinData": {},
  "meta": {
    "instanceId": "fortavault-gtm-sprint-day7"
  }
}
