{
  "id": "Dvr7DN12sJBV7qyr",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Restaurant Daily Insights Automation \u2013 AI-Powered Email Report Generator",
  "tags": [],
  "nodes": [
    {
      "id": "d25f6d93-48a7-4213-9160-915a0a640f01",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        0
      ],
      "parameters": {
        "width": 600,
        "height": 200,
        "content": "## Restaurant Daily Report Workflow \ud83c\udf7d\ufe0f\n\nThis workflow automates the generation and delivery of a daily performance report for a restaurant. It collects data from three sources: **Sales**, **Food Waste**, and **Customer Feedback**, processes them using AI, merges the insights, generates an email summary, and sends it out."
      },
      "typeVersion": 1
    },
    {
      "id": "577a3cfe-074e-41ef-8309-55ff22d523cd",
      "name": "Sales Data Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -760,
        380
      ],
      "parameters": {
        "color": 5,
        "width": 1320,
        "height": 500,
        "content": "## Sales Data Analysis \ud83d\udcc8\n\n1. Fetches daily sales records from Google Sheets.\n2. Bundles raw sales rows into a single JSON object for the AI agent.\n3. An AI Agent processes sales data, performing validation, calculations (e.g., profit margin), identifying top/bottom performers, and generating recommendations.\n4. Normalizes the AI's JSON output for consistent data structure."
      },
      "typeVersion": 1
    },
    {
      "id": "85dc03eb-fef1-47ec-896a-7818e1f3eaf9",
      "name": "Food Waste Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        1020
      ],
      "parameters": {
        "color": 4,
        "width": 800,
        "height": 260,
        "content": "## Food Waste Analysis \ud83d\uddd1\ufe0f\n\n1. Retrieves daily food waste entries from Google Sheets.\n2. Formats the raw waste data into a single JSON payload for the AI agent.\n3. An AI Agent analyzes food waste data, validating records, calculating costs, identifying top waste items, and suggesting prevention actions.\n4. Cleans and normalizes the AI's JSON output for structured use."
      },
      "typeVersion": 1
    },
    {
      "id": "93dd3719-b6f1-478c-af4b-f156be0391a8",
      "name": "Customer Feedback Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        1420
      ],
      "parameters": {
        "color": 6,
        "width": 1300,
        "height": 800,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Customer Feedback Analysis \ud83d\udcac\n\n1. Reads daily customer feedback entries from Google Sheets.\n2. Bundles raw feedback rows into a single JSON object for the AI agent.\n3. An AI Agent processes customer feedback, aggregating ratings, analyzing sentiment, identifying common themes, and providing actionable recommendations.\n4. Ensures the AI's JSON output is correctly parsed and normalized."
      },
      "typeVersion": 1
    },
    {
      "id": "7f24cc28-2b7e-4563-a27e-924bcb105919",
      "name": "Merge & Email Creation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        646,
        760
      ],
      "parameters": {
        "color": 2,
        "width": 600,
        "height": 500,
        "content": "## Merge & Email Creation \ud83d\udce7\n\n1. Combines the processed sales, food waste, and customer feedback data from the three AI analysis branches into a single data item.\n2. A Code node structures the merged data for the final AI email summary generator.\n3. An AI Agent compiles the combined data into a comprehensive, plain-text email body, including key metrics, detailed insights, and next-day suggestions."
      },
      "typeVersion": 1
    },
    {
      "id": "3bc8c564-d29a-42bd-a529-7f7a52f32077",
      "name": "Send Daily Report",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        800
      ],
      "parameters": {
        "color": 3,
        "width": 380,
        "height": 480,
        "content": "## Send Daily Report \ud83d\ude80\n\n1. A Code node ensures the AI-generated email body is clean and ready for sending.\n\n2. Sends the final daily performance summary email via Gmail to the specified recipient."
      },
      "typeVersion": 1
    },
    {
      "id": "56b5f162-622c-4d29-be8d-634886b55e33",
      "name": "Google Gemini Chat Model for summary",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1364,
        1320
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "733727b2-114b-485e-96eb-0b2c04cf58af",
      "name": "Think for summary",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        1484,
        1320
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "90a4559d-6b77-48e8-a2b9-63950eab06aa",
      "name": "Daily Report Scheduler",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -580,
        1100
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 22
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "28b16c0f-7733-4fee-bff9-48c9cdad7020",
      "name": "Fetch Daily Sales Data",
      "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": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "5c0adaba-b6e4-47e3-9d05-13c3088f1e84",
      "name": "Normalize Sales Records",
      "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 Sales Insights Generator",
      "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     \u2022 Total Cost = Quantity Sold \u00d7 Cost Per Unit  \n     \u2022 Profit Amount = Total Revenue \u2212 Total Cost  \n     \u2022 Actual Profit Margin (%) = (Profit Amount \u00f7 Total Revenue) \u00d7\u202f100 (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     \u2022 Summary Metrics  \n     \u2022 Top/Bottom Performers  \n     \u2022 Weather/Peak-Hour Analysis  \n     \u2022 Recommendations  \n     \u2022 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": "Format Sales AI Output",
      "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 Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        820
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e85b4ee4-6904-4ef6-a951-c147829f6421",
      "name": "Think Tool",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        820
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "170f519d-abc5-4ecb-ac84-6c359f983ce1",
      "name": "Fetch Daily Food Waste Records",
      "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": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "ab2eb33f-3306-4046-9f4f-4aaf66d6d53d",
      "name": "Normalize Waste Data",
      "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 Waste Reduction Insights Generator",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        80,
        1000
      ],
      "parameters": {
        "text": "={{ $json.data }}",
        "options": {
          "systemMessage": "You are a Food\u2011Waste 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 \u00d7 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\u2011cost waste items and highest\u2011quantity 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\u2011practice 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\u2011levels, FIFO adoption, supply\u2011chain 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\u2014no markdown or extra text.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "93c9c5ee-df52-4b7f-a5c5-62a6145f65c2",
      "name": "Format Waste AI Output",
      "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": "Chat Model For Food Waste",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        1220
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1328564d-4298-46ad-a08d-f5853ed57b03",
      "name": "Think For Food Waste",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        1220
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "78d8252d-4a7b-4585-8d95-c1be3810000e",
      "name": "Fetch Customer Feedback",
      "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": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "4aa3e303-d0cb-4973-a7ee-b8cc90d8960f",
      "name": "Normalize Feedback Entries",
      "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 Feedback Summary Generator",
      "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   \u2022 Calculate average overall, food, service, and ambiance ratings.\n   \u2022 Count feedback types (Positive, Neutral, Complaint).\n3. **Dish-Level Insights**:\n   \u2022 For each dish, compute average ratings and sentiment distribution.\n   \u2022 Identify dishes with highest/lowest overall satisfaction.\n4. **Sentiment & Comments Analysis**:\n   \u2022 Detect common themes and sentiment polarity (positive/negative).\n   \u2022 Highlight key recurring issues or praises (e.g., cold food, freshness).\n5. **Actionable Recommendations**:\n   \u2022 Based on insights, suggest improvements (e.g., staff training for slow service, adjusting spice levels).\n   \u2022 If specific dishes underperform, advise focused menu review or training.\n   \u2022 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\u2014only the JSON.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "b1d3646d-dbed-4e94-94aa-b6836724ffdd",
      "name": "Format Feedback AI Output",
      "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 Chat Model For Feedback",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        108,
        1720
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "27bf7481-e439-400f-88df-53482cd5e0a0",
      "name": "Think Tool For Feedback",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        228,
        1720
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ff5f1621-cf24-409a-bd3d-d5930ba8acf5",
      "name": "Combine All Insights",
      "type": "n8n-nodes-base.merge",
      "position": [
        676,
        1100
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.1
    },
    {
      "id": "fb7c7b39-e17d-48d4-8562-bab407e1256b",
      "name": "Wait for All Data Processing",
      "type": "n8n-nodes-base.wait",
      "position": [
        896,
        1100
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "949a7b7f-0155-44cc-b30c-624e27a16295",
      "name": "Prepare Final Email Input",
      "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-Generated Daily Summary",
      "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**\u2014formatted plain text only (no JSON, no markdown)\u2014ready 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  \u2022 Sales highlights & weather context  \n  \u2022 Waste issues & top waste items  \n  \u2022 Customer feedback summary & critical complaints  \n  \u2022 Cross\u2011analysis: link dishes across datasets where issues align\n- **Next\u2011day Suggestions** with clear, actionable steps under categories:\n  \u2022 Menu & upsell  \n  \u2022 Quality & service improvements  \n  \u2022 Waste reduction & inventory actions  \n  \u2022 Promotions (e.g., weather-based combos)\n\n- **Sign-off**: \u201cRegards,\\nDaily Analytics Bot\u201d\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\u2022 Total Revenue: $X\n\u2022 Total Profit: $Y\n\u2022 Average Profit Margin: Z%\n\u2022 Total Waste Cost: $W\n\u2022 Average Customer Rating: R / 5\n\u2022 Customer Feedback: P Positive, N Neutral, C Complaints\n\nDetailed Insights:\n\nSales: explanation\u2026\n\nWaste: explanation\u2026\n\nFeedback: explanation\u2026\n\nCross\u2011Analysis: bullet list\u2026\n\nNext\u2011day Suggestions:\n\nMenu & upsell: \u2026\n\nQuality & service: \u2026\n\nWaste & inventory: \u2026\n\nPromotions: \u2026\n\n\nOutput **only** this email body as plain text\u2014no JSON wrapper or other output.\n"
        },
        "promptType": "define"
      },
      "executeOnce": true,
      "typeVersion": 1.9,
      "alwaysOutputData": true
    },
    {
      "id": "2ca4c4ab-4a19-4dfd-a1bb-86cc61209bf6",
      "name": "Format Final Email Content",
      "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": "Email Final Report via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1932,
        1100
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.email_body }}\n",
        "options": {},
        "subject": "Next monday prediction",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "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
          }
        ]
      ]
    }
  }
}