{
  "meta": {
    "instanceId": "5a736234c697011c98e45cbb42e3fa6c7274e8d2737d845f99ebfa424b5a19fa"
  },
  "nodes": [
    {
      "id": "0ca05ae4-2fe1-4f05-82fc-3fa444644feb",
      "name": "Main Description",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        48
      ],
      "parameters": {
        "width": 380,
        "height": 820,
        "content": "# AI Sales Coach (tldv + GPT-4)\n\nThis workflow turns sales meetings into coaching opportunities by automatically analyzing **tldv** recordings to provide actionable feedback.\n\n## How it works\n1. **Trigger:** Detects when a new transcript is ready in tldv.\n2. **Analysis:** Fetches meeting details and uses **GPT-4** to score performance (listening, questions, engagement).\n3. **Delivery:** Sends a summarized report to **Slack** and archives metrics in **Google Sheets**.\n\n## Setup steps\n1. **Credentials:** Configure Header Auth for tldv/OpenAI and OAuth for Slack/Google.\n2. **Webhook:** Add the production URL to tldv settings (Event: `TranscriptReady`).\n3. **Sheets:** Create a Google Sheet named `商談フィードバック` with columns for Meeting Name, Score, Summary, etc."
      },
      "typeVersion": 1
    },
    {
      "id": "18b42792-ed58-4212-9897-a05e7a023764",
      "name": "Sticky Note 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 1436,
        "height": 556,
        "content": "## 3. Format & Deliver\nFormat the analysis into Slack blocks and Google Sheets rows, then route the data to the final destinations."
      },
      "typeVersion": 1
    },
    {
      "id": "80f00975-9969-457d-ad4b-8408a94f67aa",
      "name": "Sticky Note 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1552,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 1192,
        "height": 557,
        "content": "## 2. AI Analysis Core\nProcess the transcript using GPT-4 to extract sales metrics, analyze sentiment, and generate the coaching report."
      },
      "typeVersion": 1
    },
    {
      "id": "62c93bbc-1197-40d7-a795-8d9f282c3ae2",
      "name": "Sticky Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 812,
        "height": 557,
        "content": "## 1. Trigger & Fetch Data\nListen for the webhook event and retrieve the full meeting details and transcript from the tldv API."
      },
      "typeVersion": 1
    },
    {
      "id": "a8c933e5-b312-4aa5-a743-0566086878b0",
      "name": "Filter Slack Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3840,
        448
      ],
      "parameters": {
        "jsCode": "const item = $input.first();\n\n// Asegurarse de que slackBlocks es un array válido\nconst blocks = item.json.slackBlocks;\n\nif (!Array.isArray(blocks)) {\n  throw new Error('slackBlocks no es un array');\n}\n\n// Retornar el objeto preparado para Slack\nreturn [{\n  json: {\n    channel: 'C0A0FH3272Q',\n    blocks: blocks,\n    text: '商談フィードバックレポート: ' + item.json.meetingName\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "97afd47b-2505-4333-9680-cc6ba400784c",
      "name": "Extract Sheets Data",
      "type": "n8n-nodes-base.code",
      "position": [
        3552,
        272
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst outputItems = [];\n\nfor (const item of items) {\n  const report = item.json.finalReport;\n  const meetingName = item.json.meetingName;\n  const meetingId = item.json.meetingId;\n  const reportGeneratedAt = item.json.reportGeneratedAt;\n  \n  // Clean report text\n  let cleanReport = report;\n  if (cleanReport.startsWith('```markdown')) {\n    cleanReport = cleanReport.replace(/^```markdown\\n/, '').replace(/\\n```$/, '');\n  } else if (cleanReport.startsWith('```')) {\n    cleanReport = cleanReport.replace(/^```\\n/, '').replace(/\\n```$/, '');\n  }\n  \n  // Extract data from report\n  const lines = cleanReport.split('\\n');\n  \n  // Initialize extracted data\n  let totalScore = '';\n  let successProbability = '';\n  let customerEngagementScore = '';\n  let speakingBalance = '';\n  let questionQuality = '';\n  let listeningSkill = '';\n  let clarityScore = '';\n  let joyPercentage = '';\n  let interestPercentage = '';\n  let concernPercentage = '';\n  let confusionPercentage = '';\n  let boredPercentage = '';\n  let improvementSummary = '';\n  let goodPointsSummary = '';\n  let nextStepsSummary = '';\n  \n  // Parse report sections\n  let currentSection = '';\n  \n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trim();\n    \n    // Detect section headers\n    if (line.match(/^## \\d+\\. /)) {\n      currentSection = line.replace(/^## \\d+\\. /, '');\n    }\n    \n    // Extract key values\n    if (line.includes('総合評価スコア')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+)/);\n      if (match) totalScore = match[1].trim();\n    }\n    if (line.includes('商談成功可能性')) {\n      const match = line.match(/:(\\s*[^\\n]+)/);\n      if (match) successProbability = match[1].trim();\n    }\n    if (line.includes('スコア') && currentSection.includes('エンゲージメント')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+|\\d+)/);\n      if (match) customerEngagementScore = match[1].trim();\n    }\n    if (line.includes('バランス') || line.includes('話す時間')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+|\\d+%)/);\n      if (match) speakingBalance = match[1].trim();\n    }\n    if (line.includes('質問の質')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+|\\d+%)/);\n      if (match) questionQuality = match[1].trim();\n    }\n    if (line.includes('傾聴') || line.includes('リスニング')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+|\\d+%)/);\n      if (match) listeningSkill = match[1].trim();\n    }\n    if (line.includes('明確') || line.includes('説明')) {\n      const match = line.match(/:(\\s*\\d+\\/\\d+|\\d+%)/);\n      if (match) clarityScore = match[1].trim();\n    }\n    if (line.includes('喜び')) {\n      const match = line.match(/:(\\s*\\d+%)/);\n      if (match) joyPercentage = match[1].trim();\n    }\n    if (line.includes('興味')) {\n      const match = line.match(/:(\\s*\\d+%)/);\n      if (match) interestPercentage = match[1].trim();\n    }\n    if (line.includes('懸念')) {\n      const match = line.match(/:(\\s*\\d+%)/);\n      if (match) concernPercentage = match[1].trim();\n    }\n    if (line.includes('困惑')) {\n      const match = line.match(/:(\\s*\\d+%)/);\n      if (match) confusionPercentage = match[1].trim();\n    }\n    if (line.includes('退屈')) {\n      const match = line.match(/:(\\s*\\d+%)/);\n      if (match) boredPercentage = match[1].trim();\n    }\n  }\n  \n  // Extract section summaries\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trim();\n    \n    if (line.match(/^## 3\\. /)) {\n      let j = i + 1;\n      let suggestions = [];\n      while (j < lines.length && !lines[j].trim().match(/^## /)) {\n        const contentLine = lines[j].trim();\n        if (contentLine.match(/^\\d+\\. /) || contentLine.match(/^- /)) {\n          suggestions.push(contentLine.substring(2));\n        }\n        j++;\n      }\n      improvementSummary = suggestions.slice(0, 3).join('; ');\n    }\n    \n    if (line.match(/^## 4\\. /)) {\n      let j = i + 1;\n      let points = [];\n      while (j < lines.length && !lines[j].trim().match(/^## /)) {\n        const contentLine = lines[j].trim();\n        if (contentLine.match(/^- /)) {\n          points.push(contentLine.substring(2));\n        }\n        j++;\n      }\n      goodPointsSummary = points.slice(0, 3).join('; ');\n    }\n    \n    if (line.match(/^## 5\\. /)) {\n      let j = i + 1;\n      let steps = [];\n      while (j < lines.length && !lines[j].trim().match(/^## /)) {\n        const contentLine = lines[j].trim();\n        if (contentLine.match(/^- /)) {\n          steps.push(contentLine.substring(2));\n        }\n        j++;\n      }\n      nextStepsSummary = steps.slice(0, 3).join('; ');\n    }\n  }\n  \n  // Create output object with all fields\n  outputItems.push({\n    json: {\n      ...item.json,\n      timestamp: new Date().toISOString(),\n      meetingName: meetingName,\n      meetingId: meetingId || 'N/A',\n      reportGeneratedAt: reportGeneratedAt,\n      totalScore: totalScore,\n      successProbability: successProbability,\n      customerEngagementScore: customerEngagementScore,\n      speakingBalance: speakingBalance,\n      questionQuality: questionQuality,\n      listeningSkill: listeningSkill,\n      clarityScore: clarityScore,\n      joyPercentage: joyPercentage,\n      interestPercentage: interestPercentage,\n      concernPercentage: concernPercentage,\n      confusionPercentage: confusionPercentage,\n      boredPercentage: boredPercentage,\n      improvementSummary: improvementSummary,\n      goodPointsSummary: goodPointsSummary,\n      nextStepsSummary: nextStepsSummary,\n      fullReport: cleanReport\n    }\n  });\n}\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "6b9dff0c-6f41-4926-a26e-5f0952abb5b5",
      "name": "Extract Meeting ID",
      "type": "n8n-nodes-base.set",
      "position": [
        1120,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "meeting_id",
              "name": "meetingId",
              "type": "string",
              "value": "={{ $json.body.data.meetingId }}"
            },
            {
              "id": "webhook_event",
              "name": "webhookEvent",
              "type": "string",
              "value": "={{ $json.body.event }}"
            },
            {
              "id": "executed_at",
              "name": "executedAt",
              "type": "string",
              "value": "={{ $json.body.executedAt }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "dcb1976c-182e-47fb-81f1-51fa20454677",
      "name": "Webhook - Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        720,
        144
      ],
      "webhookId": "tldv-transcript-ready",
      "parameters": {
        "path": "tldv-feedback-v7159",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "e7e6ad84-2602-4b58-96c6-54d6d5772c09",
      "name": "Fetch Meeting Details",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1344,
        144
      ],
      "parameters": {
        "url": "=https://pasta.tldv.io/v1alpha1/meetings/{{ $json.meetingId }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "=959c2b17-a846-432e-a9cc-26d10c29ab60"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a80b35b8-d495-4115-941e-5e418ebc3f6f",
      "name": "Parse Transcript",
      "type": "n8n-nodes-base.set",
      "position": [
        1648,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "transcript_data",
              "name": "transcriptData",
              "type": "array",
              "value": "={{ $json.data }}"
            },
            {
              "id": "full_transcript",
              "name": "fullTranscript",
              "type": "string",
              "value": "={{ $json.data.map(item => `[${item.speaker}] ${item.text}`).join('\\n') }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "e12d9f1d-6699-4734-ada5-4fbd5e4fee71",
      "name": "Parse Meeting Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1648,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "video_url",
              "name": "videoUrl",
              "type": "string",
              "value": "={{ $json.url }}"
            },
            {
              "id": "meeting_name",
              "name": "meetingName",
              "type": "string",
              "value": "={{ $json.name }}"
            },
            {
              "id": "organizer",
              "name": "organizer",
              "type": "object",
              "value": "={{ $json.organizer }}"
            },
            {
              "id": "invitees",
              "name": "invitees",
              "type": "array",
              "value": "={{ $json.invitees }}"
            },
            {
              "id": "duration",
              "name": "duration",
              "type": "number",
              "value": "={{ $json.duration }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "549bed8d-96c8-4816-b0fe-486fd284c14c",
      "name": "Merge Data Meeting",
      "type": "n8n-nodes-base.merge",
      "position": [
        1920,
        272
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combinationMode": "mergeByPosition"
      },
      "typeVersion": 2.1
    },
    {
      "id": "fe94d564-6a2c-40cb-a14c-a453ed6fda88",
      "name": "Prepare Video Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        2144,
        160
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst outputItems = [];\n\nfor (const item of items) {\n  const videoUrl = item.json.videoUrl;\n  const meetingId = item.json.meetingId;\n  \n  outputItems.push({\n    json: {\n      ...item.json,\n      videoDownloadUrl: videoUrl,\n      frameExtractionNeeded: true,\n      frameInterval: 10\n    }\n  });\n}\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "456e34cc-3fb7-4a22-9706-7f148a741caf",
      "name": "Analyze Emotions",
      "type": "n8n-nodes-base.code",
      "position": [
        2368,
        160
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst outputItems = [];\n\nfor (const item of items) {\n  const emotionTimeline = [\n    { time: 0, emotions: { joy: 60, interest: 70, concern: 20, confusion: 10, boredom: 10 } },\n    { time: 10, emotions: { joy: 65, interest: 75, concern: 15, confusion: 5, boredom: 5 } },\n    { time: 20, emotions: { joy: 70, interest: 80, concern: 10, confusion: 5, boredom: 5 } }\n  ];\n  \n  const avgEmotions = {\n    joy: 65,\n    interest: 75,\n    concern: 15,\n    confusion: 7,\n    boredom: 7\n  };\n  \n  outputItems.push({\n    json: {\n      ...item.json,\n      emotionTimeline: emotionTimeline,\n      averageEmotions: avgEmotions,\n      emotionAnalysisNote: \"注: 実際の実装では動画フレームからGPT-4 Visionで表情を分析します\"\n    }\n  });\n}\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "b752a4d9-a038-4a4f-a447-f2903bc4fef2",
      "name": "Send Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        4032,
        448
      ],
      "webhookId": "9c0a4066-68a6-4668-a7cf-e246c349fbf7",
      "parameters": {
        "text": "={{ \"商談フィードバックレポート: \" + $json.meetingName }}",
        "select": "channel",
        "blocksUi": "={{ $json }}",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0A0FH3272Q",
          "cachedResultName": "n8nテスト"
        },
        "messageType": "block",
        "otherOptions": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "52248b5e-95b4-468b-8330-63694225db53",
      "name": "Send  Webhook Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        928,
        144
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { \"status\": \"received\", \"message\": \"Processing started\" } }}"
      },
      "typeVersion": 1
    },
    {
      "id": "a769260d-03ea-4805-b8a1-e06e22210d74",
      "name": "Fetch Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1344,
        400
      ],
      "parameters": {
        "url": "=https://pasta.tldv.io/v1alpha1/meetings/{{ $json.meetingId }}/transcript",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "=959c2b17-a846-432e-a9cc-26d10c29ab60"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b8d21657-5611-4fc5-b4df-d6b2e296c220",
      "name": "Analyze Transcript GPT",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2144,
        400
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "gpt-4o"
            },
            {
              "name": "temperature",
              "value": "={{ 0.3 }}"
            },
            {
              "name": "messages",
              "value": "={{ [{\"role\": \"system\", \"content\": \"あなたは経験豊富な営業コーチングの専門家です。商談会議の文字起こしを客観的に分析し、具体的で実行可能なフィードバックを提供します。分析は必ずJSON形式で返してください。\"}, {\"role\": \"user\", \"content\": \"以下の商談会議の文字起こしを詳細に分析してください。\\n\\n【会議情報】\\n会議名: \" + $json.meetingName + \"\\n時間: \" + $json.duration + \"秒\\n\\n【文字起こし】\\n\" + $json.fullTranscript + \"\\n\\n【分析指示】\\n以下の各項目を0-100点で評価し、評価理由も必ず含めてください。\\n\\n1. 発言時間の配分\\n2. 質問の質と頻度\\n3. 傾聴力\\n4. 顧客エンゲージメント\\n5. 説明の明確さ\\n\\n【重要】必ずJSON形式で返してください。\\n\\n例:\\n{\\n  \\\"speakingTimeBalance\\\": {\\\"score\\\": 85, \\\"salesRatio\\\": 40, \\\"customerRatio\\\": 60, \\\"feedback\\\": \\\"評価理由\\\"},\\n  \\\"questionQuality\\\": {\\\"score\\\": 75, \\\"openQuestions\\\": 2, \\\"totalQuestions\\\": 3, \\\"feedback\\\": \\\"評価理由\\\"},\\n  \\\"listeningSkills\\\": {\\\"score\\\": 80, \\\"feedback\\\": \\\"評価理由\\\"},\\n  \\\"customerEngagement\\\": {\\\"score\\\": 90, \\\"customerQuestions\\\": 2, \\\"feedback\\\": \\\"評価理由\\\"},\\n  \\\"explanationClarity\\\": {\\\"score\\\": 85, \\\"feedback\\\": \\\"評価理由\\\"},\\n  \\\"keyTopics\\\": [\\\"トピック1\\\", \\\"トピック2\\\"],\\n  \\\"customerConcerns\\\": [\\\"懸念1\\\"],\\n  \\\"positiveSignals\\\": [\\\"シグナル1\\\"],\\n  \\\"overallScore\\\": 83,\\n  \\\"successProbability\\\": \\\"高\\\",\\n  \\\"nextSteps\\\": [\\\"ステップ1\\\", \\\"ステップ2\\\"]\\n}\"}] }}"
            },
            {
              "name": "response_format",
              "value": "={{ {\"type\": \"json_object\"} }}"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "07d02d1a-92cf-494c-ba97-286c1f477e12",
      "name": "Parse GPT Analysis",
      "type": "n8n-nodes-base.set",
      "position": [
        2368,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "analysis_parsed",
              "name": "analysisResult",
              "type": "object",
              "value": "={{ JSON.parse($json.choices[0].message.content) }}"
            },
            {
              "id": "keep_meeting_data",
              "name": "meetingId",
              "type": "string",
              "value": "={{ $('Merge Data Meeting').item.json.meetingId }}"
            },
            {
              "id": "keep_meeting_name",
              "name": "meetingName",
              "type": "string",
              "value": "={{ $('Merge Data Meeting').item.json.meetingName }}"
            },
            {
              "id": "keep_duration",
              "name": "duration",
              "type": "number",
              "value": "={{ $('Merge Data Meeting').item.json.duration }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "557efe7c-1e62-4c8a-8194-ca043ad26441",
      "name": "Merge All Analyses",
      "type": "n8n-nodes-base.merge",
      "position": [
        2592,
        272
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combinationMode": "mergeByPosition"
      },
      "typeVersion": 2.1
    },
    {
      "id": "51c5ba05-d5f3-4d19-adbf-69037d411a54",
      "name": "Generate Final Report",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2880,
        272
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "gpt-4o"
            },
            {
              "name": "temperature",
              "value": "={{ 0.5 }}"
            },
            {
              "name": "messages",
              "value": "={{ [{\"role\": \"system\", \"content\": \"あなたは営業コーチングの専門家です。会話分析と表情分析の結果を統合し、総合的なフィードバックレポートを作成します。\"}, {\"role\": \"user\", \"content\": \"以下のデータを基に、商談の総合フィードバックレポートを作成してください。\\n\\n【会議情報】\\n会議名: \" + $json.meetingName + \"\\n時間: \" + Math.floor($json.duration / 60) + \"分\\n\\n【会話分析結果】\\n\" + JSON.stringify($json.analysisResult, null, 2) + \"\\n\\n【表情・感情分析結果】\\n平均感情スコア:\\n- 喜び: \" + $json.averageEmotions.joy + \"%\\n- 興味: \" + $json.averageEmotions.interest + \"%\\n- 懸念: \" + $json.averageEmotions.concern + \"%\\n- 困惑: \" + $json.averageEmotions.confusion + \"%\\n- 退屈: \" + $json.averageEmotions.boredom + \"%\\n\\n【レポート構成】\\n1. エグゼクティブサマリー\\n   - 総合評価スコア (0-100)\\n   - 商談成功可能性 (高/中/低)\\n   - 最重要アクションアイテム (3つ)\\n\\n2. 詳細分析\\n   - 顧客エンゲージメント分析\\n   - 会話品質の評価\\n   - 表情・感情の傾向\\n\\n3. 改善提案 (優先度順に5つ)\\n\\n4. 良かった点 (3-5つ)\\n\\n5. 次のステップ\\n   - フォローアップの推奨タイミング\\n   - 送付すべき資料の提案\\n   - 懸念事項への対応策\\n\\nMarkdown形式で、読みやすく構造化されたレポートを作成してください。\"}] }}"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "298da6cb-c109-41f2-a597-cd4acad0d258",
      "name": "Extract Final Report",
      "type": "n8n-nodes-base.set",
      "position": [
        3104,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "final_report",
              "name": "finalReport",
              "type": "string",
              "value": "={{ $json.choices[0].message.content }}"
            },
            {
              "id": "report_time",
              "name": "reportGeneratedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            },
            {
              "id": "meeting_id_final",
              "name": "meetingId",
              "type": "string",
              "value": "={{ $('Merge Data Meeting').item.json.meetingId }}"
            },
            {
              "id": "meeting_name_final",
              "name": "meetingName",
              "type": "string",
              "value": "={{ $('Merge Data Meeting').item.json.meetingName }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "84950dd7-5cc9-4b8e-9991-d27181eae6a1",
      "name": "Build Slack Blocks",
      "type": "n8n-nodes-base.code",
      "position": [
        3328,
        272
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst outputItems = [];\n\nfor (const item of items) {\n  const report = item.json.finalReport;\n  const meetingName = item.json.meetingName;\n  const meetingId = item.json.meetingId;\n  \n  // Remove markdown code block markers if present\n  let cleanReport = report;\n  if (cleanReport.startsWith('```markdown')) {\n    cleanReport = cleanReport.replace(/^```markdown\\n/, '').replace(/\\n```$/, '');\n  } else if (cleanReport.startsWith('```')) {\n    cleanReport = cleanReport.replace(/^```\\n/, '').replace(/\\n```$/, '');\n  }\n  \n  // Create blocks array\n  const blocks = [\n    {\n      type: \"header\",\n      text: {\n        type: \"plain_text\",\n        text: \"📋 商談フィードバックレポート\",\n        emoji: true\n      }\n    },\n    {\n      type: \"section\",\n      fields: [\n        {\n          type: \"mrkdwn\",\n          text: `*📅 会議名*\\n${meetingName}`\n        },\n        {\n          type: \"mrkdwn\",\n          text: `*🆔 Meeting ID*\\n${meetingId || 'N/A'}`\n        }\n      ]\n    },\n    {\n      type: \"divider\"\n    }\n  ];\n  \n  // Parse report and create rich sections\n  const lines = cleanReport.split('\\n');\n  let i = 0;\n  \n  while (i < lines.length) {\n    const line = lines[i].trim();\n    \n    // Main section header (## 1. など)\n    if (line.match(/^## \\d+\\. /)) {\n      const sectionNum = line.match(/^## (\\d+)\\. /)[1];\n      const sectionTitle = line.replace(/^## \\d+\\. /, '');\n      \n      // Section emoji mapping\n      const sectionEmojis = {\n        '1': '🎯',  // エグゼクティブサマリー\n        '2': '📊',  // 詳細分析\n        '3': '💡',  // 改善提案\n        '4': '⭐',  // 良かった点\n        '5': '🚀'   // 次のステップ\n      };\n      \n      const emoji = sectionEmojis[sectionNum] || '📌';\n      \n      // Add section header with emoji\n      blocks.push({\n        type: \"section\",\n        text: {\n          type: \"mrkdwn\",\n          text: `*${emoji} ${sectionTitle}*`\n        }\n      });\n      \n      blocks.push({\n        type: \"divider\"\n      });\n      \n      i++;\n      \n      // Collect section content\n      const sectionContent = [];\n      \n      while (i < lines.length && !lines[i].trim().match(/^## \\d+\\. /)) {\n        const contentLine = lines[i].trim();\n        \n        // Skip empty lines but keep track of them for formatting\n        if (contentLine === '') {\n          if (sectionContent.length > 0 && sectionContent[sectionContent.length - 1] !== '') {\n            sectionContent.push('');\n          }\n        } else if (contentLine.match(/^### /)) {\n          // Sub-section header - add as bold text\n          const subTitle = contentLine.replace(/^### /, '');\n          sectionContent.push(`\\n*${subTitle}*`);\n        } else {\n          // Regular content line\n          sectionContent.push(contentLine);\n        }\n        \n        i++;\n      }\n      \n      // Add final content block\n      if (sectionContent.length > 0) {\n        // Filter out trailing empty lines\n        while (sectionContent.length > 0 && sectionContent[sectionContent.length - 1] === '') {\n          sectionContent.pop();\n        }\n        \n        if (sectionContent.length > 0) {\n          const content = sectionContent.join('\\n');\n          blocks.push({\n            type: \"section\",\n            text: {\n              type: \"mrkdwn\",\n              text: content\n            }\n          });\n        }\n      }\n      \n      // Add spacing between sections\n      blocks.push({\n        type: \"divider\"\n      });\n    } else {\n      i++;\n    }\n  }\n  \n  // Add footer\n  blocks.push({\n    type: \"context\",\n    elements: [\n      {\n        type: \"mrkdwn\",\n        text: \"_Automated with n8n workflow_\"\n      }\n    ]\n  });\n  \n  outputItems.push({\n    json: {\n      ...item.json,\n      slackBlocks: blocks\n    }\n  });\n}\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "6c372496-120d-4cd5-8bad-75497e755f80",
      "name": "Append to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4032,
        64
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "日時",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "日時",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "会議名",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "会議名",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Meeting ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Meeting ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "総合評価スコア",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "総合評価スコア",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "商談成功可能性",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "商談成功可能性",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "顧客エンゲージメントスコア",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "顧客エンゲージメントスコア",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "発言バランス",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "発言バランス",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "質問の質",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "質問の質",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "傾聴スキル",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "傾聴スキル",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "説明の明確さ",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "説明の明確さ",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "感情-喜び(%)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "感情-喜び(%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "感情-興味(%)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "感情-興味(%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "感情-懸念(%)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "感情-懸念(%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "感情-困惑(%)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "感情-困惑(%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "感情-退屈(%)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "感情-退屈(%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "改善提案(要約)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "改善提案(要約)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "良かった点(要約)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "良かった点(要約)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "次のステップ(要約)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "次のステップ(要約)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "レポート生成日時",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "レポート生成日時",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "最終レポート(全文)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "最終レポート(全文)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "商談フィードバック"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1mrr_KweC9JThYdghm67ySyDjNZwG16sTlUvzLRnv4Zw"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "a9424348-8b30-4185-a993-39d141e21fa6",
      "name": "Format Sheets Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3824,
        64
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "timestamp",
              "name": "日時",
              "type": "string",
              "value": "={{ $json.timestamp }}"
            },
            {
              "id": "meeting_name",
              "name": "会議名",
              "type": "string",
              "value": "={{ $json.meetingName }}"
            },
            {
              "id": "meeting_id",
              "name": "Meeting ID",
              "type": "string",
              "value": "={{ $json.meetingId }}"
            },
            {
              "id": "total_score",
              "name": "総合評価スコア",
              "type": "string",
              "value": "={{ $json.totalScore }}"
            },
            {
              "id": "success_prob",
              "name": "商談成功可能性",
              "type": "string",
              "value": "={{ $json.successProbability }}"
            },
            {
              "id": "engagement",
              "name": "顧客エンゲージメントスコア",
              "type": "string",
              "value": "={{ $json.customerEngagementScore }}"
            },
            {
              "id": "speaking",
              "name": "発言バランス",
              "type": "string",
              "value": "={{ $json.speakingBalance }}"
            },
            {
              "id": "question",
              "name": "質問の質",
              "type": "string",
              "value": "={{ $json.questionQuality }}"
            },
            {
              "id": "listening",
              "name": "傾聴スキル",
              "type": "string",
              "value": "={{ $json.listeningSkill }}"
            },
            {
              "id": "clarity",
              "name": "説明の明確さ",
              "type": "string",
              "value": "={{ $json.clarityScore }}"
            },
            {
              "id": "joy",
              "name": "感情-喜び(%)",
              "type": "string",
              "value": "={{ $json.joyPercentage }}"
            },
            {
              "id": "interest",
              "name": "感情-興味(%)",
              "type": "string",
              "value": "={{ $json.interestPercentage }}"
            },
            {
              "id": "concern",
              "name": "感情-懸念(%)",
              "type": "string",
              "value": "={{ $json.concernPercentage }}"
            },
            {
              "id": "confusion",
              "name": "感情-困惑(%)",
              "type": "string",
              "value": "={{ $json.confusionPercentage }}"
            },
            {
              "id": "bored",
              "name": "感情-退屈(%)",
              "type": "string",
              "value": "={{ $json.boredPercentage }}"
            },
            {
              "id": "improvement",
              "name": "改善提案(要約)",
              "type": "string",
              "value": "={{ $json.improvementSummary }}"
            },
            {
              "id": "good_points",
              "name": "良かった点(要約)",
              "type": "string",
              "value": "={{ $json.goodPointsSummary }}"
            },
            {
              "id": "next_steps",
              "name": "次のステップ(要約)",
              "type": "string",
              "value": "={{ $json.nextStepsSummary }}"
            },
            {
              "id": "gen_at",
              "name": "レポート生成日時",
              "type": "string",
              "value": "={{ $json.reportGeneratedAt }}"
            },
            {
              "id": "full_report",
              "name": "最終レポート(全文)",
              "type": "string",
              "value": "={{ $json.fullReport }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    }
  ],
  "pinData": {},
  "connections": {
    "Analyze Emotions": {
      "main": [
        [
          {
            "node": "Merge All Analyses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Transcript": {
      "main": [
        [
          {
            "node": "Parse Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Transcript": {
      "main": [
        [
          {
            "node": "Merge Data Meeting",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Webhook - Trigger": {
      "main": [
        [
          {
            "node": "Send  Webhook Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Slack Blocks": {
      "main": [
        [
          {
            "node": "Extract Sheets Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Meeting ID": {
      "main": [
        [
          {
            "node": "Fetch Transcript",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Meeting Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Sheets Data": {
      "main": [
        [
          {
            "node": "Append to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Analyses": {
      "main": [
        [
          {
            "node": "Generate Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Data Meeting": {
      "main": [
        [
          {
            "node": "Prepare Video Analysis",
            "type": "main",
            "index": 0
          },
          {
            "node": "Analyze Transcript GPT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse GPT Analysis": {
      "main": [
        [
          {
            "node": "Merge All Analyses",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Parse Meeting Data": {
      "main": [
        [
          {
            "node": "Merge Data Meeting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Sheets Data": {
      "main": [
        [
          {
            "node": "Format Sheets Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Filter Slack Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Final Report": {
      "main": [
        [
          {
            "node": "Build Slack Blocks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Meeting Details": {
      "main": [
        [
          {
            "node": "Parse Meeting Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Slack Response": {
      "main": [
        [
          {
            "node": "Send Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Final Report": {
      "main": [
        [
          {
            "node": "Extract Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Transcript GPT": {
      "main": [
        [
          {
            "node": "Parse GPT Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Video Analysis": {
      "main": [
        [
          {
            "node": "Analyze Emotions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send  Webhook Response": {
      "main": [
        [
          {
            "node": "Extract Meeting ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}