{
  "name": "n8n workflow",
  "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 — 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 — 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 — Every Monday 9AM** triggers the workflow each week\n- **2. Set — Config Values** stores Fireflies API key, Sheet ID, both Slack channels, and company name\n- **3. HTTP — Fetch Last 50 Transcripts** pulls the 50 most recent transcripts from Fireflies\n- **4. Code — Filter Last 7 Days** filters to the past 7 days and outputs one item per transcript\n- **5. IF — Meetings Found?** stops cleanly if no meetings were recorded this week\n- **7. HTTP — Fetch Full Transcript** fetches complete transcript data per meeting including sentiment analytics\n- **8. Code — Extract Sentiment Data** extracts Fireflies NLP scores, computes a 0–100 score, assigns a label, and extracts client name from the meeting title\n- **9. AI Agent — Sentiment Analysis** uses GPT-4o-mini to add gptAnalysis, churnRisk, recommendedAction, and keyQuote\n- **12. Code — Combine Analysis Results** adds emoji flags and determines if an alert is needed\n- **13. Google Sheets — Log Client Sentiment** appends one row per meeting to the tracker\n- **14. IF — Needs Urgent Alert?** checks if sentiment is Negative or churn risk is High or Critical\n- **15. Slack — Send Urgent Alert** fires immediately for each at-risk meeting to the alert channel\n\n## Set up steps\n1. In **2. Set — Config Values** — replace YOUR_FIREFLIES_API_KEY, YOUR_GOOGLE_SHEET_ID, sheet tab name, both Slack channel names, and your company name\n2. In **10. OpenAI — GPT-4o-mini Model** — connect your OpenAI credential\n3. In **13. Google Sheets — Log Client Sentiment** — connect your Google Sheets OAuth2 credential\n4. In **15. Slack — Send Urgent Alert** — 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 — it runs every Monday at 9AM automatically"
      },
      "typeVersion": 1
    },
    {
      "id": "27065b92-7b96-4613-a9fb-fa56a5b4952b",
      "name": "Section — 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 — 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 — 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 — 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–3 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 — 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 — if so, fires an immediate Slack alert to the urgent channel."
      },
      "typeVersion": 1
    },
    {
      "id": "3c29e0e4-b444-48d3-b924-1d2c78184d2a",
      "name": "1. Schedule — 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 — 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 — 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 — Filter Last 7 Days",
      "type": "n8n-nodes-base.code",
      "position": [
        -3664,
        96
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst config = $('2. Set — 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 — 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 — 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 — 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 — Extract Sentiment Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -2880,
        80
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst prev = $('4. Code — 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*[-–|]|$)/i,\n  /(.+?)\\s*(?:call|meeting|review|sync|check.?in|catchup|catch.?up)/i,\n  /(.+?)\\s*[-–|]/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 — 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 — 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 — 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 — 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 — The single most important sentence from this meeting — positive or negative — 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 — 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 — 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 — 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 — 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': '🟢',\n  'Mostly Positive': '🟡',\n  'Neutral': '⚪',\n  'Mostly Negative': '🟠',\n  'Negative': '🔴'\n}[meetingData.sentimentLabel] || '⚪';\n\n// Emoji for churn risk\nconst riskEmoji = {\n  'Low': '✅',\n  'Medium': '⚠️',\n  'High': '🚨',\n  'Critical': '🆘'\n}[churnRisk] || '⚠️';\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 — 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 — 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 — 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 — 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 — {{ $json.sentimentLabel }} sentiment, {{ $json.churnRisk }} churn risk. No alert needed."
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "2. Set — Config Values": {
      "main": [
        [
          {
            "node": "3. HTTP — Fetch Last 50 Transcripts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. IF — Meetings Found?": {
      "main": [
        [
          {
            "node": "7. HTTP — Fetch Full Transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6. Set — No Meetings This Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14. IF — Needs Urgent Alert?": {
      "main": [
        [
          {
            "node": "15. Slack — Send Urgent Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "16. Set — No Alert Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code — Filter Last 7 Days": {
      "main": [
        [
          {
            "node": "5. IF — Meetings Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Schedule — Every Monday 9AM": {
      "main": [
        [
          {
            "node": "2. Set — Config Values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. OpenAI — GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "9. AI Agent — Sentiment Analysis",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "7. HTTP — Fetch Full Transcript": {
      "main": [
        [
          {
            "node": "8. Code — Extract Sentiment Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Code — Extract Sentiment Data": {
      "main": [
        [
          {
            "node": "9. AI Agent — Sentiment Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. AI Agent — Sentiment Analysis": {
      "main": [
        [
          {
            "node": "12. Code — Combine Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Code — Combine Analysis Results": {
      "main": [
        [
          {
            "node": "13. Google Sheets — Log Client Sentiment",
            "type": "main",
            "index": 0
          },
          {
            "node": "14. IF — Needs Urgent Alert?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP — Fetch Last 50 Transcripts": {
      "main": [
        [
          {
            "node": "4. Code — Filter Last 7 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. Parser — Structured Analysis Output": {
      "ai_outputParser": [
        [
          {
            "node": "9. AI Agent — Sentiment Analysis",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "active": false
}
