{
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        5792,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000001",
      "name": "Execute Workflow Trigger"
    },
    {
      "parameters": {
        "jsCode": "// Merge defaults with any values passed from calling workflow\nconst input = $input.first().json || {};\nconst defaults = {\n  sheetId: '1cD3xVEhfP5Iy-PwumXymLctXgI1X_mbEUlhjHDZPhto', // YOUR_GOOGLE_SHEET_ID \u2014 Steward_Deals gdrive Sheet\n  sheetName: 'Requirements',\n  trackingSheetName: 'Tracked Prices',\n  historySheetName: 'Price History',\n  region: 'Switzerland',\n  retailers: 'Digitec, Galaxus, Toppreise, IKEA, Interdiscount, MediaMarkt, Brack, Microspot',\n  currency: 'CHF'\n};\n\n// Input values override defaults\nreturn [{ json: { ...defaults, ...input } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5992,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000002",
      "name": "Config"
    },
    {
      "parameters": {
        "jsCode": "// Parse incoming command from menu-handler\nconst input = $input.first().json;\nconst text = (input.text || '').trim();\nconst chatId = input.chatId;\n\n// Internal operations (called by other workflows)\nif (text.toLowerCase() === 'check_prices') {\n  return [{ json: { operation: 'check_prices', chatId } }];\n}\n\n// Explicit track command\nif (text.toLowerCase().startsWith('track ')) {\n  const remainder = text.substring(6).trim();\n  const urlMatch = remainder.match(/https?:\\/\\/\\S+/);\n  const url = urlMatch ? urlMatch[0] : '';\n  const notifyMode = remainder.toLowerCase().includes('on_change') ? 'on_change' : 'always';\n  return [{ json: { operation: 'track', url, notifyMode, chatId } }];\n}\n\n// Untrack / stop tracking\nif (text.toLowerCase().match(/^(untrack|stop tracking|stop)\\s/)) {\n  const identifier = text.replace(/^(untrack|stop tracking|stop)\\s+/i, '').trim();\n  return [{ json: { operation: 'untrack', identifier, chatId } }];\n}\n\n// List tracked items\nif (text.toLowerCase().match(/^(tracked|list|tracking|what am i tracking)/)) {\n  return [{ json: { operation: 'tracked', chatId } }];\n}\n\n// History command \u2014 single product price chart\nif (text.toLowerCase().startsWith('history ')) {\n  const identifier = text.substring(8).trim();\n  return [{ json: { operation: 'history', identifier, chatId } }];\n}\n\n// Plot command \u2014 all products overlay chart\nif (text.toLowerCase().match(/^(plot|chart|graph|trends?)$/)) {\n  return [{ json: { operation: 'plot', chatId } }];\n}\n\n// URL detection \u2014 message contains a URL, treat as track\nconst urlInText = text.match(/https?:\\/\\/\\S+/);\nif (urlInText && !['add','remove','pause','resume','digest'].some(op => text.toLowerCase().startsWith(op))) {\n  const url = urlInText[0];\n  const notifyMode = text.toLowerCase().includes('on_change') ? 'on_change' : 'always';\n  return [{ json: { operation: 'track', url, notifyMode, chatId } }];\n}\n\n// Original operations (unchanged)\nlet operation = 'digest';\nlet category = '';\nlet constraints = '';\nlet maxPrice = '';\n\nif (text.toLowerCase().startsWith('add ')) {\n  operation = 'add';\n  const parts = text.substring(4).trim().split(/\\s+/);\n  category = parts[0] || '';\n  const priceIdx = parts.findIndex((p, i) => i > 0 && /^\\d+/.test(p));\n  if (priceIdx > 0) {\n    maxPrice = parts[priceIdx];\n    constraints = parts.slice(priceIdx + 1).join(' ');\n  } else {\n    constraints = parts.slice(1).join(' ');\n  }\n} else if (text.toLowerCase().startsWith('remove ')) {\n  operation = 'remove';\n  category = text.substring(7).trim();\n} else if (text.toLowerCase().startsWith('pause ')) {\n  operation = 'pause';\n  category = text.substring(6).trim();\n} else if (text.toLowerCase().startsWith('resume ')) {\n  operation = 'resume';\n  category = text.substring(7).trim();\n} else if (text) {\n  operation = 'digest';\n  category = text.toLowerCase();\n}\n\nreturn [{\n  json: {\n    operation,\n    category: category.toLowerCase(),\n    constraints,\n    maxPrice,\n    chatId\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6212,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000003",
      "name": "Parse Command"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-digest",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "digest",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "digest"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-check-prices",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "check_prices",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "check_prices"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-add",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "add",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "add"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-remove",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "remove",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "remove"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-pause",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "pause",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "pause"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-resume",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "resume",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "resume"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-track",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "track",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "track"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-untrack",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "untrack",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "untrack"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-tracked",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "tracked",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "tracked"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-history",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "history",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "history"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-plot",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "plot",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "plot"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        6432,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000004",
      "name": "Route Operation"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        5632
      ],
      "id": "df100001-0001-0001-0001-000000000005",
      "name": "Load Requirements",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Filter requirements: active only, optional category filter\nconst command = $('Parse Command').first().json;\nconst categoryFilter = command.category;\nconst chatId = command.chatId;\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n  const row = item.json;\n  if ((row.status || '').toLowerCase() !== 'active') return false;\n  if (categoryFilter && (row.category || '').toLowerCase() !== categoryFilter) return false;\n  return true;\n});\n\nif (filtered.length === 0) {\n  // If user asked for a specific category, still show \"not found\"\n  if (categoryFilter) {\n    return [{\n      json: {\n        empty: true,\n        chatId,\n        response: `No active requirements found for \\\"${categoryFilter}\\\".\\n\\nTry: /deals add ${categoryFilter} 500CHF your constraints here`\n      }\n    }];\n  }\n\n  // No requirements at all \u2014 use a demo product so it works out of the box\n  return [{\n    json: {\n      seed: true,\n      category: 'Batteries',\n      constraints: 'rechargeable, AA, 4-pack, NiMH, long-lasting',\n      max_price: '30 CHF',\n      priority: 'low',\n      status: 'active'\n    }\n  }];\n}\n\nreturn filtered;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6880,
        5632
      ],
      "id": "df100001-0001-0001-0001-000000000006",
      "name": "Filter Active"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-empty",
              "leftValue": "={{ $json.empty }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7088,
        5584
      ],
      "id": "df100001-0001-0001-0001-000000000007",
      "name": "Check Empty"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst chatId = $('Parse Command').first().json.chatId;\nconst categories = items.map(i => i.json.category).filter(Boolean);\nconst count = categories.length;\nconst list = categories.join(', ');\nreturn [{ json: { chatId, response: `\ud83d\udd0d Researching ${count} categor${count === 1 ? 'y' : 'ies'}: ${list}...` } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7088,
        5440
      ],
      "id": "df100001-0001-0001-0001-000000000083",
      "name": "Build Indicator"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        7584,
        5808
      ],
      "id": "df100001-0001-0001-0001-000000000009",
      "name": "Loop Requirements"
    },
    {
      "parameters": {
        "messages": {
          "message": [
            {
              "content": "=You are a shopping advisor for {{ $('Config').first().json.region }}. Find the current best value option for:\n\nCategory: {{ $json.category }}\nRequirements: {{ $json.constraints }}\nBudget: {{ $json.max_price }}\nPriority: {{ $json.priority }}\n\nSearch retailers in {{ $('Config').first().json.region }} ({{ $('Config').first().json.retailers }}, etc.) and recommend 2-3 options with:\n- Product name and model\n- Current price in {{ $('Config').first().json.currency }}\n- Where to buy (retailer name)\n- Why it's a good value\n\nFocus on products actually available in {{ $('Config').first().json.region }}. Be concise but include specific prices and retailers."
            }
          ]
        },
        "options": {
          "maxTokens": 1024
        },
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.perplexity",
      "typeVersion": 1,
      "position": [
        7904,
        5776
      ],
      "id": "df100001-0001-0001-0001-000000000010",
      "name": "Perplexity Research",
      "credentials": {
        "perplexityApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Format Perplexity response with category header\nconst requirement = $('Loop Requirements').first().json;\nconst perplexityResponse = $input.first().json;\n\nconst content = perplexityResponse.choices?.[0]?.message?.content || 'No recommendations found.';\n\nconst emoji = {\n  'phone': '\ud83d\udcf1',\n  'desk': '\ud83e\ude91',\n  'laptop': '\ud83d\udcbb',\n  'kitchen': '\ud83c\udf73',\n  'audio': '\ud83c\udfa7',\n  'camera': '\ud83d\udcf7',\n  'tv': '\ud83d\udcfa',\n  'appliance': '\ud83c\udfe0'\n};\n\nconst icon = emoji[requirement.category.toLowerCase()] || '\ud83d\uded2';\n\nreturn [{\n  json: {\n    category: requirement.category,\n    formatted: `${icon} *${requirement.category.toUpperCase()}* (Budget: ${requirement.max_price})\\n\\n${content}`,\n    recommendations: content,\n    last_researched: new Date().toISOString().split('T')[0]\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        8064,
        5792
      ],
      "id": "df100001-0001-0001-0001-000000000011",
      "name": "Format Result"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        7984,
        5632
      ],
      "id": "df100001-0001-0001-0001-000000000012",
      "name": "Collect Results"
    },
    {
      "parameters": {
        "jsCode": "// Combine all formatted results into final digest\nconst results = $input.first().json.data || [];\nconst chatId = $('Parse Command').first().json.chatId;\n\nif (results.length === 0) {\n  return [{\n    json: {\n      chatId,\n      response: '\ud83d\uded2 No recommendations generated.'\n    }\n  }];\n}\n\nconst digest = results.map(r => r.formatted).join('\\n\\n---\\n\\n');\nconst header = `\ud83d\uded2 *Deal Finder Digest*\\n_${results.length} categor${results.length === 1 ? 'y' : 'ies'} researched_\\n\\n`;\n\nreturn [{\n  json: {\n    chatId,\n    response: header + digest\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        8208,
        5632
      ],
      "id": "df100001-0001-0001-0001-000000000013",
      "name": "Build Digest"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "category": "={{ $('Parse Command').first().json.category }}",
            "constraints": "={{ $('Parse Command').first().json.constraints }}",
            "max_price": "={{ $('Parse Command').first().json.maxPrice }}",
            "priority": "medium",
            "status": "active"
          }
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000015",
      "name": "Append Requirement",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $('Parse Command').first().json.url }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "text"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        6672,
        6412
      ],
      "id": "df100001-0001-0001-0001-000000000040",
      "name": "Fetch Product Page"
    },
    {
      "parameters": {
        "jsCode": "const html = $input.first().json.body || $input.first().json.data || '';\nconst url = $('Parse Command').first().json.url;\nconst currency = $('Config').first().json.currency;\n\nlet productName = 'Unknown Product';\nlet currentPrice = null;\n\n// Strategy 1: JSON-LD structured data\nconst jsonLdBlocks = html.match(/<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>[\\s\\S]*?<\\/script>/gi) || [];\nfor (const block of jsonLdBlocks) {\n  try {\n    const jsonStr = block.replace(/<script[^>]*>/, '').replace(/<\\/script>/, '');\n    const ld = JSON.parse(jsonStr);\n    const items = Array.isArray(ld) ? ld : [ld];\n    for (const item of items) {\n      if (item['@type'] === 'Product' || (item['@type']?.includes?.('Product'))) {\n        productName = item.name || productName;\n        const offers = item.offers;\n        if (offers) {\n          const offer = Array.isArray(offers) ? offers[0] : offers;\n          if (offer.price) currentPrice = parseFloat(offer.price);\n          else if (offer.lowPrice) currentPrice = parseFloat(offer.lowPrice);\n        }\n      }\n    }\n  } catch (e) { /* skip malformed JSON-LD */ }\n}\n\n// Strategy 2: Open Graph meta tags\nif (productName === 'Unknown Product') {\n  const ogTitle = html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  if (ogTitle) productName = ogTitle[1].replace(/\\s*[|\\-\\u2013].*$/, '').trim();\n}\nif (currentPrice === null) {\n  const ogPrice = html.match(/<meta[^>]*property=[\"']product:price:amount[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  if (ogPrice) currentPrice = parseFloat(ogPrice[1]);\n}\n\n// Strategy 3: HTML title fallback\nif (productName === 'Unknown Product') {\n  const titleTag = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n  if (titleTag) productName = titleTag[1].replace(/\\s*[|\\-\\u2013].*$/, '').trim();\n}\n\n// Strategy 4: Regex fallback for Swiss price patterns\nif (currentPrice === null) {\n  const priceMatch = html.match(/(\\d+[.,]\\d{2})\\s*(?:CHF|\\.\\u2013)|CHF\\s*(\\d+[.,]\\d{2})|(?:CHF|Fr\\.)\\s*(\\d+)\\.\\u2013/);\n  if (priceMatch) {\n    const priceStr = priceMatch[1] || priceMatch[2] || priceMatch[3];\n    currentPrice = parseFloat(priceStr.replace(',', '.'));\n  }\n}\n\n// Extract domain from URL\nconst domain = url.match(/https?:\\/\\/(?:www\\.)?([^/]+)/)?.[1] || '';\nconst today = new Date().toISOString().split('T')[0];\n\nreturn [{\n  json: {\n    url,\n    product_name: productName,\n    domain,\n    current_price: currentPrice,\n    currency,\n    previous_price: null,\n    lowest_price: currentPrice,\n    highest_price: currentPrice,\n    first_tracked: today,\n    last_checked: today,\n    status: 'active',\n    notify_mode: $('Parse Command').first().json.notifyMode || 'always',\n    price_threshold: null,\n    chatId: $('Parse Command').first().json.chatId\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6892,
        6412
      ],
      "id": "df100001-0001-0001-0001-000000000041",
      "name": "Parse Product Page"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.trackingSheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "url": "={{ $json.url }}",
            "product_name": "={{ $json.product_name }}",
            "domain": "={{ $json.domain }}",
            "current_price": "={{ $json.current_price }}",
            "currency": "={{ $json.currency }}",
            "previous_price": "",
            "lowest_price": "={{ $json.lowest_price }}",
            "highest_price": "={{ $json.highest_price }}",
            "first_tracked": "={{ $json.first_tracked }}",
            "last_checked": "={{ $json.last_checked }}",
            "status": "={{ $json.status }}",
            "notify_mode": "={{ $json.notify_mode }}",
            "price_threshold": ""
          }
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        7112,
        6412
      ],
      "id": "df100001-0001-0001-0001-000000000042",
      "name": "Append Tracked Item",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.trackingSheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        6632
      ],
      "id": "df100001-0001-0001-0001-000000000044",
      "name": "Load Tracked for Untrack",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Find tracked item by name or URL substring\nconst identifier = ($('Parse Command').first().json.identifier || '').toLowerCase();\nconst items = $input.all();\n\nlet rowIndex = -1;\nlet matchedName = '';\nfor (let i = 0; i < items.length; i++) {\n  const row = items[i].json;\n  const name = (row.product_name || '').toLowerCase();\n  const url = (row.url || '').toLowerCase();\n  if (name.includes(identifier) || url.includes(identifier)) {\n    rowIndex = i + 2; // +2 for header row and 1-based index\n    matchedName = row.product_name || row.url;\n    break;\n  }\n}\n\nreturn [{\n  json: {\n    identifier,\n    rowIndex,\n    found: rowIndex > 0,\n    matchedName\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6892,
        6632
      ],
      "id": "df100001-0001-0001-0001-000000000045",
      "name": "Find Tracked Row"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-tracked-found",
              "leftValue": "={{ $json.found }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7112,
        6632
      ],
      "id": "df100001-0001-0001-0001-000000000046",
      "name": "Check Tracked Found"
    },
    {
      "parameters": {
        "operation": "delete",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.trackingSheetName }}",
          "mode": "name"
        },
        "deleteType": "specificRows",
        "rowsToDelete": "={{ $json.rowIndex }}"
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        7332,
        6572
      ],
      "id": "df100001-0001-0001-0001-000000000047",
      "name": "Delete Tracked Row",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.trackingSheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        6872
      ],
      "id": "df100001-0001-0001-0001-000000000050",
      "name": "Load All Tracked",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst chatId = $('Parse Command').first().json.chatId;\nconst activeItems = items.filter(i => i.json.url);\n\nif (activeItems.length === 0) {\n  return [{ json: { chatId, response: '\ud83d\udccb No tracked items yet.\\n\\nTrack a product:\\n/deals track https://digitec.ch/...\\n\\nOr add a requirement:\\n/deals add phone 800CHF flagship camera' } }];\n}\n\nconst lines = activeItems.map((item, idx) => {\n  const r = item.json;\n  const statusIcon = r.status === 'paused' ? '\u23f8\ufe0f ' : '';\n  const priceInfo = r.current_price != null && r.current_price !== '' ? `${r.current_price} ${r.currency}` : 'checking...';\n\n  let changeInfo = '';\n  if (r.previous_price != null && r.previous_price !== '' && r.current_price != null && r.current_price !== '' && Number(r.previous_price) !== Number(r.current_price)) {\n    const prev = Number(r.previous_price);\n    const curr = Number(r.current_price);\n    const pct = (((curr - prev) / prev) * 100).toFixed(1);\n    const arrow = curr < prev ? '\ud83d\udcc9' : '\ud83d\udcc8';\n    changeInfo = ` (was ${prev}, ${pct > 0 ? '+' : ''}${pct}%) ${arrow}`;\n  }\n\n  const notifyLabel = r.notify_mode === 'on_change' ? '\ud83d\udd15 On change' : r.notify_mode === 'threshold' ? `\ud83d\udcb0 < ${r.price_threshold}` : '\ud83d\udd14 Daily';\n\n  return `${idx + 1}. ${statusIcon}*${r.product_name}*\\n   \ud83d\udcb0 ${priceInfo}${changeInfo}\\n   \ud83d\udd17 ${r.domain} | ${notifyLabel}`;\n});\n\nconst response = `\ud83d\udccb *Tracked Items* (${activeItems.length})\\n\\n${lines.join('\\n\\n')}`;\nreturn [{ json: { chatId, response } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6892,
        6872
      ],
      "id": "df100001-0001-0001-0001-000000000051",
      "name": "Format Tracked List"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "YOUR_PRICE_CHECKER_WORKFLOW_ID",
          "mode": "list",
          "cachedResultUrl": "/workflow/YOUR_PRICE_CHECKER_WORKFLOW_ID",
          "cachedResultName": "price-checker"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        6672,
        5408
      ],
      "id": "df100001-0001-0001-0001-000000000053",
      "name": "Execute Price Checker"
    },
    {
      "parameters": {
        "jsCode": "// Format price-checker output for Telegram reply\nconst chatId = $('Parse Command').first().json.chatId;\nconst data = $input.first().json || {};\n\nconst priceSection = data.priceSection || '';\nconst priceReport = data.priceReport || [];\n\nif (!priceSection && priceReport.length === 0) {\n  return [{ json: { chatId, response: '\ud83d\udccd No tracked items to check.\\n\\nUse /deals track <url> to start tracking a product.' } }];\n}\n\nreturn [{ json: { chatId, response: priceSection || '\ud83d\udccd Price check complete \u2014 no updates.' } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6880,
        5408
      ],
      "id": "df100001-0001-0001-0001-000000000054",
      "name": "Build Price Response"
    },
    {
      "parameters": {
        "content": "## Deal Finder\n\nPersonal shopping advisor with price tracking and Perplexity research.\nRegion, retailers, and currency are configurable in the Config node.\n\n**Requirement Commands:**\n- `/deals` - Get recommendations for all active categories\n- `/deals phone` - Get recommendations for specific category\n- `/deals add phone 800CHF flagship camera` - Add requirement\n- `/deals remove phone` - Remove requirement\n- `/deals pause phone` - Pause requirement\n- `/deals resume phone` - Resume requirement\n\n**Tracking Commands:**\n- `/deals track <url>` - Track a product's price\n- `/deals tracked` - List all tracked items\n- `/deals untrack <name>` - Stop tracking an item\n- `check_prices` - Internal: run price checker\n\n**Config (with defaults):**\n- `region`: Switzerland\n- `retailers`: Digitec, Galaxus, Toppreise, etc.\n- `currency`: CHF\n\nCallers can override via Execute Workflow input.",
        "height": 480,
        "width": 340
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        5792,
        5392
      ],
      "id": "df100001-0001-0001-0001-000000000035",
      "name": "Sticky Note - Overview"
    },
    {
      "parameters": {
        "content": "### Google Sheet Schemas\n\n**Requirements tab:**\n| Column | Example |\n|--------|--------|\n| category | Phone |\n| constraints | flagship, good camera |\n| max_price | 800 CHF |\n| priority | high / medium / low |\n| status | active / paused |\n| recommendations | (Perplexity output) |\n| last_researched | 2026-02-17 |\n\n**Tracked Prices tab:**\n| Column | Example |\n|--------|--------|\n| url | https://digitec.ch/... |\n| product_name | Raspberry Pi 5 8GB |\n| domain | digitec.ch |\n| current_price | 89.90 |\n| currency | CHF |\n| previous_price | 95.00 |\n| lowest_price | 85.00 |\n| highest_price | 99.00 |\n| first_tracked | 2026-02-08 |\n| last_checked | 2026-02-10 |\n| status | active / paused |\n| notify_mode | always / on_change / threshold |\n| price_threshold | 80.00 |",
        "height": 520,
        "width": 340
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        6152,
        5392
      ],
      "id": "df100001-0001-0001-0001-000000000036",
      "name": "Sticky Note - Schema"
    },
    {
      "parameters": {
        "content": "workflowId: YOUR_PRICE_CHECKER_WORKFLOW_ID\n(no required inputs \u2014 price-checker Config has defaults)",
        "height": 80,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        6640,
        5360
      ],
      "id": "df100001-0001-0001-0001-000000000060",
      "name": "Sticky Note - Price Checker"
    },
    {
      "parameters": {
        "content": "**Setup required**\nThe sheetId default points to the developer's Google Sheet.\nReplace it with your own sheet ID in the Config node.\nchatId is passed by the caller (menu-handler).\nSee setup-guide.md for full post-import checklist.",
        "height": 140,
        "width": 340,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        5972,
        5832
      ],
      "id": "df100001-0001-0001-0001-000000000061",
      "name": "Sticky Note - Config Setup"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        6132
      ],
      "id": "df100001-0001-0001-0001-000000000056",
      "name": "Load for Modify",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Find row by category and pass through operation\nconst command = $('Parse Command').first().json;\nconst category = command.category;\nconst operation = command.operation;\nconst chatId = command.chatId;\nconst items = $input.all();\n\nlet rowIndex = -1;\nlet currentStatus = '';\nfor (let i = 0; i < items.length; i++) {\n  if ((items[i].json.category || '').toLowerCase() === category) {\n    rowIndex = i + 2; // +2 for header row and 1-based index\n    currentStatus = items[i].json.status || '';\n    break;\n  }\n}\n\nreturn [{\n  json: { category, operation, chatId, rowIndex, found: rowIndex > 0, currentStatus }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6892,
        6132
      ],
      "id": "df100001-0001-0001-0001-000000000057",
      "name": "Find & Route"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-found-modify",
              "leftValue": "={{ $json.found }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7112,
        6132
      ],
      "id": "df100001-0001-0001-0001-000000000062",
      "name": "Check Found"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "route-remove",
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "remove",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "remove"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        7332,
        6072
      ],
      "id": "df100001-0001-0001-0001-000000000063",
      "name": "Action Switch"
    },
    {
      "parameters": {
        "operation": "delete",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "deleteType": "specificRows",
        "rowsToDelete": "={{ $json.rowIndex }}"
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        7552,
        6012
      ],
      "id": "df100001-0001-0001-0001-000000000064",
      "name": "Delete Requirement",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "status": "={{ $json.operation === 'pause' ? 'paused' : 'active' }}"
          }
        },
        "options": {
          "cellFormat": "USER_ENTERED",
          "handlingExtraData": "ignoreIt"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "category",
              "lookupValue": "={{ $json.category }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        7552,
        6132
      ],
      "id": "df100001-0001-0001-0001-000000000065",
      "name": "Update Status",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const { operation, category, chatId } = $('Find & Route').first().json;\nconst emoji = { remove: '\u2705', pause: '\u23f8\ufe0f', resume: '\u25b6\ufe0f' };\nconst verb = { remove: 'Removed', pause: 'Paused', resume: 'Resumed' };\nconst suffix = {\n  remove: 'from watchlist',\n  pause: \"\u2014 won't appear in digests\",\n  resume: '\u2014 will appear in digests'\n};\n\nreturn [{\n  json: {\n    chatId,\n    response: `${emoji[operation]} ${verb[operation]} \"${category}\" ${suffix[operation]}`\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7772,
        6072
      ],
      "id": "df100001-0001-0001-0001-000000000066",
      "name": "Build Modify Response"
    },
    {
      "parameters": {
        "jsCode": "const command = $('Parse Command').first().json;\nconst { operation, chatId } = command;\nlet response;\nif (operation === 'untrack') {\n  response = `\u274c Item \"${command.identifier}\" not found in tracked items`;\n} else {\n  response = `\u274c Category \"${command.category}\" not found in watchlist`;\n}\nreturn [{ json: { chatId, response } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7332,
        6252
      ],
      "id": "df100001-0001-0001-0001-000000000067",
      "name": "Build Not Found"
    },
    {
      "parameters": {
        "jsCode": "const command = $('Parse Command').first().json;\nreturn [{\n  json: {\n    chatId: command.chatId,\n    response: `\u2705 Added \"${command.category}\" to watchlist\\n\\nBudget: ${command.maxPrice || 'not specified'}\\nConstraints: ${command.constraints || 'none'}\\n\\nUse /deals to get recommendations.`\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6880,
        5872
      ],
      "id": "df100001-0001-0001-0001-000000000068",
      "name": "Build Add Response"
    },
    {
      "parameters": {
        "jsCode": "const p = $('Parse Product Page').first().json;\nconst priceText = p.current_price !== null ? `${p.current_price} ${p.currency}` : 'checking...';\nconst notifyText = p.notify_mode === 'on_change' ? 'only when price changes' : 'every day in briefing';\n\nreturn [{\n  json: {\n    chatId: p.chatId,\n    response: `\ud83d\udccd Now tracking: *${p.product_name}*\\n\ud83d\udcb0 Current price: ${priceText} (${p.domain})\\n\ud83d\udcca Updates: ${notifyText}\\n\\nUse /deals tracked to see all items.`\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7332,
        6412
      ],
      "id": "df100001-0001-0001-0001-000000000069",
      "name": "Build Track Response"
    },
    {
      "parameters": {
        "jsCode": "const tracked = $('Find Tracked Row').first().json;\nconst chatId = $('Parse Command').first().json.chatId;\nreturn [{\n  json: {\n    chatId,\n    response: `\u2705 Stopped tracking \"${tracked.matchedName}\"`\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7552,
        6572
      ],
      "id": "df100001-0001-0001-0001-000000000070",
      "name": "Build Untrack Response"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.response }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        8672,
        6096
      ],
      "id": "df100001-0001-0001-0001-000000000072",
      "name": "Send Reply",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-seed",
              "leftValue": "={{ $json.seed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7232,
        5712
      ],
      "id": "df100001-0001-0001-0001-000000000080",
      "name": "Is Seed?"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "category": "={{ $json.category }}",
            "constraints": "={{ $json.constraints }}",
            "max_price": "={{ $json.max_price }}",
            "priority": "={{ $json.priority }}",
            "status": "={{ $json.status }}",
            "recommendations": "",
            "last_researched": ""
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "category",
              "displayName": "category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "constraints",
              "displayName": "constraints",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "max_price",
              "displayName": "max_price",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "priority",
              "displayName": "priority",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "recommendations",
              "displayName": "recommendations",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_researched",
              "displayName": "last_researched",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        7392,
        5696
      ],
      "id": "df100001-0001-0001-0001-000000000081",
      "name": "Seed Demo",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "category"
          ],
          "schema": [
            {
              "id": "category",
              "displayName": "category",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "recommendations",
              "displayName": "recommendations",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_researched",
              "displayName": "last_researched",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "USER_ENTERED",
          "handlingExtraData": "ignoreIt"
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        8256,
        5792
      ],
      "id": "df100001-0001-0001-0001-000000000082",
      "name": "Save Results",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Config').first().json.sheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Config').first().json.historySheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6672,
        7100
      ],
      "id": "df100001-0001-0001-0001-000000000090",
      "name": "Load History",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all().map(i => i.json);\nconst operation = $('Parse Command').first().json.operation;\nconst chatId = $('Parse Command').first().json.chatId;\nconst currency = $('Config').first().json.currency;\n\nif (operation === 'history') {\n  const identifier = ($('Parse Command').first().json.identifier || '').toLowerCase();\n  const matched = items.filter(i => {\n    const name = (i.product_name || '').toLowerCase();\n    const url = (i.url || '').toLowerCase();\n    return name.includes(identifier) || url.includes(identifier);\n  });\n\n  if (matched.length === 0) {\n    return [{ json: { hasData: false, chatId, response: `No price history found for \"${identifier}\".\\n\\nUse /deals tracked to see your tracked items.` } }];\n  }\n\n  const dataPoints = matched\n    .filter(i => i.price != null && i.price !== '')\n    .map(i => ({ date: i.date, price: Number(i.price) }))\n    .sort((a, b) => a.date.localeCompare(b.date));\n\n  const productName = matched[0].product_name || identifier;\n\n  if (dataPoints.length < 2) {\n    return [{ json: { hasData: false, chatId, response: `Only ${dataPoints.length} data point${dataPoints.length === 1 ? '' : 's'} for \"${productName}\".\\n\\nWait for more price checks to build a chart.` } }];\n  }\n\n  const prices = dataPoints.map(d => d.price);\n  const current = prices[prices.length - 1];\n  const first = prices[0];\n  const low = Math.min(...prices);\n  const high = Math.max(...prices);\n  const changePct = (((current - first) / first) * 100).toFixed(1);\n  const arrow = current < first ? '\\ud83d\\udcc9' : current > first ? '\\ud83d\\udcc8' : '\\u27a1\\ufe0f';\n\n  const caption = `\\ud83d\\udcca *${productName}*\\n\\n\\ud83d\\udcb0 Current: ${current} ${currency}\\n${arrow} Change: ${changePct > 0 ? '+' : ''}${changePct}% since ${dataPoints[0].date}\\n\\ud83d\\udcc9 Low: ${low} ${currency} | \\ud83d\\udcc8 High: ${high} ${currency}\\n\\ud83d\\udcca ${dataPoints.length} data points (${dataPoints[0].date} \\u2014 ${dataPoints[dataPoints.length - 1].date})`;\n\n  const chartConfig = {\n    type: 'line',\n    data: {\n      labels: dataPoints.map(d => d.date),\n      datasets: [{\n        label: productName,\n        data: dataPoints.map(d => d.price),\n        fill: true,\n        borderColor: 'rgb(54, 162, 235)',\n        backgroundColor: 'rgba(54, 162, 235, 0.1)',\n        tension: 0.3,\n        pointRadius: 3\n      }]\n    },\n    options: {\n      plugins: {\n        title: { display: true, text: `${productName} \\u2014 Price History`, font: { size: 16 } },\n        legend: { display: false }\n      },\n      scales: {\n        y: { title: { display: true, text: `Price (${currency})` }, beginAtZero: false },\n        x: { title: { display: true, text: 'Date' } }\n      }\n    }\n  };\n\n  return [{ json: { hasData: true, chartConfig, chatId, caption } }];\n\n} else {\n  // plot operation\n  const validItems = items.filter(i => i.price != null && i.price !== '' && i.url);\n\n  if (validItems.length === 0) {\n    return [{ json: { hasData: false, chatId, response: 'No price history data yet.\\n\\nTrack products with /deals track <url> and wait for price checks to build up data.' } }];\n  }\n\n  const byProduct = {};\n  for (const item of validItems) {\n    const url = item.url;\n    if (!byProduct[url]) {\n      byProduct[url] = { name: item.product_name || url, points: [] };\n    }\n    byProduct[url].points.push({ date: item.date, price: Number(item.price) });\n  }\n\n  for (const key of Object.keys(byProduct)) {\n    byProduct[key].points.sort((a, b) => a.date.localeCompare(b.date));\n  }\n\n  const allDates = [...new Set(validItems.map(i => i.date))].sort();\n\n  const colors = [\n    'rgb(54, 162, 235)', 'rgb(255, 99, 132)', 'rgb(75, 192, 192)',\n    'rgb(255, 159, 64)', 'rgb(153, 102, 255)', 'rgb(255, 205, 86)',\n    'rgb(201, 203, 207)', 'rgb(0, 128, 0)'\n  ];\n\n  const products = Object.values(byProduct);\n  const datasets = products.map((prod, idx) => {\n    const dateMap = {};\n    for (const p of prod.points) { dateMap[p.date] = p.price; }\n    const data = allDates.map(d => dateMap[d] !== undefined ? dateMap[d] : null);\n\n    return {\n      label: prod.name,\n      data,\n      borderColor: colors[idx % colors.length],\n      fill: false,\n      tension: 0.3,\n      spanGaps: true,\n      pointRadius: 2\n    };\n  });\n\n  const chartConfig = {\n    type: 'line',\n    data: { labels: allDates, datasets },\n    options: {\n      plugins: {\n        title: { display: true, text: 'Price Tracker \\u2014 All Products', font: { size: 16 } },\n        legend: { display: true, position: 'bottom' }\n      },\n      scales: {\n        y: { title: { display: true, text: `Price (${currency})` }, beginAtZero: false },\n        x: { title: { display: true, text: 'Date' } }\n      }\n    }\n  };\n\n  const dateRange = `${allDates[0]} \\u2014 ${allDates[allDates.length - 1]}`;\n  const caption = `\\ud83d\\udcca *Price Tracker* (${products.length} product${products.length === 1 ? '' : 's'})\\n${dateRange}\\n${validItems.length} total data points`;\n\n  return [{ json: { hasData: true, chatId, chartConfig, caption } }];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6892,
        7100
      ],
      "id": "df100001-0001-0001-0001-000000000110",
      "name": "Build Chart Data"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-has-data",
              "leftValue": "={{ $json.hasData }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7112,
        7100
      ],
      "id": "df100001-0001-0001-0001-000000000111",
      "name": "Has Data?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://quickchart.io/chart",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ chart: $json.chartConfig, width: 700, height: 450, format: 'png', version: '3' }) }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        7332,
        7040
      ],
      "id": "df100001-0001-0001-0001-000000000112",
      "name": "QuickChart API"
    },
    {
      "parameters": {
        "operation": "sendPhoto",
        "chatId": "={{ $('Build Chart Data').first().json.chatId }}",
        "binaryData": true,
        "additionalFields": {
          "caption": "={{ $('Build Chart Data').first().json.caption }}",
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        7552,
        7040
      ],
      "id": "df100001-0001-0001-0001-000000000113",
      "name": "Send Chart",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Parse Command",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Command": {
      "main": [
        [
          {
            "node": "Route Operation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Operation": {
      "main": [
        [
          {
            "node": "Load Requirements",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Execute Price Checker",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append Requirement",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load for Modify",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load for Modify",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load for Modify",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Product Page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load Tracked for Untrack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load All Tracked",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load History",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Requirements": {
      "main": [
        [
          {
            "node": "Filter Active",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Active": {
      "main": [
        [
          {
            "node": "Check Empty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Empty": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Indicator",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Seed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Indicator": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Requirements": {
      "main": [
        [
          {
            "node": "Collect Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Perplexity Research",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Perplexity Research": {
      "main": [
        [
          {
            "node": "Format Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Result": {
      "main": [
        [
          {
            "node": "Save Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Seed?": {
      "main": [
        [
          {
            "node": "Seed Demo",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Loop Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed Demo": {
      "main": [
        [
          {
            "node": "Loop Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Results": {
      "main": [
        [
          {
            "node": "Loop Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect Results": {
      "main": [
        [
          {
            "node": "Build Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Digest": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Requirement": {
      "main": [
        [
          {
            "node": "Build Add Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Add Response": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load for Modify": {
      "main": [
        [
          {
            "node": "Find & Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find & Route": {
      "main": [
        [
          {
            "node": "Check Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Found": {
      "main": [
        [
          {
            "node": "Action Switch",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Action Switch": {
      "main": [
        [
          {
            "node": "Delete Requirement",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Requirement": {
      "main": [
        [
          {
            "node": "Build Modify Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Status": {
      "main": [
        [
          {
            "node": "Build Modify Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Modify Response": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Not Found": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Product Page": {
      "main": [
        [
          {
            "node": "Parse Product Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Product Page": {
      "main": [
        [
          {
            "node": "Append Tracked Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Tracked Item": {
      "main": [
        [
          {
            "node": "Build Track Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Track Response": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Tracked for Untrack": {
      "main": [
        [
          {
            "node": "Find Tracked Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Tracked Row": {
      "main": [
        [
          {
            "node": "Check Tracked Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Tracked Found": {
      "main": [
        [
          {
            "node": "Delete Tracked Row",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Tracked Row": {
      "main": [
        [
          {
            "node": "Build Untrack Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Untrack Response": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Price Checker": {
      "main": [
        [
          {
            "node": "Build Price Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Price Response": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load All Tracked": {
      "main": [
        [
          {
            "node": "Format Tracked List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Tracked List": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load History": {
      "main": [
        [
          {
            "node": "Build Chart Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Chart Data": {
      "main": [
        [
          {
            "node": "Has Data?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Data?": {
      "main": [
        [
          {
            "node": "QuickChart API",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QuickChart API": {
      "main": [
        [
          {
            "node": "Send Chart",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}