{
  "id": "iiX2o66wTdLNtnhT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Meal Planner: Cost Tracking, Leftover Recipes & Nutrition Diary in Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "3be82f45-7aad-4c17-80e6-a5ff30488753",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -1424,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b0771629-a41c-498f-846d-032d4c9d752e",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -1296,
        112
      ],
      "parameters": {
        "jsonSchemaExample": "[\n  {\n    \"json\": {\n      \"Date\": \"2023-10-27\",\n      \"Item\": \"Braised Pork Belly\",\n      \"Ingredients\": \"Pork Belly (block)\",\n      \"Ingredient Cost\": 800,\n      \"Unit Price\": \"1,600$/1kg\",\n      \"Quantity\": \"500g\",\n      \"Total\": 800,\n      \"Cost\": 1070,\n      \"Leftover Ingredients\": \"None\"\n    }\n  },\n  {\n    \"json\": {\n      \"Date\": \"2023-10-27\",\n      \"Item\": \"Braised Pork Belly\",\n      \"Ingredients\": \"Ginger\",\n      \"Ingredient Cost\": 50,\n      \"Unit Price\": \"100\u5186/100g\",\n      \"Quantity\": \"1 piece\",\n      \"Total\": 50,\n      \"Cost\": 1070,\n      \"Leftover Ingredients\": \"None\"\n    }\n  }\n]"
      },
      "typeVersion": 1.3
    },
    {
      "id": "2b8ad996-0b53-43b5-a391-3f0efc39d3a0",
      "name": "Append row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1072,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "Cost": "={{ $json.output[0].json['Cost'] }}",
            "Date": "={{ $json.output[0].json['Date'] }}",
            "Item": "={{ $json.output[0].json['Item'] }}",
            "Total": "={{ $json.output[0].json['Total'] }}",
            "Quantity": "={{ $json.output[0].json['Quantity'] }}",
            "Unit Price": "={{ $json.output[0].json['Unit Price'] }}",
            "Ingredients": "={{ $json.output[0].json['Ingredients'] }}",
            "Ingredient Cost": "={{ $json.output[0].json['Ingredient Cost'] }}",
            "Leftover Ingredients": "={{ $json.output[0].json['Leftover Ingredients'] }}"
          },
          "schema": [
            {
              "id": "Date",
              "required": false,
              "displayName": "Date",
              "canBeUsedToMatch": true
            },
            {
              "id": "Item",
              "required": false,
              "displayName": "Item",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ingredients",
              "required": false,
              "displayName": "Ingredients",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ingredient Cost",
              "required": false,
              "displayName": "Ingredient Cost",
              "canBeUsedToMatch": true
            },
            {
              "id": "Unit Price",
              "required": false,
              "displayName": "Unit Price",
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "required": false,
              "displayName": "Quantity",
              "canBeUsedToMatch": true
            },
            {
              "id": "Total",
              "required": false,
              "displayName": "Total",
              "canBeUsedToMatch": true
            },
            {
              "id": "Cost",
              "required": false,
              "displayName": "Cost",
              "canBeUsedToMatch": true
            },
            {
              "id": "Leftover Ingredients",
              "required": false,
              "displayName": "Leftover Ingredients",
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "output"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "cellFormat": "USER_ENTERED"
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "recipe"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "Recipe List"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "1c6ad655-8149-43f7-bd94-4c1c22570751",
      "name": "Append row in sheet1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        80,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "Diary": "={{ $json.output }}"
          },
          "schema": [
            {
              "id": "Diary",
              "required": false,
              "displayName": "Diary",
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Diary"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "diary"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "Recipe List"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c24b82e7-52b5-44f3-9bd7-e739001d1975",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1648,
        -128
      ],
      "parameters": {
        "path": "your-webhook-path",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "57916f99-a102-4af8-a262-9a23705f2d90",
      "name": "OpenRouter Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        384,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "90922a4d-9fe2-4542-a1bd-b41836212cea",
      "name": "OpenRouter Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -208,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "88c896f1-6444-406f-9b99-6c2cade605b0",
      "name": "OpenRouter Chat Model3",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -784,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "8dea555e-872d-46ab-bfe3-cbaa25acb606",
      "name": "Append row in sheet2",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -496,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Menu Agent').item.json.output[0].json['Date'] }}",
            "Ingredients": "={{ $json.output }}"
          },
          "schema": [
            {
              "id": "Date",
              "required": false,
              "displayName": "Date",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ingredients",
              "required": false,
              "displayName": "Ingredients",
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "leftovers"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": "Recipe List"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "9d3cb079-4053-4f31-a82e-8def53383c4c",
      "name": "Create Direct Message",
      "type": "n8n-nodes-base.twitter",
      "position": [
        656,
        -128
      ],
      "parameters": {
        "text": "={{ $json.output }}",
        "user": {
          "__rl": true,
          "mode": "username",
          "value": ""
        },
        "resource": "directMessage",
        "additionalFields": {}
      },
      "typeVersion": 2
    },
    {
      "id": "a06637b5-b25b-4e18-a391-1a459bb733ac",
      "name": "Menu Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1424,
        -128
      ],
      "parameters": {
        "text": "={{ $json.body }}",
        "options": {
          "systemMessage": "You are a housekeeper. From the searched content, output the necessary ingredients and costs. You do not need to show the cost per use for seasonings. Costs should be shown as both the total amount for one shopping trip and the unit price. Include units for ingredients and prices."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "62fa2287-a374-4a6d-9770-f74323381a84",
      "name": "Leftovers Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -848,
        -128
      ],
      "parameters": {
        "text": "={{ $('Menu Agent').item.json.output }}",
        "options": {
          "systemMessage": "You are a specialist in utilizing unused ingredients effectively. List leftover ingredients. Suggest three recipes that can be cooked with the remaining ingredients. You do not need to describe the cooking method."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "5bf1ec16-a1ee-4b0c-b06d-25662fe1fb56",
      "name": "Nutritionist Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -272,
        -128
      ],
      "parameters": {
        "text": "={{ $('Menu Agent').item.json.output }}",
        "options": {
          "systemMessage": "You are a nutritionist specialist. Create a diary-style entry with dietary advice and what nutrients should be consumed."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "52e4b8a7-cb34-4db7-a9ed-267dbd5ecfe3",
      "name": "Post Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        304,
        -128
      ],
      "parameters": {
        "text": "={{ $json['Diary'] }}",
        "options": {
          "systemMessage": "You are an X (Twitter) poster. Create a compelling post to attract viewers."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "1ce6d15e-6710-4138-a7eb-bb8f96db5240",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2320,
        -592
      ],
      "parameters": {
        "width": 496,
        "height": 624,
        "content": "### How it works / What it does\n\nThis advanced workflow acts as a comprehensive culinary assistant. Triggered by a new menu item, it performs several key functions:\n\n*   **Cost and Ingredient Tracking:** A \"Menu Agent\" uses AI to analyze your input (e.g., a recipe or dish) and extract a detailed list of ingredients, their associated costs, unit prices, and total cost, then logs this into a Google Sheet as a \"Recipe List.\"\n*   **Leftover Management:** A \"Leftovers Agent\" identifies any unused ingredients from your planned dish and suggests three new recipes to utilize them, helping to minimize food waste. This information is also recorded in a Google Sheet.\n*   **Nutritional Diary:** A \"Nutritionist Agent\" generates a diary-style entry with dietary advice based on the meal, highlighting key nutrients and offering personalized suggestions. This entry is appended to a \"Diary\" Google Sheet.\n*   **Social Media Promotion:** A \"Post Agent\" takes the nutritional diary entry and transforms it into an engaging social media post (specifically for X/Twitter in this template), which is then sent as a direct message, ready for you to share with your followers.\n\n### Requirements\n\n*   An n8n instance.\n*   A Google account with Google Sheets enabled.\n*   An OpenRouter API key.\n*   A Twitter (X) account with developer access to send Direct Messages"
      },
      "typeVersion": 1
    },
    {
      "id": "8df4e680-cb25-4d27-8623-347c12727dcd",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1152,
        -416
      ],
      "parameters": {
        "color": 7,
        "width": 1392,
        "height": 448,
        "content": " **Google Sheets Integration:**\n\n    *   You need to set up a Google Sheets credential for your n8n instance.\n    *   Create a Google Sheet document (e.g., \"Recipe List\"). Within this document, create three sheets:\n        *   **\"Recipe:** This sheet will store your menu items, ingredients, costs, etc. Ensure it has columns for `Date`, `Item`, `Ingredients`, `Ingredient Cost`, `Unit Price`, `Quantity`, `Total`, `Cost`, and `Leftover Ingredients`.\n        *   **\"leftovers\" (Leftovers):** This sheet will store suggested recipes for leftover ingredients. Ensure it has columns for `Date` and `Ingredients`.\n        *   **\"diary\" (Diary):** This sheet will store your nutritional diary entries. Ensure it has a column for `Diary`.\n    *   In the \"Append row in sheet\", \"Append row in sheet1\", and \"Append row in sheet2\" nodes, replace the `Document ID` with the ID of your Google Sheet. For \"Sheet Name,\" ensure you select the correct sheet (e.g., \"\u30ec\u30b7\u30d4\", \"diary\", \"leftovers\") from the dropdown."
      },
      "typeVersion": 1
    },
    {
      "id": "410700dd-3d65-4d9a-8211-2f58b697e03c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1696,
        -416
      ],
      "parameters": {
        "color": 7,
        "width": 192,
        "height": 448,
        "content": "  **Webhook Trigger:**\n\n    *   The workflow starts with a Webhook. Copy the webhook URL from the \"Webhook\" node. You will send your menu item input to this URL."
      },
      "typeVersion": 1
    },
    {
      "id": "d559ed46-36d3-4021-a74f-4e179dca7115",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        -416
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 448,
        "content": "**Twitter Integration:**\n    *   Set up your Twitter credentials for the \"Create Direct Message\" node.\n    *   In the \"Create Direct Message\" node, specify the `User` (username) to whom the direct message should be sent. This is typically your own Twitter handle or a test account.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bc536540-7bb6-4efb-84b1-366a01c56907",
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Menu Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Menu Agent": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Agent": {
      "main": [
        [
          {
            "node": "Create Direct Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leftovers Agent": {
      "main": [
        [
          {
            "node": "Append row in sheet2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Nutritionist Agent": {
      "main": [
        [
          {
            "node": "Append row in sheet1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append row in sheet": {
      "main": [
        [
          {
            "node": "Leftovers Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append row in sheet1": {
      "main": [
        [
          {
            "node": "Post Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append row in sheet2": {
      "main": [
        [
          {
            "node": "Nutritionist Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Menu Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Post Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "Nutritionist Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "Leftovers Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Menu Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}