{
  "id": "hMRDyYoD0nvR4E5y",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Customer Survey Summary Generator",
  "tags": [
    "surveys",
    "ai-analysis",
    "slack",
    "sheets"
  ],
  "nodes": [
    {
      "id": "note1",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        224
      ],
      "parameters": {
        "color": 5,
        "width": 350,
        "height": 280,
        "content": "## Customer Survey Summary Generator (Template)\n\nThis workflow groups customer survey responses by sentiment (positive / neutral / negative), then uses an AI agent to extract themes, insights, and actionable recommendations, and finally saves or shares a consolidated report.\n\n**What you get**\n- Automated daily run via Schedule Trigger\n- Clean grouping & batching for accurate AI analysis\n- Consolidated summary (themes / insights / recommendations)\n- Output to Google Sheets and Slack\n\n**No secrets included** \u2014 add your own credentials after import."
      },
      "typeVersion": 1
    },
    {
      "id": "trigger1",
      "name": "Daily Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        208,
        816
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "note2",
      "name": "Data Source Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        224
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 260,
        "content": "## Setup: Data Source\n\nConnect your survey data source:\n- Google Sheets (responses table)\n- Database (SQL query)\n- CSV/other sources\n\n**How to configure (example: Google Sheets)**\n1. Add Google Sheets credentials (OAuth2) in n8n Credentials.\n2. Replace `YOUR_SHEET_ID` and `YOUR_SHEET_NAME` on both Sheets nodes.\n3. Make sure columns exist (e.g., `\u6e80\u8db3\u5ea6 (Rating)`, `\u81ea\u7531\u8a18\u8ff0\u30b3\u30e1\u30f3\u30c8 (Comment)`, `\u56de\u7b54\u65e5\u6642 (Timestamp)`)."
      },
      "typeVersion": 1
    },
    {
      "id": "sheets1",
      "name": "Get Survey Responses",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        432,
        816
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SHEET_ID"
        }
      },
      "credentials": {},
      "typeVersion": 4.5
    },
    {
      "id": "code1",
      "name": "Group & Prepare Data",
      "type": "n8n-nodes-base.code",
      "position": [
        656,
        816
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\n\n// Group responses by rating\nconst groupedByRating = {\n  positive: [],\n  neutral: [],\n  negative: []\n};\n\nfor (const item of items) {\n  // Google Sheet\u306e\u65e5\u672c\u8a9e\u5217\u540d\u3092\u6b63\u3057\u304f\u53c2\u7167\u3059\u308b\u3088\u3046\u306b\u4fee\u6b63\n  const rating = parseInt(item.json[\"\u6e80\u8db3\u5ea6 (Rating)\"] || 0);\n  const feedback = item.json[\"\u81ea\u7531\u8a18\u8ff0\u30b3\u30e1\u30f3\u30c8 (Comment)\"] || '';\n  const date = item.json[\"\u56de\u7b54\u65e5\u6642 (Timestamp)\"] || new Date().toISOString();\n  \n  const response = {\n    rating: rating,\n    feedback: feedback,\n    date: date\n  };\n  \n  // --- \u3053\u3053\u304b\u3089\u4fee\u6b63 ---\n  // 5\u6bb5\u968e\u8a55\u4fa1\u306b\u5408\u308f\u305b\u3066\u611f\u60c5\u3092\u5206\u985e\n  if (rating >= 4) {\n    // 4\u70b9\u4ee5\u4e0a\u306a\u3089 \"positive\"\n    groupedByRating.positive.push(response);\n  } else if (rating === 3) {\n    // 3\u70b9\u306a\u3089 \"neutral\"\n    groupedByRating.neutral.push(response);\n  } else {\n    // 3\u70b9\u672a\u6e80\u306a\u3089 \"negative\"\n    groupedByRating.negative.push(response);\n  }\n  // --- \u3053\u3053\u307e\u3067\u4fee\u6b63 ---\n}\n\n// Prepare batches for AI processing\nconst batches = [];\n\nfor (const [sentiment, responses] of Object.entries(groupedByRating)) {\n  if (responses.length > 0) {\n    batches.push({\n      sentiment: sentiment,\n      count: responses.length,\n      responses: responses.slice(0, 50) // 1\u30d0\u30c3\u30c1\u3042\u305f\u308a\u306e\u56de\u7b54\u6570\u309250\u306b\u5236\u9650\n    });\n  }\n}\n\nreturn batches;"
      },
      "typeVersion": 2
    },
    {
      "id": "note3",
      "name": "AI Processing Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        224
      ],
      "parameters": {
        "color": 4,
        "width": 300,
        "height": 260,
        "content": "## Setup: AI Processing\n\nThe AI Agent analyzes each batch to:\n1. Identify recurring themes\n2. Extract key insights\n3. Propose actionable recommendations\n\n**Configure**\n- Add your preferred model credentials (e.g., OpenAI or OpenRouter) in n8n.\n- Keep the JSON-only output requirement for reliable downstream parsing."
      },
      "typeVersion": 1
    },
    {
      "id": "agent1",
      "name": "Analyze Survey Batch",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1104,
        432
      ],
      "parameters": {
        "text": "=Analyze these {{ $json.sentiment }} customer survey responses and provide insights.\nNumber of responses: {{ $json.count }}\n\nResponses:\n{{ $json.responses.map(r => `Rating: ${r.rating}/5 - Feedback: \"${r.feedback}\"`).join('\\n\\n') }}\n\nPlease provide:\n1. Top 3-5 recurring themes\n2. Key insights and patterns\n3. Actionable recommendations for improvement\n\nIMPORTANT: Your entire response must be ONLY the JSON object, starting with { and ending with }. Do not include any other text, explanations, or markdown formatting.",
        "options": {
          "systemMessage": "# ROLE\nYou are an exceptional expert in customer feedback analysis.\n\n# CORE TASK\nYour mission is tao thoroughly analyze the provided batch of customer survey responses and extract strategic insights that drive business growth, not just mere summaries.\n\n# ANALYSIS PRINCIPLES\n1.  **Deep Dive:** Go beyond surface-level keywords. Delve deep into the underlying causes and customer emotions behind the responses.\n2.  **Thematization:** Identify recurring patterns and themes that appear across multiple responses. Support each theme with specific quotes or examples from the feedback.\n3.  **Actionability:** Ensure your analysis leads to specific, measurable, and actionable business improvements. Avoid vague recommendations.\n\n# CRITICAL: OUTPUT FORMAT\nYour entire response MUST strictly adhere to the provided JSON schema. Do NOT include any explanatory text, conversational pleasantries, or markdown formatting. Your entire output must be a single, valid JSON object and nothing else."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2
    },
    {
      "id": "parser1",
      "name": "Structure Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1248,
        640
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"themes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"theme\": { \"type\": \"string\" },\n          \"frequency\": { \"type\": \"string\" },\n          \"examples\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n        }\n      },\n      \"description\": \"Top recurring themes from the feedback\"\n    },\n    \"insights\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"description\": \"Key insights and patterns identified\"\n    },\n    \"recommendations\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"description\": \"Actionable recommendations for improvement\"\n    },\n    \"sentiment_summary\": {\n      \"type\": \"string\",\n      \"description\": \"Overall sentiment summary for this batch\"\n    }\n  },\n  \"required\": [\"themes\", \"insights\", \"recommendations\", \"sentiment_summary\"]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "code2",
      "name": "Aggregate Results",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        800
      ],
      "parameters": {
        "jsCode": "// Aggregate all AI analysis results\nconst allAnalyses = $input.all();\nconst timestamp = new Date().toISOString();\nconst totalResponses = allAnalyses.reduce((sum, item) => sum + (item.json.originalCount || 0), 0);\n\n// Combine all themes\nconst allThemes = [];\nconst themeMap = new Map();\n\nfor (const analysis of allAnalyses) {\n  // --- \u3053\u3053\u304b\u3089\u4fee\u6b63 ---\n  // \"output\" \u30ad\u30fc\u306e\u4e2d\u306b\u3042\u308b themes \u3092\u53c2\u7167\u3059\u308b\u3088\u3046\u306b\u5909\u66f4\n  if (analysis.json.output && analysis.json.output.themes) {\n    for (const theme of analysis.json.output.themes) {\n  // --- \u3053\u3053\u307e\u3067\u4fee\u6b63 ---\n      const key = theme.theme.toLowerCase();\n      if (themeMap.has(key)) {\n        const existing = themeMap.get(key);\n        existing.examples = [...existing.examples, ...theme.examples];\n        existing.frequency = 'Multiple mentions';\n      } else {\n        themeMap.set(key, { ...theme });\n      }\n    }\n  }\n}\n\n// Convert map to array and sort by frequency\nconst consolidatedThemes = Array.from(themeMap.values())\n  .sort((a, b) => b.examples.length - a.examples.length)\n  .slice(0, 10);\n\n// Combine all insights and recommendations\nconst allInsights = [];\nconst allRecommendations = [];\nconst sentimentBreakdown = {};\n\nfor (const analysis of allAnalyses) {\n  const sentiment = analysis.json.originalSentiment || 'unknown';\n  sentimentBreakdown[sentiment] = analysis.json.originalCount || 0;\n  \n  // --- \u3053\u3053\u304b\u3089\u4fee\u6b63 ---\n  // \"output\" \u30ad\u30fc\u306e\u4e2d\u306b\u3042\u308b insights \u3068 recommendations \u3092\u53c2\u7167\n  if (analysis.json.output && analysis.json.output.insights) {\n    allInsights.push(...analysis.json.output.insights);\n  }\n  if (analysis.json.output && analysis.json.output.recommendations) {\n    allRecommendations.push(...analysis.json.output.recommendations);\n  }\n  // --- \u3053\u3053\u307e\u3067\u4fee\u6b63 ---\n}\n\n// Create final summary\nconst summary = {\n  reportDate: timestamp,\n  totalResponsesAnalyzed: totalResponses,\n  sentimentBreakdown: sentimentBreakdown,\n  topThemes: consolidatedThemes,\n  keyInsights: [...new Set(allInsights)].slice(0, 10),\n  priorityRecommendations: [...new Set(allRecommendations)].slice(0, 7),\n  executiveSummary: `Analysis of ${totalResponses} customer survey responses revealed ${consolidatedThemes.length} key themes. Overall sentiment distribution: Positive (${sentimentBreakdown.positive || 0}), Neutral (${sentimentBreakdown.neutral || 0}), Negative (${sentimentBreakdown.negative || 0}).`\n};\n\nreturn [summary];"
      },
      "typeVersion": 2
    },
    {
      "id": "note4",
      "name": "Output Options Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        160
      ],
      "parameters": {
        "color": 6,
        "width": 300,
        "height": 260,
        "content": "## Output Options\n\nChoose one or more:\n1. Save the consolidated summary to Google Sheets\n2. Send a formatted report to Slack\n3. (Optional) Add CSV export or Database insert\n\n**Reminder**\n- Replace all placeholders (e.g., Sheet ID/Name, Slack Channel).\n- Add your own credentials in n8n after importing."
      },
      "typeVersion": 1
    },
    {
      "id": "sheets2",
      "name": "Save to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1456,
        992
      ],
      "parameters": {
        "columns": {
          "value": {
            "\u5831\u544a\u65e5": "={{ $json.reportDate }}",
            "\u611f\u60c5\u306e\u5185\u8a33": "={{ $json.sentimentBreakdown }}",
            "\u30c8\u30c3\u30d7\u30c6\u30fc\u30de": "={{ $json.topThemes }}",
            "\u30ad\u30fc\u30a4\u30f3\u30b5\u30a4\u30c8": "={{ $json.keyInsights }}",
            "\u512a\u5148\u5ea6\u63a8\u5968\u4e8b\u9805": "={{ $json.priorityRecommendations }}",
            "\u5206\u6790\u3055\u308c\u305f\u5408\u8a08\u5fdc\u7b54\u6570": "={{ $json.totalResponsesAnalyzed }}",
            "\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30b5\u30de\u30ea\u30fc```": "={{ $json.executiveSummary }}"
          },
          "schema": [
            {
              "id": "\u5831\u544a\u65e5",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u5831\u544a\u65e5",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u5206\u6790\u3055\u308c\u305f\u5408\u8a08\u5fdc\u7b54\u6570",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u5206\u6790\u3055\u308c\u305f\u5408\u8a08\u5fdc\u7b54\u6570",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u611f\u60c5\u306e\u5185\u8a33",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u611f\u60c5\u306e\u5185\u8a33",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u30c8\u30c3\u30d7\u30c6\u30fc\u30de",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u30c8\u30c3\u30d7\u30c6\u30fc\u30de",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u30ad\u30fc\u30a4\u30f3\u30b5\u30a4\u30c8",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u30ad\u30fc\u30a4\u30f3\u30b5\u30a4\u30c8",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u512a\u5148\u5ea6\u63a8\u5968\u4e8b\u9805",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u512a\u5148\u5ea6\u63a8\u5968\u4e8b\u9805",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30b5\u30de\u30ea\u30fc```",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30b5\u30de\u30ea\u30fc```",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SHEET_ID"
        }
      },
      "credentials": {},
      "typeVersion": 4.5
    },
    {
      "id": "note5",
      "name": "Processing Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        720
      ],
      "parameters": {
        "color": 2,
        "width": 250,
        "height": 204,
        "content": "## Processing Logic\n\n- `Group & Prepare Data` splits responses into three buckets by rating (>=4 Positive, =3 Neutral, <3 Negative).\n- `Loop Over Batches` ensures the AI analyzes each bucket separately for better accuracy.\n- Results are aggregated into a single executive summary for sharing."
      },
      "typeVersion": 1
    },
    {
      "id": "loop1",
      "name": "Loop Over Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        864,
        816
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "code3",
      "name": "Add Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        1456,
        560
      ],
      "parameters": {
        "jsCode": "// Add metadata to each analysis result\nconst item = $input.first();\nconst loopData = $('Loop Over Batches').first();\n\nreturn {\n  ...item.json,\n  originalSentiment: loopData.json.sentiment,\n  originalCount: loopData.json.count\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "9a64a877-3946-4c33-82c2-e802c98c30f8",
      "name": "Send a message1",
      "type": "n8n-nodes-base.slack",
      "position": [
        1456,
        1184
      ],
      "parameters": {
        "text": "=*\u9867\u5ba2\u30a2\u30f3\u30b1\u30fc\u30c8\u5206\u6790\u30ec\u30dd\u30fc\u30c8*\n\n*\u5831\u544a\u65e5\u6642:* {{ DateTime.fromISO($json.reportDate).toFormat('yyyy\u5e74MM\u6708dd\u65e5 HH:mm') }}\n*\u5206\u6790\u5bfe\u8c61\u306e\u56de\u7b54\u6570:* {{ $json.totalResponsesAnalyzed }} \u4ef6\n---------------------------------\n\n*\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30b5\u30de\u30ea\u30fc*\n{{ $json.executiveSummary }}\n\n*\u611f\u60c5\u306e\u5185\u8a33*\n\u2022 \u30dd\u30b8\u30c6\u30a3\u30d6: {{ $json.sentimentBreakdown.positive || 0 }} \u4ef6\n\u2022 \u30cd\u30ac\u30c6\u30a3\u30d6: {{ $json.sentimentBreakdown.negative || 0 }} \u4ef6\n\u2022 \u4e2d\u7acb: {{ $json.sentimentBreakdown.neutral || 0 }} \u4ef6\n\n*\u30c8\u30c3\u30d7\u30c6\u30fc\u30de*\n{{ $json.topThemes.length > 0 ? '\\n> ' + $json.topThemes.map(item => item.theme).join('\\n> ') : '\u8a72\u5f53\u306a\u3057' }}\n\n*\u30ad\u30fc\u30a4\u30f3\u30b5\u30a4\u30c8*\n{{ $json.keyInsights.length > 0 ? '\\n> ' + $json.keyInsights.join('\\n> ') : '\u8a72\u5f53\u306a\u3057' }}\n\n*\u512a\u5148\u5ea6\u63a8\u5968\u4e8b\u9805*\n{{ $json.priorityRecommendations.length > 0 ? '\\n> ' + $json.priorityRecommendations.join('\\n> ') : '\u8a72\u5f53\u306a\u3057' }}\n\n---------------------------------\n_\u672c\u30e1\u30c3\u30bb\u30fc\u30b8\u306fn8n\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306b\u3088\u3063\u3066\u81ea\u52d5\u9001\u4fe1\u3055\u308c\u307e\u3057\u305f\u3002_",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_CHANNEL_ID",
          "cachedResultName": "YOUR_CHANNEL_NAME"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {},
      "typeVersion": 2.3
    },
    {
      "id": "0c3ebcf9-2daf-4014-94c8-ae8a9c2a4244",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1104,
        640
      ],
      "parameters": {
        "options": {
          "responseFormat": "json_object"
        }
      },
      "credentials": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "timezone": "Asia/Tokyo",
    "errorWorkflow": "",
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "versionId": "2657a174-a4b8-49e9-a6f8-d0979141c6be",
  "connections": {
    "Add Metadata": {
      "main": [
        [
          {
            "node": "Loop Over Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structure Output": {
      "ai_outputParser": [
        [
          {
            "node": "Analyze Survey Batch",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Results": {
      "main": [
        [
          {
            "node": "Save to Sheet",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send a message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Batches": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze Survey Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Survey Batch": {
      "main": [
        [
          {
            "node": "Add Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Survey Responses": {
      "main": [
        [
          {
            "node": "Group & Prepare Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Group & Prepare Data": {
      "main": [
        [
          {
            "node": "Loop Over Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Survey Batch",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Daily Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Survey Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}