{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "bb12023f-1d7b-49d2-aa19-45de5090ccff",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5008,
        -384
      ],
      "parameters": {
        "color": 4,
        "width": 588,
        "height": 1264,
        "content": "## Client Sentiment Monitor \u2014 Fireflies + GPT-4o-mini + Sheets + Slack\n\nFor account managers, customer success teams, and agency leads who want automatic weekly client health monitoring based on real meeting conversations. Every Monday the workflow pulls the last 7 days of Fireflies transcripts, extracts the built-in Fireflies NLP sentiment scores per meeting, then sends each transcript to GPT-4o-mini for deeper contextual analysis \u2014 explaining WHY the sentiment was positive or negative, scoring churn risk, extracting a key quote, and recommending one concrete action. Every meeting is logged to Google Sheets. Negative or high-risk meetings trigger an immediate Slack alert to a dedicated urgent channel.\n\n## How it works\n- **1. Schedule \u2014 Every Monday 9AM** triggers the workflow each week\n- **2. Set \u2014 Config Values** stores Fireflies API key, Sheet ID, both Slack channels, and company name\n- **3. HTTP \u2014 Fetch Last 50 Transcripts** pulls the 50 most recent transcripts from Fireflies\n- **4. Code \u2014 Filter Last 7 Days** filters to the past 7 days and outputs one item per transcript\n- **5. IF \u2014 Meetings Found?** stops cleanly if no meetings were recorded this week\n- **7. HTTP \u2014 Fetch Full Transcript** fetches complete transcript data per meeting including sentiment analytics\n- **8. Code \u2014 Extract Sentiment Data** extracts Fireflies NLP scores, computes a 0\u2013100 score, assigns a label, and extracts client name from the meeting title\n- **9. AI Agent \u2014 Sentiment Analysis** uses GPT-4o-mini to add gptAnalysis, churnRisk, recommendedAction, and keyQuote\n- **12. Code \u2014 Combine Analysis Results** adds emoji flags and determines if an alert is needed\n- **13. Google Sheets \u2014 Log Client Sentiment** appends one row per meeting to the tracker\n- **14. IF \u2014 Needs Urgent Alert?** checks if sentiment is Negative or churn risk is High or Critical\n- **15. Slack \u2014 Send Urgent Alert** fires immediately for each at-risk meeting to the alert channel\n\n## Set up steps\n1. In **2. Set \u2014 Config Values** \u2014 replace YOUR_FIREFLIES_API_KEY, YOUR_GOOGLE_SHEET_ID, sheet tab name, both Slack channel names, and your company name\n2. In **10. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **13. Google Sheets \u2014 Log Client Sentiment** \u2014 connect your Google Sheets OAuth2 credential\n4. In **15. Slack \u2014 Send Urgent Alert** \u2014 connect your Slack OAuth2 credential and invite the bot to both channels\n5. Create a Google Sheet tab named Client Sentiment Log with columns: Week, Meeting Date, Meeting Title, Client Name, Duration, Participants, Sentiment Score, Sentiment Label, Positive %, Negative %, GPT Analysis, Churn Risk, Recommended Action, Fireflies URL, Logged At\n6. Activate the workflow \u2014 it runs every Monday at 9AM automatically"
      },
      "typeVersion": 1
    },
    {
      "id": "27065b92-7b96-4613-a9fb-fa56a5b4952b",
      "name": "Section \u2014 Schedule, Config, and Transcript Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4384,
        -80
      ],
      "parameters": {
        "color": 5,
        "width": 660,
        "height": 516,
        "content": "## Schedule, Config, and Transcript List Fetch\nWorkflow triggers every Monday at 9AM. Config stores all credentials and channels. HTTP fetches the 50 most recent Fireflies transcripts for filtering."
      },
      "typeVersion": 1
    },
    {
      "id": "fbfbaeb2-e591-4548-a590-892bbe097625",
      "name": "Section \u2014 7-Day Filter and Gate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3696,
        -192
      ],
      "parameters": {
        "color": 6,
        "width": 484,
        "height": 788,
        "content": "## 7-Day Filter and Gate\nFilters the 50 transcripts to the past 7 days only. Outputs one item per transcript. If no meetings found this week, exits cleanly via the Set node below."
      },
      "typeVersion": 1
    },
    {
      "id": "6d4d556f-c42c-4848-8137-26f4c2708da5",
      "name": "Section \u2014 Full Transcript Fetch and Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3168,
        -96
      ],
      "parameters": {
        "color": 6,
        "width": 420,
        "height": 516,
        "content": "## Full Transcript Fetch and Processing\nFetches complete transcript details per meeting including sentences, summary, and Fireflies NLP sentiment scores. Extracts a weighted sentiment score, label, client name, and 4000-char transcript excerpt."
      },
      "typeVersion": 1
    },
    {
      "id": "6b78580f-bf25-4b9c-b30a-0f0703ce4575",
      "name": "Section \u2014 AI Sentiment Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2704,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 324,
        "height": 996,
        "content": "## AI Sentiment Analysis\nGPT-4o-mini adds four contextual intelligence fields: a 2\u20133 sentence explanation of WHY the sentiment was positive or negative, a churn risk score (Low/Medium/High/Critical), a concrete recommended action, and the most important quote from the meeting."
      },
      "typeVersion": 1
    },
    {
      "id": "6c72be71-f035-44bc-bfb2-45f02d9dec6b",
      "name": "Section \u2014 Results Assembly, Logging, and Alert Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2320,
        -240
      ],
      "parameters": {
        "color": 4,
        "width": 1044,
        "height": 740,
        "content": "## Results Assembly, Logging, and Alert Routing\nMerges AI output with meeting data and adds emoji flags. Google Sheets logs every meeting. The IF node checks if sentiment is Negative or churn risk is High or Critical \u2014 if so, fires an immediate Slack alert to the urgent channel."
      },
      "typeVersion": 1
    },
    {
      "id": "3c29e0e4-b444-48d3-b924-1d2c78184d2a",
      "name": "1. Schedule \u2014 Every Monday 9AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -4336,
        96
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3b84778f-c8d1-4a58-835a-38c6d76f3023",
      "name": "2. Set \u2014 Config Values",
      "type": "n8n-nodes-base.set",
      "position": [
        -4112,
        96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "firefliesApiKey",
              "type": "string",
              "value": "YOUR_FIREFLIES_API_KEY"
            },
            {
              "id": "cfg-002",
              "name": "sheetId",
              "type": "string",
              "value": "YOUR_GOOGLE_SHEET_ID"
            },
            {
              "id": "cfg-003",
              "name": "sheetName",
              "type": "string",
              "value": "Client Sentiment Log"
            },
            {
              "id": "cfg-004",
              "name": "slackChannel",
              "type": "string",
              "value": "#client-health"
            },
            {
              "id": "cfg-005",
              "name": "slackAlertChannel",
              "type": "string",
              "value": "#urgent-alerts"
            },
            {
              "id": "cfg-006",
              "name": "companyName",
              "type": "string",
              "value": "YOUR COMPANY NAME"
            },
            {
              "id": "cfg-007",
              "name": "weekStart",
              "type": "string",
              "value": "={{ $now.minus({days: 7}).toFormat('dd MMM yyyy') }}"
            },
            {
              "id": "cfg-008",
              "name": "weekEnd",
              "type": "string",
              "value": "={{ $now.toFormat('dd MMM yyyy') }}"
            },
            {
              "id": "cfg-009",
              "name": "sevenDaysAgoMs",
              "type": "number",
              "value": "={{ $now.minus({days: 7}).toMillis() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f01db6a3-3538-45e0-9446-ea8f9a78812c",
      "name": "3. HTTP \u2014 Fetch Last 50 Transcripts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -3888,
        96
      ],
      "parameters": {
        "url": "https://api.fireflies.ai/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"query GetRecentTranscripts { transcripts(limit: 50) { id title date duration participants host_email transcript_url } }\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.firefliesApiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3ba46225-f9b1-4b64-b815-1d4e90c56dc7",
      "name": "4. Code \u2014 Filter Last 7 Days",
      "type": "n8n-nodes-base.code",
      "position": [
        -3664,
        96
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst config = $('2. Set \u2014 Config Values').item.json;\n\nconst allTranscripts = response?.data?.transcripts || [];\n\nif (allTranscripts.length === 0) {\n  return [{\n    json: {\n      hasTranscripts: false,\n      message: 'No transcripts in Fireflies account',\n      weekStart: config.weekStart,\n      weekEnd: config.weekEnd,\n      slackChannel: config.slackChannel,\n      companyName: config.companyName\n    }\n  }];\n}\n\n// Filter last 7 days\nconst sevenDaysAgo = config.sevenDaysAgoMs;\nconst recent = allTranscripts.filter(t => (t.date || 0) >= sevenDaysAgo);\n\nif (recent.length === 0) {\n  return [{\n    json: {\n      hasTranscripts: false,\n      message: 'No meetings in last 7 days',\n      weekStart: config.weekStart,\n      weekEnd: config.weekEnd,\n      slackChannel: config.slackChannel,\n      companyName: config.companyName\n    }\n  }];\n}\n\n// Return one item per transcript for loop processing\nreturn recent.map(t => ({\n  json: {\n    hasTranscripts: true,\n    transcriptId: t.id,\n    transcriptTitle: t.title || 'Untitled Meeting',\n    transcriptDate: t.date\n      ? new Date(t.date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })\n      : 'Unknown',\n    durationMin: Math.round((t.duration || 0) / 60),\n    participants: (t.participants || []).join(', ') || 'Unknown',\n    transcriptUrl: t.transcript_url || 'Not available',\n    totalMeetings: recent.length,\n    firefliesApiKey: config.firefliesApiKey,\n    sheetId: config.sheetId,\n    sheetName: config.sheetName,\n    slackChannel: config.slackChannel,\n    slackAlertChannel: config.slackAlertChannel,\n    companyName: config.companyName,\n    weekStart: config.weekStart,\n    weekEnd: config.weekEnd\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "f5e8a749-e8aa-4bf2-b113-748cfdc5a391",
      "name": "5. IF \u2014 Meetings Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3408,
        96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-001",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.hasTranscripts }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8a56848a-4eca-4ebb-9922-9944fe662790",
      "name": "6. Set \u2014 No Meetings This Week",
      "type": "n8n-nodes-base.set",
      "position": [
        -3408,
        336
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "no-mtg-001",
              "name": "result",
              "type": "string",
              "value": "=No meetings found this week. Nothing to analyze."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0ccdbe30-faa5-4028-abf0-5d9fcdb7e0a7",
      "name": "7. HTTP \u2014 Fetch Full Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -3104,
        80
      ],
      "parameters": {
        "url": "https://api.fireflies.ai/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"query GetTranscript($id: String!) { transcript(id: $id) { id title date duration participants transcript_url sentences { speaker_name text } summary { overview keywords action_items } analytics { sentiments { positive_pct neutral_pct negative_pct } } } }\",\n  \"variables\": {\n    \"id\": \"{{ $json.transcriptId }}\"\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.firefliesApiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3276f97d-58f7-4927-b0f1-44ccddc42235",
      "name": "8. Code \u2014 Extract Sentiment Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -2880,
        80
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst prev = $('4. Code \u2014 Filter Last 7 Days').item.json;\n\nconst transcript = response?.data?.transcript;\nif (!transcript || !transcript.id) {\n  return [{\n    json: {\n      hasData: false,\n      transcriptId: prev.transcriptId,\n      transcriptTitle: prev.transcriptTitle,\n      sheetId: prev.sheetId,\n      sheetName: prev.sheetName,\n      slackChannel: prev.slackChannel,\n      slackAlertChannel: prev.slackAlertChannel,\n      companyName: prev.companyName,\n      weekStart: prev.weekStart,\n      weekEnd: prev.weekEnd,\n      totalMeetings: prev.totalMeetings\n    }\n  }];\n}\n\n// Build readable transcript excerpt (max 4000 chars)\nconst sentences = transcript.sentences || [];\nlet transcriptText = '';\nsentences.forEach(s => {\n  const speaker = s.speaker_name || 'Speaker';\n  const text = (s.text || '').trim();\n  if (text) transcriptText += speaker + ': ' + text + '\\n';\n});\nconst transcriptExcerpt = transcriptText.substring(0, 4000);\n\n// Fireflies built-in sentiment scores\nconst sentiments = transcript.analytics?.sentiments || {};\nconst positivePct = Math.round(sentiments.positive_pct || 0);\nconst neutralPct = Math.round(sentiments.neutral_pct || 0);\nconst negativePct = Math.round(sentiments.negative_pct || 0);\n\n// Compute composite sentiment score (0-100)\nconst sentimentScore = Math.round((positivePct * 1) + (neutralPct * 0.5) - (negativePct * 0.5));\n\n// Determine label from Fireflies scores\nlet sentimentLabel = 'Neutral';\nif (positivePct >= 60) sentimentLabel = 'Positive';\nelse if (negativePct >= 35) sentimentLabel = 'Negative';\nelse if (positivePct >= 45) sentimentLabel = 'Mostly Positive';\nelse if (negativePct >= 25) sentimentLabel = 'Mostly Negative';\n\n// Extract client name from meeting title\nconst title = transcript.title || 'Untitled Meeting';\nlet clientName = 'Unknown Client';\nconst patterns = [\n  /call with (.+?)(?:\\s*[-\u2013|]|$)/i,\n  /(.+?)\\s*(?:call|meeting|review|sync|check.?in|catchup|catch.?up)/i,\n  /(.+?)\\s*[-\u2013|]/i\n];\nfor (const pattern of patterns) {\n  const match = title.match(pattern);\n  if (match && match[1] && match[1].trim().length > 1) {\n    clientName = match[1].trim();\n    break;\n  }\n}\nif (clientName === 'Unknown Client' && title.length < 40) {\n  clientName = title;\n}\n\nconst summary = transcript.summary || {};\nconst overview = (summary.overview || '').substring(0, 600);\nconst keywords = (summary.keywords || []).slice(0, 8).join(', ');\nconst actionItems = (summary.action_items || []).slice(0, 5).join(' | ');\n\nconst meetingDate = transcript.date\n  ? new Date(transcript.date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })\n  : prev.transcriptDate;\n\nreturn [{\n  json: {\n    hasData: true,\n    transcriptId: transcript.id,\n    transcriptTitle: title,\n    clientName,\n    meetingDate,\n    durationMin: Math.round((transcript.duration || 0) / 60) || prev.durationMin,\n    participants: (transcript.participants || []).join(', ') || prev.participants,\n    transcriptUrl: transcript.transcript_url || prev.transcriptUrl,\n    transcriptExcerpt,\n    overview,\n    keywords,\n    actionItems,\n    positivePct,\n    neutralPct,\n    negativePct,\n    sentimentScore,\n    sentimentLabel,\n    sheetId: prev.sheetId,\n    sheetName: prev.sheetName,\n    slackChannel: prev.slackChannel,\n    slackAlertChannel: prev.slackAlertChannel,\n    companyName: prev.companyName,\n    weekStart: prev.weekStart,\n    weekEnd: prev.weekEnd,\n    totalMeetings: prev.totalMeetings\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "707f0678-8672-4bd5-b811-61f714b45f6e",
      "name": "9. AI Agent \u2014 Sentiment Analysis",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -2656,
        80
      ],
      "parameters": {
        "text": "=You are a client relationship analyst at {{ $json.companyName }}.\n\nAnalyze this client meeting and provide intelligence about client health and relationship status.\n\nMEETING DETAILS:\nClient: {{ $json.clientName }}\nMeeting Title: {{ $json.transcriptTitle }}\nDate: {{ $json.meetingDate }}\nDuration: {{ $json.durationMin }} minutes\nParticipants: {{ $json.participants }}\n\nFIREFLIES SENTIMENT SCORES:\nPositive: {{ $json.positivePct }}%\nNeutral: {{ $json.neutralPct }}%\nNegative: {{ $json.negativePct }}%\nOverall Label: {{ $json.sentimentLabel }}\n\nMEETING OVERVIEW:\n{{ $json.overview }}\n\nKEYWORDS: {{ $json.keywords }}\n\nACTION ITEMS: {{ $json.actionItems }}\n\nTRANSCRIPT EXCERPT:\n{{ $json.transcriptExcerpt }}\n\nReturn ONLY a valid JSON object with exactly these 4 fields. No extra text. No markdown. No backticks.\n\ngptAnalysis \u2014 2 to 3 plain text sentences explaining WHY this meeting had this sentiment. What specifically was discussed that was positive, neutral, or negative? Reference actual content from the transcript. Under 80 words.\n\nchurnRisk \u2014 One of exactly these values: Low OR Medium OR High OR Critical\nLow = client is happy, engaged, expanding\nMedium = some concerns raised but relationship is stable\nHigh = clear frustration, unresolved issues, or signs of disengagement\nCritical = client threatened to leave, expressed serious anger, or is clearly about to churn\n\nrecommendedAction \u2014 One specific action the account manager should take within 48 hours. Be concrete. Start with a verb. Under 20 words. Plain text only.\n\nkeyQuote \u2014 The single most important sentence from this meeting \u2014 positive or negative \u2014 that best captures the client mood. Under 25 words. Plain text. If no clear quote found write: No standout quote identified.",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "6ada0272-3981-4bfc-8857-d2cafce94e81",
      "name": "10. OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -2656,
        272
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 500,
          "temperature": 0.3
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "8ecb4683-6647-4d99-a3ae-45e1fe826cd0",
      "name": "11. Parser \u2014 Structured Analysis Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -2512,
        448
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"gptAnalysis\": {\n      \"type\": \"string\",\n      \"description\": \"2-3 sentences explaining WHY this meeting had this sentiment. Under 80 words.\"\n    },\n    \"churnRisk\": {\n      \"type\": \"string\",\n      \"description\": \"Exactly one of: Low, Medium, High, Critical\"\n    },\n    \"recommendedAction\": {\n      \"type\": \"string\",\n      \"description\": \"One specific action starting with a verb. Under 20 words.\"\n    },\n    \"keyQuote\": {\n      \"type\": \"string\",\n      \"description\": \"Most important sentence from the meeting under 25 words.\"\n    }\n  },\n  \"required\": [\"gptAnalysis\", \"churnRisk\", \"recommendedAction\", \"keyQuote\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "28a3f64c-0241-4b77-a143-d1e3e01a89f3",
      "name": "12. Code \u2014 Combine Analysis Results",
      "type": "n8n-nodes-base.code",
      "position": [
        -2272,
        80
      ],
      "parameters": {
        "jsCode": "// Combine AI output with meeting data for Sheets + Slack\nconst aiOutput = $input.first().json.output;\nconst meetingData = $('8. Code \u2014 Extract Sentiment Data').item.json;\n\nconst gptAnalysis = aiOutput?.gptAnalysis || 'Analysis not available';\nconst churnRisk = aiOutput?.churnRisk || 'Unknown';\nconst recommendedAction = aiOutput?.recommendedAction || 'Review meeting manually';\nconst keyQuote = aiOutput?.keyQuote || 'No standout quote identified';\n\n// Determine if this needs an urgent Slack alert\nconst isNegative = ['Negative', 'Mostly Negative'].includes(meetingData.sentimentLabel);\nconst isHighRisk = ['High', 'Critical'].includes(churnRisk);\nconst needsAlert = isNegative || isHighRisk;\n\n// Emoji for sentiment label\nconst sentimentEmoji = {\n  'Positive': '\ud83d\udfe2',\n  'Mostly Positive': '\ud83d\udfe1',\n  'Neutral': '\u26aa',\n  'Mostly Negative': '\ud83d\udfe0',\n  'Negative': '\ud83d\udd34'\n}[meetingData.sentimentLabel] || '\u26aa';\n\n// Emoji for churn risk\nconst riskEmoji = {\n  'Low': '\u2705',\n  'Medium': '\u26a0\ufe0f',\n  'High': '\ud83d\udea8',\n  'Critical': '\ud83c\udd98'\n}[churnRisk] || '\u26a0\ufe0f';\n\nconst loggedAt = new Date().toISOString().replace('T', ' ').substring(0, 16);\n\nreturn [{\n  json: {\n    weekLabel: meetingData.weekStart + ' to ' + meetingData.weekEnd,\n    meetingDate: meetingData.meetingDate,\n    meetingTitle: meetingData.transcriptTitle,\n    clientName: meetingData.clientName,\n    durationMin: meetingData.durationMin,\n    participants: meetingData.participants,\n    sentimentScore: meetingData.sentimentScore,\n    sentimentLabel: meetingData.sentimentLabel,\n    positivePct: meetingData.positivePct,\n    negativePct: meetingData.negativePct,\n    gptAnalysis,\n    churnRisk,\n    recommendedAction,\n    transcriptUrl: meetingData.transcriptUrl,\n    loggedAt,\n    needsAlert,\n    sentimentEmoji,\n    riskEmoji,\n    keyQuote,\n    sheetId: meetingData.sheetId,\n    sheetName: meetingData.sheetName,\n    slackChannel: meetingData.slackChannel,\n    slackAlertChannel: meetingData.slackAlertChannel,\n    companyName: meetingData.companyName,\n    weekStart: meetingData.weekStart,\n    weekEnd: meetingData.weekEnd,\n    totalMeetings: meetingData.totalMeetings\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e416b4b8-de52-4ea2-a6a4-665a6a79ffc2",
      "name": "13. Google Sheets \u2014 Log Client Sentiment",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1904,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "Week": "={{ $json.weekLabel }}",
            "Duration": "={{ $json.durationMin }} min",
            "Logged At": "={{ $json.loggedAt }}",
            "Churn Risk": "={{ $json.riskEmoji }} {{ $json.churnRisk }}",
            "Negative %": "={{ $json.negativePct }}%",
            "Positive %": "={{ $json.positivePct }}%",
            "Client Name": "={{ $json.clientName }}",
            "GPT Analysis": "={{ $json.gptAnalysis }}",
            "Meeting Date": "={{ $json.meetingDate }}",
            "Participants": "={{ $json.participants }}",
            "Fireflies URL": "={{ $json.transcriptUrl }}",
            "Meeting Title": "={{ $json.meetingTitle }}",
            "Sentiment Label": "={{ $json.sentimentEmoji }} {{ $json.sentimentLabel }}",
            "Sentiment Score": "={{ $json.sentimentScore }}",
            "Recommended Action": "={{ $json.recommendedAction }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {
          "cellFormat": "USER_ENTERED"
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.sheetName }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.sheetId }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5618ef96-17a0-4956-9cba-9feb8e79e267",
      "name": "14. IF \u2014 Needs Urgent Alert?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1904,
        80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-alert",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.needsAlert }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "73add6af-887a-4bd0-bbf9-0846d2c35428",
      "name": "15. Slack \u2014 Send Urgent Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        -1536,
        -128
      ],
      "parameters": {
        "text": "=*CLIENT HEALTH ALERT* {{ $json.riskEmoji }}\n\n*Client:* {{ $json.clientName }}\n*Meeting:* {{ $json.meetingTitle }}\n*Date:* {{ $json.meetingDate }} | *Duration:* {{ $json.durationMin }} min\n\n*Sentiment:* {{ $json.sentimentEmoji }} {{ $json.sentimentLabel }} (Positive: {{ $json.positivePct }}% | Negative: {{ $json.negativePct }}%)\n*Churn Risk:* {{ $json.riskEmoji }} {{ $json.churnRisk }}\n\n*What Happened:*\n{{ $json.gptAnalysis }}\n\n*Key Quote:*\n_{{ $json.keyQuote }}_\n\n*Recommended Action:*\n{{ $json.recommendedAction }}\n\n_Fireflies: {{ $json.transcriptUrl }}_",
        "otherOptions": {
          "mrkdwn": true
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    },
    {
      "id": "f3f7085b-1cdd-4d3b-81a6-5000ec06bbf7",
      "name": "16. Set \u2014 No Alert Needed",
      "type": "n8n-nodes-base.set",
      "position": [
        -1536,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "no-alert-001",
              "name": "result",
              "type": "string",
              "value": "=Meeting healthy \u2014 {{ $json.sentimentLabel }} sentiment, {{ $json.churnRisk }} churn risk. No alert needed."
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "2. Set \u2014 Config Values": {
      "main": [
        [
          {
            "node": "3. HTTP \u2014 Fetch Last 50 Transcripts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. IF \u2014 Meetings Found?": {
      "main": [
        [
          {
            "node": "7. HTTP \u2014 Fetch Full Transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6. Set \u2014 No Meetings This Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14. IF \u2014 Needs Urgent Alert?": {
      "main": [
        [
          {
            "node": "15. Slack \u2014 Send Urgent Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "16. Set \u2014 No Alert Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code \u2014 Filter Last 7 Days": {
      "main": [
        [
          {
            "node": "5. IF \u2014 Meetings Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Schedule \u2014 Every Monday 9AM": {
      "main": [
        [
          {
            "node": "2. Set \u2014 Config Values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "9. AI Agent \u2014 Sentiment Analysis",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "7. HTTP \u2014 Fetch Full Transcript": {
      "main": [
        [
          {
            "node": "8. Code \u2014 Extract Sentiment Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Code \u2014 Extract Sentiment Data": {
      "main": [
        [
          {
            "node": "9. AI Agent \u2014 Sentiment Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. AI Agent \u2014 Sentiment Analysis": {
      "main": [
        [
          {
            "node": "12. Code \u2014 Combine Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Code \u2014 Combine Analysis Results": {
      "main": [
        [
          {
            "node": "13. Google Sheets \u2014 Log Client Sentiment",
            "type": "main",
            "index": 0
          },
          {
            "node": "14. IF \u2014 Needs Urgent Alert?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP \u2014 Fetch Last 50 Transcripts": {
      "main": [
        [
          {
            "node": "4. Code \u2014 Filter Last 7 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. Parser \u2014 Structured Analysis Output": {
      "ai_outputParser": [
        [
          {
            "node": "9. AI Agent \u2014 Sentiment Analysis",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}