8
n8n 中文网amn8n.com

餐厅每日洞察自动化 - AI驱动的邮件报告生成器

高级

这是一个Document Extraction, AI Summarization领域的自动化工作流,包含 33 个节点。主要使用 Code, Wait, Gmail, Merge, GoogleSheets 等节点。 餐厅每日洞察与Gemini AI - 销售、浪费与反馈邮件摘要

前置要求
  • Google 账号和 Gmail API 凭证
  • Google Sheets API 凭证
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "Dvr7DN12sJBV7qyr",
  "meta": {
    "instanceId": "dd69efaf8212c74ad206700d104739d3329588a6f3f8381a46a481f34c9cc281",
    "templateCredsSetupCompleted": true
  },
  "name": "餐厅每日洞察自动化 – AI驱动的邮件报告生成器",
  "tags": [],
  "nodes": [
    {
      "id": "d25f6d93-48a7-4213-9160-915a0a640f01",
      "name": "工作流概览",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        0
      ],
      "parameters": {
        "width": 600,
        "height": 200,
        "content": "## 餐厅每日报告工作流 🍽️"
      },
      "typeVersion": 1
    },
    {
      "id": "577a3cfe-074e-41ef-8309-55ff22d523cd",
      "name": "销售数据分析",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -760,
        380
      ],
      "parameters": {
        "color": 5,
        "width": 1320,
        "height": 500,
        "content": "## 销售数据分析 📈"
      },
      "typeVersion": 1
    },
    {
      "id": "85dc03eb-fef1-47ec-896a-7818e1f3eaf9",
      "name": "食物浪费分析",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        1020
      ],
      "parameters": {
        "color": 4,
        "width": 800,
        "height": 260,
        "content": "## 食物浪费分析 🗑️"
      },
      "typeVersion": 1
    },
    {
      "id": "93dd3719-b6f1-478c-af4b-f156be0391a8",
      "name": "客户反馈分析",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        1420
      ],
      "parameters": {
        "color": 6,
        "width": 1300,
        "height": 800,
        "content": "## 客户反馈分析 💬"
      },
      "typeVersion": 1
    },
    {
      "id": "7f24cc28-2b7e-4563-a27e-924bcb105919",
      "name": "合并与邮件创建",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        646,
        760
      ],
      "parameters": {
        "color": 2,
        "width": 600,
        "height": 500,
        "content": "## 合并与邮件创建 📧"
      },
      "typeVersion": 1
    },
    {
      "id": "3bc8c564-d29a-42bd-a529-7f7a52f32077",
      "name": "发送每日报告",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        800
      ],
      "parameters": {
        "color": 3,
        "width": 380,
        "height": 480,
        "content": "## 发送每日报告 🚀"
      },
      "typeVersion": 1
    },
    {
      "id": "56b5f162-622c-4d29-be8d-634886b55e33",
      "name": "Google Gemini聊天模型用于摘要",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1364,
        1320
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "RvSkIBjP48ORJKhU",
          "name": "Google Gemini(PaLM) Api account - test"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "733727b2-114b-485e-96eb-0b2c04cf58af",
      "name": "思考用于摘要",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        1484,
        1320
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "90a4559d-6b77-48e8-a2b9-63950eab06aa",
      "name": "每日报告调度器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -580,
        1100
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 22
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "28b16c0f-7733-4fee-bff9-48c9cdad7020",
      "name": "获取每日销售数据",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -360,
        700
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1013658249,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit#gid=1013658249",
          "cachedResultName": "sales page"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit?usp=drivesdk",
          "cachedResultName": "daily report 13-07-2025"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "ScSS2KxGQULuPtdy",
          "name": "Google Sheets- test"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "5c0adaba-b6e4-47e3-9d05-13c3088f1e84",
      "name": "规范化销售记录",
      "type": "n8n-nodes-base.code",
      "position": [
        -140,
        700
      ],
      "parameters": {
        "jsCode": "// Fetch all incoming items\nconst items = $input.all();\n\n// Extract the raw row data (each item.json is one row)\nconst rawRows = items.map(item => item.json);\n\n// Bundle everything into a single field\nconst payload = { rows: rawRows };\n\n// Return a single output item whose json contains your full dataset\nreturn [{ json: { data: payload } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "60217633-b248-4cb8-bf79-077f88555734",
      "name": "AI销售洞察生成器",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        80,
        600
      ],
      "parameters": {
        "text": "={{ $json.data }}",
        "options": {
          "systemMessage": "You are a data-driven AI Sales Analyst. You will receive structured input data (JSON) representing daily sales performance for various dishes including: Date, Dish Name, Category, Quantity Sold, Unit Price, Total Revenue, Cost Per Unit, Profit Margin (%), Peak Hour, Weather Impact.\n\nYour responsibilities:\n\n1. **Data Validation & Calculations**\n   - Confirm each record includes all fields.\n   - Calculate:\n     • Total Cost = Quantity Sold × Cost Per Unit  \n     • Profit Amount = Total Revenue − Total Cost  \n     • Actual Profit Margin (%) = (Profit Amount ÷ Total Revenue) × 100 (compare with provided field).\n\n2. **Aggregation & Insights**\n   - Identify top 3 dishes by quantity sold.\n   - Identify top 3 dishes by revenue.\n   - Identify dishes with highest and lowest profit margins.\n   - Analyze overall metrics: total quantity sold, total revenue, average profit margin for the day.\n\n3. **Trend & Pattern Analysis**\n   - Correlate weather impact (\"Cloudy\") with sales patterns.\n   - Highlight peak hours across categories.\n   - Suggest category-level performance insights.\n\n4. **Actionable Recommendations**\n   - Recommend strategic actions (e.g., menu adjustments, upsells, pricing changes, inventory focus).\n   - If a dish underperforms in revenue but has high margin, suggest promoting opportunities.\n\n5. **Output Format**\n   - Deliver a concise report with sections:\n     • Summary Metrics  \n     • Top/Bottom Performers  \n     • Weather/Peak-Hour Analysis  \n     • Recommendations  \n     • Data Quality (note mismatches or missing fields)\n\n- Return output as structured JSON with keys: `summary`, `top_dishes_by_quantity`, `top_dishes_by_revenue`, `margin_extremes`, `weather_insights`, `peak_hour_insights`, `recommendations`, `data_quality_issues`.\n\nOnly output valid JSON (no markdown, no explanatory text outside JSON).\n"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "0557d918-2535-4d3f-a78e-df06dca98243",
      "name": "格式化销售AI输出",
      "type": "n8n-nodes-base.code",
      "position": [
        456,
        700
      ],
      "parameters": {
        "jsCode": "/**\n * Normalize raw AI JSON response (possibly wrapped in ```json blocks).\n * To be used inside an n8n Function/Code node.\n * Input item should have raw output under `item.json.output`.\n */\nfunction normalizeAI(item) {\n  let raw = item.json.output;\n  if (typeof raw !== 'string') {\n    throw new Error('Missing AI output string');\n  }\n\n  // 1. Strip ```json and backticks\n  raw = raw\n    .trim()\n    .replace(/^```json\\s*/, '')\n    .replace(/```$/, '')\n    .trim();\n\n  // 2. Remove stray backslashes (if necessary)\n  raw = raw.replace(/\\\\/g, '');\n\n  // 3. Parse JSON\n  let report;\n  try {\n    report = JSON.parse(raw);\n  } catch (e) {\n    throw new Error('Failed to parse AI JSON: ' + e.message);\n  }\n\n  // 4. Normalize numeric fields\n  const toNum = x => (x !== undefined ? Number(x) : x);\n\n  if (report.summary) {\n    report.summary.total_quantity_sold = toNum(report.summary.total_quantity_sold);\n    report.summary.total_revenue = toNum(report.summary.total_revenue);\n    report.summary.total_profit = toNum(report.summary.total_profit);\n    report.summary.average_profit_margin = toNum(report.summary.average_profit_margin);\n  }\n\n  const normalizeList = (arr, key) => {\n    if (!Array.isArray(arr)) return [];\n    return arr.map(i => ({\n      ...i,\n      [key]: toNum(i[key])\n    }));\n  };\n\n  report.top_dishes_by_quantity = normalizeList(report.top_dishes_by_quantity, 'quantity_sold');\n  report.top_dishes_by_revenue = normalizeList(report.top_dishes_by_revenue, 'total_revenue');\n\n  if (report.margin_extremes) {\n    if (report.margin_extremes.highest) {\n      report.margin_extremes.highest.calculated_profit_margin =\n        toNum(report.margin_extremes.highest.calculated_profit_margin);\n    }\n    if (report.margin_extremes.lowest) {\n      report.margin_extremes.lowest.calculated_profit_margin =\n        toNum(report.margin_extremes.lowest.calculated_profit_margin);\n    }\n  }\n\n  // 5. Ensure recommendations is array\n  if (!Array.isArray(report.recommendations)) {\n    report.recommendations = [];\n  }\n\n  return { json: report };\n}\n\n// Map over all incoming items\nreturn items.map(item => normalizeAI(item));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0464780d-0315-4b7d-80ab-9fef79ad7612",
      "name": "Google聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        820
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "RvSkIBjP48ORJKhU",
          "name": "Google Gemini(PaLM) Api account - test"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e85b4ee4-6904-4ef6-a951-c147829f6421",
      "name": "思考工具",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        820
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "170f519d-abc5-4ecb-ac84-6c359f983ce1",
      "name": "获取每日食物浪费记录",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -360,
        1100
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1085743843,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit#gid=1085743843",
          "cachedResultName": "food waste page"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit?usp=drivesdk",
          "cachedResultName": "daily report 13-07-2025"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "ScSS2KxGQULuPtdy",
          "name": "Google Sheets- test"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "ab2eb33f-3306-4046-9f4f-4aaf66d6d53d",
      "name": "规范化浪费数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -140,
        1100
      ],
      "parameters": {
        "jsCode": "// Fetch all incoming items\nconst items = $input.all();\n\n// Extract the raw row data (each item.json is one row)\nconst rawRows = items.map(item => item.json);\n\n// Bundle everything into a single field\nconst payload = { rows: rawRows };\n\n// Return a single output item whose json contains your full dataset\nreturn [{ json: { data: payload } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "85ce40ab-591e-4712-8072-da1d471a9a75",
      "name": "AI浪费减少洞察生成器",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        80,
        1000
      ],
      "parameters": {
        "text": "={{ $json.data }}",
        "options": {
          "systemMessage": "You are a Food‑Waste Analyst AI. You will receive structured JSON data rows representing daily waste entries, each with: Date, Item Name, Category, Waste Type, Quantity Wasted, Unit, Cost Per Unit, Total Waste Cost, Waste Reason, Prevention Action.\n\nYour tasks:\n1. **Validate & compute metrics**:\n   - Ensure each record has required fields.\n   - Calculate missing values if needed, e.g. Total Waste Cost = Quantity × Cost Per Unit.\n   - Aggregate daily totals: total quantity wasted, total waste cost, average cost/unit.\n\n2. **Categorical breakdown**:\n   - Summarize waste by Category (e.g. Prepared vs Raw) and Waste Type.\n   - Identify top 3 highest‑cost waste items and highest‑quantity waste items.\n\n3. **Reason & action analysis**:\n   - Count occurrences of Waste Reasons and Prevention Actions.\n   - Highlight recurring issues and evaluate if prevention actions align.\n\n4. **Best‑practice recommendations**:\n   - Suggest improvements based on proven strategies: waste audits, staff training, FIFO, inventory controls, automated tracking, tech integration :contentReference[oaicite:1]{index=1}.\n   - For frequent kitchen errors: recommend staff retraining or timer systems.\n   - For inventory expiration: suggest inventory analytics, par‑levels, FIFO adoption, supply‑chain review :contentReference[oaicite:2]{index=2}.\n\n5. **Output format**:\n   Return a JSON with keys:\n   - `validation` (missing fields, mismatches)\n   - `daily_totals`\n   - `by_category`\n   - `top_waste_items`\n   - `reason_summary`\n   - `action_evaluation`\n   - `recommendations`\n\nEnsure output is valid JSON only—no markdown or extra text.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "93c9c5ee-df52-4b7f-a5c5-62a6145f65c2",
      "name": "格式化浪费AI输出",
      "type": "n8n-nodes-base.code",
      "position": [
        456,
        1100
      ],
      "parameters": {
        "jsCode": "/**\n * n8n Code node to parse and normalize AI Agent JSON response.\n * Expects raw output in item.json.output.\n */\nfunction normalizeAI(item) {\n  let raw = item.json.output;\n  if (typeof raw !== 'string') {\n    throw new Error('Expected output string in item.json.output');\n  }\n\n  // 1. Clean up markdown wrappers and code fences\n  raw = raw\n    .trim()\n    .replace(/^```json\\s*/, '')\n    .replace(/```$/, '')\n    .trim();\n\n  // 2. Parse JSON with safe fallback\n  let report;\n  try {\n    report = JSON.parse(raw);\n  } catch (e) {\n    throw new Error('Invalid JSON from AI: ' + e.message);\n  }\n\n  // 3. Helper to convert numeric fields\n  const toNumber = val => {\n    const num = Number(val);\n    return isNaN(num) ? null : num;\n  };\n\n  // 4. Normalize daily_totals\n  if (report.daily_totals) {\n    report.daily_totals.total_quantity_wasted = toNumber(report.daily_totals.total_quantity_wasted);\n    report.daily_totals.total_waste_cost = toNumber(report.daily_totals.total_waste_cost);\n    report.daily_totals.average_cost_per_unit_wasted = toNumber(report.daily_totals.average_cost_per_unit_wasted);\n  }\n\n  // 5. Normalize by_category and by_waste_type\n  ['by_category', 'by_waste_type'].forEach(section => {\n    if (report[section] && typeof report[section] === 'object') {\n      Object.entries(report[section]).forEach(([key, val]) => {\n        val.total_cost = toNumber(val.total_cost);\n        val.total_quantity = toNumber(val.total_quantity);\n      });\n    }\n  });\n\n  // 6. Normalize top_waste_items arrays\n  if (report.top_waste_items) {\n    if (Array.isArray(report.top_waste_items.by_cost)) {\n      report.top_waste_items.by_cost = report.top_waste_items.by_cost.map(i => ({\n        item_name: i['Item Name'],\n        total_waste_cost: toNumber(i['Total Waste Cost']),\n      }));\n    }\n    if (Array.isArray(report.top_waste_items.by_quantity)) {\n      report.top_waste_items.by_quantity = report.top_waste_items.by_quantity.map(i => ({\n        item_name: i['Item Name'],\n        quantity_wasted: toNumber(i['Quantity Wasted']),\n        unit: i['Unit'] || null,\n      }));\n    }\n  }\n\n  // 7. Normalize reason_summary counts\n  if (report.reason_summary && typeof report.reason_summary === 'object') {\n    Object.entries(report.reason_summary).forEach(([reason, count]) => {\n      report.reason_summary[reason] = toNumber(count);\n    });\n  }\n\n  // 8. Normalize action_evaluation.alignment\n  if (report.action_evaluation?.alignment && Array.isArray(report.action_evaluation.alignment)) {\n    report.action_evaluation.alignment = report.action_evaluation.alignment.map(i => ({\n      reason: i.reason || null,\n      action: i.action || null,\n      evaluation: i.evaluation || null,\n    }));\n  }\n\n  // 9. Ensure recommendations is an array with normalized entries\n  if (Array.isArray(report.recommendations)) {\n    report.recommendations = report.recommendations.map(r => ({\n      area: r.area || null,\n      issue: r.issue || null,\n      recommendation: r.recommendation || null,\n      reference: r.reference || null,\n    }));\n  } else {\n    report.recommendations = [];\n  }\n\n  // Return item with normalized JSON\n  return { json: report };\n}\n\n// Process all items\nreturn items.map(item => normalizeAI(item));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "711a1507-04e4-4edf-b5fc-35b66da9338c",
      "name": "食物浪费聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        1220
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "RvSkIBjP48ORJKhU",
          "name": "Google Gemini(PaLM) Api account - test"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1328564d-4298-46ad-a08d-f5853ed57b03",
      "name": "食物浪费思考",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        1220
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "78d8252d-4a7b-4585-8d95-c1be3810000e",
      "name": "获取客户反馈",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -360,
        1500
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1969429831,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit#gid=1969429831",
          "cachedResultName": "feedback page"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BZnpfYjOlu2C_N-CnSiChJpRsYX6hsLWBMP_Lo4ZD_A/edit?usp=drivesdk",
          "cachedResultName": "daily report 13-07-2025"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "ScSS2KxGQULuPtdy",
          "name": "Google Sheets- test"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "4aa3e303-d0cb-4973-a7ee-b8cc90d8960f",
      "name": "规范化反馈条目",
      "type": "n8n-nodes-base.code",
      "position": [
        -140,
        1500
      ],
      "parameters": {
        "jsCode": "// Fetch all incoming items\nconst items = $input.all();\n\n// Extract the raw row data (each item.json is one row)\nconst rawRows = items.map(item => item.json);\n\n// Bundle everything into a single field\nconst payload = { rows: rawRows };\n\n// Return a single output item whose json contains your full dataset\nreturn [{ json: { data: payload } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3b1c4778-5936-4fde-8d74-7b24fd266a56",
      "name": "AI反馈摘要生成器",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        80,
        1500
      ],
      "parameters": {
        "text": "={{ $json.data }}",
        "options": {
          "systemMessage": "You are a Customer Feedback Analyst AI for a restaurant. You receive structured JSON data representing feedback entries, each with:\n- Date, Customer ID, Dish Name\n- Overall Rating, Food Quality, Service Rating, Ambiance Rating\n- Feedback Type (Positive, Neutral, Complaint)\n- Comments (textual feedback)\n\nTasks:\n1. **Validate** each record (check presence/type of fields).\n2. **Aggregate Ratings & Sentiment**:\n   • Calculate average overall, food, service, and ambiance ratings.\n   • Count feedback types (Positive, Neutral, Complaint).\n3. **Dish-Level Insights**:\n   • For each dish, compute average ratings and sentiment distribution.\n   • Identify dishes with highest/lowest overall satisfaction.\n4. **Sentiment & Comments Analysis**:\n   • Detect common themes and sentiment polarity (positive/negative).\n   • Highlight key recurring issues or praises (e.g., cold food, freshness).\n5. **Actionable Recommendations**:\n   • Based on insights, suggest improvements (e.g., staff training for slow service, adjusting spice levels).\n   • If specific dishes underperform, advise focused menu review or training.\n   • For high-performing dishes, recommend upsell or highlight in marketing.\n6. **Output Format**:\n   Return valid JSON only, structured as:\n   {\n     \"validation\": {...},\n     \"summary_ratings\": {...},\n     \"feedback_counts\": {...},\n     \"dish_insights\": [...],\n     \"common_themes\": [...],\n     \"recommendations\": [...]\n   }\nNo additional text or markdown—only the JSON.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "b1d3646d-dbed-4e94-94aa-b6836724ffdd",
      "name": "格式化反馈AI输出",
      "type": "n8n-nodes-base.code",
      "position": [
        456,
        1500
      ],
      "parameters": {
        "jsCode": "/**\n * n8n Code node: normalize AI agent JSON feedback report\n * Input: item.json.output contains the raw JSON wrapped in ```json ... ```\n */\nfunction normalizeAI(item) {\n  let raw = item.json.output;\n  if (typeof raw !== 'string') {\n    throw new Error('Expected output string in item.json.output');\n  }\n\n  // 1. Remove ```json and trailing ``` fences\n  raw = raw\n    .trim()\n    .replace(/^```json\\s*/, '')\n    .replace(/```$/, '')\n    .trim(); // Adapted from StackOverflow advice :contentReference[oaicite:1]{index=1}\n\n  // 2. Parse JSON\n  let report;\n  try {\n    report = JSON.parse(raw);\n  } catch (e) {\n    throw new Error('Invalid JSON from AI: ' + e.message);\n  }\n\n  // 3. Utility to coerce numeric values\n  const toNum = v => {\n    const n = Number(v);\n    return isNaN(n) ? null : n;\n  };\n\n  // Normalize summary_ratings\n  if (report.summary_ratings) {\n    ['average_overall_rating', 'average_food_quality', 'average_service_rating', 'average_ambiance_rating']\n      .forEach(key => { report.summary_ratings[key] = toNum(report.summary_ratings[key]); });\n  }\n\n  // Normalize feedback_counts\n  if (report.feedback_counts) {\n    ['total', 'positive', 'neutral', 'complaint']\n      .forEach(key => { report.feedback_counts[key] = toNum(report.feedback_counts[key]); });\n  }\n\n  // Normalize dish_insights array\n  if (Array.isArray(report.dish_insights)) {\n    report.dish_insights = report.dish_insights.map(d => {\n      ['average_overall_rating','average_food_quality','average_service_rating','average_ambiance_rating']\n        .forEach(k => { d[k] = toNum(d[k]); });\n      d.record_count = toNum(d.record_count);\n      return d;\n    });\n  }\n\n  // Normalize common_themes array\n  if (Array.isArray(report.common_themes)) {\n    report.common_themes = report.common_themes.map(t => {\n      t.mentions = toNum(t.mentions);\n      return t;\n    });\n  }\n\n  // Normalize recommendations array\n  if (Array.isArray(report.recommendations)) {\n    report.recommendations = report.recommendations.map(r => ({\n      area: r.area,\n      priority: r.priority,\n      action: r.action,\n      dish_specific: r.dish_specific,\n    }));\n  }\n\n  return { json: report };\n}\n\n// Process all items\nreturn items.map(item => normalizeAI(item));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9bfd69bc-c1cc-4d13-96ab-926b4099d93b",
      "name": "Google Gemini聊天模型用于反馈",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        1720
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "RvSkIBjP48ORJKhU",
          "name": "Google Gemini(PaLM) Api account - test"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "27bf7481-e439-400f-88df-53482cd5e0a0",
      "name": "反馈思考工具",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        1720
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ff5f1621-cf24-409a-bd3d-d5930ba8acf5",
      "name": "合并所有洞察",
      "type": "n8n-nodes-base.merge",
      "position": [
        676,
        1100
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.1
    },
    {
      "id": "fb7c7b39-e17d-48d4-8562-bab407e1256b",
      "name": "等待所有数据处理",
      "type": "n8n-nodes-base.wait",
      "position": [
        896,
        1100
      ],
      "webhookId": "50615ec2-df7e-4021-8bc0-5096013df039",
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "949a7b7f-0155-44cc-b30c-624e27a16295",
      "name": "准备最终邮件输入",
      "type": "n8n-nodes-base.code",
      "position": [
        1116,
        1100
      ],
      "parameters": {
        "jsCode": "// Fetch all incoming items\nconst items = $input.all();\n\n// Extract the raw row data (each item.json is one row)\nconst rawRows = items.map(item => item.json);\n\n// Bundle everything into a single field\nconst payload = { rows: rawRows };\n\n// Return a single output item whose json contains your full dataset\nreturn [{ json: { data: payload } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "300a9fad-55ed-438e-9398-b059285d79a1",
      "name": "AI生成的每日摘要",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1336,
        1100
      ],
      "parameters": {
        "text": "={{ $json.data }}",
        "options": {
          "systemMessage": "You are the Restaurant Daily Performance Analyst AI. You will receive three structured JSON datasets for the same date:\n1. Sales Report\n2. Food Waste Report\n3. Customer Feedback Report\n\nYour single output must be the **email_body**—formatted plain text only (no JSON, no markdown)—ready to send via email. It must include:\n\n- **Greeting** and acknowledgment of date.\n- **Key Metrics at a glance** (bulleted): sales (revenue, profit, margin), waste cost, average customer rating, sentiment counts.\n- **Detailed Insights**, covering:\n  • Sales highlights & weather context  \n  • Waste issues & top waste items  \n  • Customer feedback summary & critical complaints  \n  • Cross‑analysis: link dishes across datasets where issues align\n- **Next‑day Suggestions** with clear, actionable steps under categories:\n  • Menu & upsell  \n  • Quality & service improvements  \n  • Waste reduction & inventory actions  \n  • Promotions (e.g., weather-based combos)\n\n- **Sign-off**: “Regards,\\nDaily Analytics Bot”\n\nEnsure each insight and suggestion is included. The tone should be professional, concise, and directive.\n\nExample structure:\n\nHello Team,\n\nKey Metrics at a glance:\n• Total Revenue: $X\n• Total Profit: $Y\n• Average Profit Margin: Z%\n• Total Waste Cost: $W\n• Average Customer Rating: R / 5\n• Customer Feedback: P Positive, N Neutral, C Complaints\n\nDetailed Insights:\n\nSales: explanation…\n\nWaste: explanation…\n\nFeedback: explanation…\n\nCross‑Analysis: bullet list…\n\nNext‑day Suggestions:\n\nMenu & upsell: …\n\nQuality & service: …\n\nWaste & inventory: …\n\nPromotions: …\n\n\nOutput **only** this email body as plain text—no JSON wrapper or other output.\n"
        },
        "promptType": "define"
      },
      "executeOnce": true,
      "typeVersion": 1.9,
      "alwaysOutputData": true
    },
    {
      "id": "2ca4c4ab-4a19-4dfd-a1bb-86cc61209bf6",
      "name": "格式化最终邮件内容",
      "type": "n8n-nodes-base.code",
      "position": [
        1712,
        1100
      ],
      "parameters": {
        "jsCode": "/**\n * n8n Function/Code node: Normalize AI email output\n * Input: item.json.output contains the raw email text (possibly with fences)\n */\nfunction normalizeEmail(item) {\n  let text = item.json.output;\n  if (typeof text !== 'string') {\n    throw new Error('Expected raw email text in item.json.output');\n  }\n\n  // 1. Clean common wrappers & prefixes\n  text = text\n    .replace(/^```(?:json|text)?\\n?/, '')\n    .replace(/\\n?```$/, '')\n    .replace(/^(Here is the output:|Output:)\\s*/, '');\n\n  // 2. Trim whitespace\n  text = text.trim();\n\n  return { json: { email_body: text } };\n}\n\n// Map over all items\nreturn items.map(item => normalizeEmail(item));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8c1b6c2e-b910-40ac-bf27-31aaf75a8828",
      "name": "通过Gmail邮件发送最终报告",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1932,
        1100
      ],
      "webhookId": "9f54cf6e-88c6-4e17-8eb1-f07a87c9d381",
      "parameters": {
        "sendTo": "abc@gmail.com",
        "message": "={{ $json.email_body }}\n",
        "options": {},
        "subject": "Next monday prediction",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "PcTqvGU9uCunfltE",
          "name": "Gmail account - test"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "6e30cc8b-6a89-412b-b291-cf85434bb375",
  "connections": {
    "Think Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Sales Insights Generator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Google Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Sales Insights Generator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Think for summary": {
      "ai_tool": [
        [
          {
            "node": "AI-Generated Daily Summary",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Combine All Insights": {
      "main": [
        [
          {
            "node": "Wait for All Data Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Waste Data": {
      "main": [
        [
          {
            "node": "AI Waste Reduction Insights Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Think For Food Waste": {
      "ai_tool": [
        [
          {
            "node": "AI Waste Reduction Insights Generator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Daily Report Scheduler": {
      "main": [
        [
          {
            "node": "Fetch Daily Sales Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Daily Food Waste Records",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Customer Feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Daily Sales Data": {
      "main": [
        [
          {
            "node": "Normalize Sales Records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Sales AI Output": {
      "main": [
        [
          {
            "node": "Combine All Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Waste AI Output": {
      "main": [
        [
          {
            "node": "Combine All Insights",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Fetch Customer Feedback": {
      "main": [
        [
          {
            "node": "Normalize Feedback Entries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Sales Records": {
      "main": [
        [
          {
            "node": "AI Sales Insights Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Think Tool For Feedback": {
      "ai_tool": [
        [
          {
            "node": "AI Feedback Summary Generator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Chat Model For Food Waste": {
      "ai_languageModel": [
        [
          {
            "node": "AI Waste Reduction Insights Generator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Format Feedback AI Output": {
      "main": [
        [
          {
            "node": "Combine All Insights",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Prepare Final Email Input": {
      "main": [
        [
          {
            "node": "AI-Generated Daily Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI-Generated Daily Summary": {
      "main": [
        [
          {
            "node": "Format Final Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Final Email Content": {
      "main": [
        [
          {
            "node": "Email Final Report via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Feedback Entries": {
      "main": [
        [
          {
            "node": "AI Feedback Summary Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Sales Insights Generator": {
      "main": [
        [
          {
            "node": "Format Sales AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for All Data Processing": {
      "main": [
        [
          {
            "node": "Prepare Final Email Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Feedback Summary Generator": {
      "main": [
        [
          {
            "node": "Format Feedback AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Daily Food Waste Records": {
      "main": [
        [
          {
            "node": "Normalize Waste Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model for summary": {
      "ai_languageModel": [
        [
          {
            "node": "AI-Generated Daily Summary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Waste Reduction Insights Generator": {
      "main": [
        [
          {
            "node": "Format Waste AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model For Feedback": {
      "ai_languageModel": [
        [
          {
            "node": "AI Feedback Summary Generator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 文档提取, AI 摘要总结

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量33
分类2
节点类型10
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Oneclick AI Squad

Oneclick AI Squad

@oneclick-ai

The AI Squad Initiative is a pioneering effort to build, automate and scale AI-powered workflows using n8n.io. Our mission is to help individuals and businesses integrate AI agents seamlessly into their daily operations from automating tasks and enhancing productivity to creating innovative, intelligent solutions. We design modular, reusable AI workflow templates that empower creators, developers and teams to supercharge their automation with minimal effort and maximum impact.

外部链接
在 n8n.io 查看

分享此工作流