AutomationFlowsData & Sheets › Track Singapore Coe Prices with AI Forecasting & Smart Buy Recommendations…

Track Singapore Coe Prices with AI Forecasting & Smart Buy Recommendations…

Original n8n title: Track Singapore Coe Prices with AI Forecasting & Smart Buy Recommendations via Telegram

ByCheng Siong Chin @cschin on n8n.io

Automates Singapore COE price tracking with AI forecasts and buy/wait recommendations. Weekly scraping collects LTA data, enriches with economic indicators, predicts 6-month trends, and alerts users via Telegram/email—helping car buyers and fleet managers make data-driven…

Cron / scheduled trigger★★★★★ complexityAI-powered44 nodesHTTP RequestGoogle SheetsAgentEmail SendTelegramOpenRouter ChatAirtable
Data & Sheets Trigger: Cron / scheduled Nodes: 44 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10625 — we link there as the canonical source.

This workflow follows the Agent → Airtable recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "U7DxutTrX9dYZsYT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI-GROK-4 COE Price Tracker with Smart Buy/Wait Recommendations",
  "tags": [],
  "nodes": [
    {
      "id": "432d1c6a-17ae-44a4-adce-06152589e2f1",
      "name": "Schedule Trigger - Bi-Weekly COE Scraping",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        112,
        464
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 20 * * 3"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c6e3d1b9-c26d-4007-a2b2-6303b0f637c7",
      "name": "Scrape COE Data from OneMotoring",
      "type": "n8n-nodes-base.httpRequest",
      "disabled": true,
      "position": [
        400,
        432
      ],
      "parameters": {
        "url": "https://onemotoring.lta.gov.sg/content/onemotoring/home/buying/upfront-vehicle-costs/certificate-of-entitlement--coe-.html",
        "options": {
          "timeout": 30000
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "13595ddf-b04a-405a-a432-bcdc24b4d442",
      "name": "Extract COE Price Data",
      "type": "n8n-nodes-base.code",
      "position": [
        608,
        576
      ],
      "parameters": {
        "jsCode": "const html = $input.item.json.data;\nconst cheerio = require('cheerio');\nconst $ = cheerio.load(html);\n\nconst categories = ['Category A', 'Category B', 'Category C', 'Category D', 'Category E'];\nconst coeData = [];\nconst biddingDate = new Date().toISOString().split('T')[0];\n\n$('table tbody tr').each((index, row) => {\n  const cells = $(row).find('td');\n  if (cells.length >= 4) {\n    const category = $(cells[0]).text().trim();\n    const quotaPremium = parseFloat($(cells[1]).text().replace(/[^0-9.]/g, ''));\n    const quotaAvailable = parseInt($(cells[2]).text().replace(/[^0-9]/g, ''));\n    const bidsReceived = parseInt($(cells[3]).text().replace(/[^0-9]/g, ''));\n    \n    if (categories.some(cat => category.includes(cat))) {\n      coeData.push({\n        biddingDate,\n        category,\n        quotaPremium,\n        quotaAvailable,\n        bidsReceived,\n        timestamp: new Date().toISOString(),\n        biddingRound: new Date().getDate() <= 15 ? 1 : 2\n      });\n    }\n  }\n});\n\nif (coeData.length === 0) {\n  throw new Error('No COE data extracted - possible website structure change');\n}\n\nreturn coeData.map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "7230f728-2dd1-435c-a96a-e3e843eac3e4",
      "name": "Store in Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1136,
        576
      ],
      "parameters": {
        "columns": {
          "value": {
            "category": "={{ $json.category }}",
            "timestamp": "={{ $json.timestamp }}",
            "biddingDate": "={{ $json.biddingDate }}",
            "biddingRound": "={{ $json.biddingRound }}",
            "bidsReceived": "={{ $json.bidsReceived }}",
            "quotaPremium": "={{ $json.quotaPremium }}",
            "quotaAvailable": "={{ $json.quotaAvailable }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "COE Historical Data"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "{{ $json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "9747556f-348f-42cd-b254-80b1cf954789",
      "name": "Retrieve Historical COE Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1376,
        784
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "COE Historical Data"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8b3ef486-ec56-4eab-be32-69adfa2509ce",
      "name": "Calculate Technical Indicators",
      "type": "n8n-nodes-base.code",
      "position": [
        1584,
        768
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst categories = ['Category A', 'Category B', 'Category C', 'Category D', 'Category E'];\n\nconst processedData = {};\n\ncategories.forEach(category => {\n  const categoryData = items.filter(item => \n    item.json.category && item.json.category.includes(category)\n  ).sort((a, b) => new Date(a.json.biddingDate) - new Date(b.json.biddingDate));\n  \n  if (categoryData.length < 4) {\n    processedData[category] = { error: 'Insufficient data' };\n    return;\n  }\n  \n  const prices = categoryData.map(item => item.json.quotaPremium);\n  const latestPrice = prices[prices.length - 1];\n  \n  const ma4 = prices.slice(-4).reduce((a, b) => a + b, 0) / 4;\n  const ma12 = prices.length >= 12 ? prices.slice(-12).reduce((a, b) => a + b, 0) / 12 : ma4;\n  const ma26 = prices.length >= 26 ? prices.slice(-26).reduce((a, b) => a + b, 0) / 26 : ma12;\n  \n  const rateOfChange = prices.length >= 5 ? ((latestPrice - prices[prices.length - 5]) / prices[prices.length - 5]) * 100 : 0;\n  \n  const variance = prices.slice(-12).reduce((sum, price) => sum + Math.pow(price - ma12, 2), 0) / Math.min(12, prices.length);\n  const volatility = Math.sqrt(variance);\n  \n  const now = new Date();\n  const month = now.getMonth() + 1;\n  const quarter = Math.ceil(month / 3);\n  const isCNYPeriod = month === 1 || month === 2;\n  const isYearEnd = month === 11 || month === 12;\n  const biddingRound = categoryData[categoryData.length - 1].json.biddingRound || 1;\n  \n  processedData[category] = {\n    latestPrice,\n    ma4,\n    ma12,\n    ma26,\n    rateOfChange,\n    volatility,\n    month,\n    quarter,\n    isCNYPeriod,\n    isYearEnd,\n    biddingRound,\n    historicalPrices: prices.slice(-26),\n    dataPoints: categoryData.length\n  };\n});\n\nreturn [{ json: processedData }];"
      },
      "typeVersion": 2
    },
    {
      "id": "62064423-11e3-4c12-890d-39febc727275",
      "name": "Prepare AI Prediction Prompt",
      "type": "n8n-nodes-base.set",
      "disabled": true,
      "position": [
        1888,
        576
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "predictionPrompt",
              "type": "string",
              "value": "=You are a Singapore COE market analysis expert. Analyze the following COE data and provide detailed price predictions:\n\n{{ Object.keys($json).map(category => {\n  const data = $json[category];\n  return `${category}:\n- Latest Price: $${data.latestPrice}\n- 4-week MA: $${data.ma4?.toFixed(0)}\n- 12-week MA: $${data.ma12?.toFixed(0)}\n- 26-week MA: $${data.ma26?.toFixed(0)}\n- Rate of Change: ${data.rateOfChange?.toFixed(2)}%\n- Volatility: $${data.volatility?.toFixed(0)}\n- Current Period: Q${data.quarter}, Month ${data.month}\n- CNY Period: ${data.isCNYPeriod}\n- Year-End Period: ${data.isYearEnd}\n- Historical Prices (last 26 weeks): ${data.historicalPrices?.join(', ')}`;\n}).join('\\n\\n') }}\n\nProvide:\n1. Price predictions for next 6 bidding rounds (3 months) for each category\n2. Confidence levels (High/Medium/Low) for each prediction\n3. Market trend analysis (uptrend/downtrend/consolidation)\n4. Seasonal factors impact\n5. Risk assessment\n\nFormat as JSON:\n{\n  \"predictions\": {\n    \"Category A\": [{\"round\": 1, \"predictedPrice\": 95000, \"confidence\": \"High\", \"lower\": 92000, \"upper\": 98000}, ...],\n    ...\n  },\n  \"trends\": {\"Category A\": \"uptrend\", ...},\n  \"seasonalImpact\": \"High impact expected due to...\",\n  \"riskLevel\": \"Medium\"\n}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "4fc3d93a-3ccc-40bd-bf7f-bc3f235d2f2c",
      "name": "AI Agent - COE Analysis",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2640,
        544
      ],
      "parameters": {
        "text": "={{ $json.predictionPrompt }}",
        "options": {}
      },
      "typeVersion": 1.7
    },
    {
      "id": "55d5e431-97e4-4094-8339-8490b48b8f0d",
      "name": "Generate Buy Recommendations",
      "type": "n8n-nodes-base.code",
      "position": [
        3200,
        752
      ],
      "parameters": {
        "jsCode": "const technicalData = $('Calculate Technical Indicators').first().json;\nconst aiPrediction = $input.first().json;\n\nconst categories = Object.keys(technicalData);\nconst recommendations = {};\n\ncategories.forEach(category => {\n  const tech = technicalData[category];\n  if (tech.error) {\n    recommendations[category] = { action: 'Insufficient Data', reasoning: tech.error };\n    return;\n  }\n  \n  let score = 50;\n  let reasoning = [];\n  \n  if (tech.latestPrice < tech.ma12) {\n    score += 15;\n    reasoning.push('Price below 12-week average (bullish signal)');\n  } else if (tech.latestPrice > tech.ma12 * 1.1) {\n    score -= 15;\n    reasoning.push('Price significantly above moving average (overheated)');\n  }\n  \n  if (tech.rateOfChange < -5) {\n    score += 20;\n    reasoning.push(`Strong downward momentum (${tech.rateOfChange.toFixed(1)}%)`);\n  } else if (tech.rateOfChange > 10) {\n    score -= 20;\n    reasoning.push(`Strong upward momentum (${tech.rateOfChange.toFixed(1)}%) - avoid buying`);\n  }\n  \n  if (tech.isCNYPeriod) {\n    score -= 10;\n    reasoning.push('CNY period typically sees higher prices');\n  }\n  \n  if (tech.isYearEnd) {\n    score -= 5;\n    reasoning.push('Year-end period - slightly elevated demand');\n  }\n  \n  if (tech.volatility > tech.ma12 * 0.15) {\n    score -= 10;\n    reasoning.push('High volatility - uncertain market conditions');\n  }\n  \n  let action, timingAdvice, potentialSavings;\n  if (score >= 70) {\n    action = 'BUY NOW';\n    timingAdvice = 'Strong buying opportunity. Market conditions favorable.';\n    potentialSavings = 0;\n  } else if (score >= 50) {\n    action = 'MONITOR CLOSELY';\n    timingAdvice = 'Market at neutral position. Consider waiting 2-4 weeks.';\n    potentialSavings = Math.round(tech.latestPrice * 0.03);\n  } else {\n    action = 'WAIT';\n    const weeksToWait = score < 30 ? 8 : 4;\n    timingAdvice = `Wait ${weeksToWait} weeks. Market showing unfavorable conditions.`;\n    potentialSavings = Math.round(tech.latestPrice * 0.08);\n  }\n  \n  recommendations[category] = {\n    action,\n    score,\n    currentPrice: tech.latestPrice,\n    timingAdvice,\n    potentialSavings,\n    reasoning: reasoning.join('; '),\n    trend: tech.rateOfChange > 5 ? 'Uptrend' : tech.rateOfChange < -5 ? 'Downtrend' : 'Consolidation',\n    volatilityLevel: tech.volatility > tech.ma12 * 0.15 ? 'High' : tech.volatility > tech.ma12 * 0.08 ? 'Medium' : 'Low'\n  };\n});\n\nconst bestCategory = Object.entries(recommendations)\n  .filter(([_, rec]) => !rec.reasoning.includes('Insufficient'))\n  .sort((a, b) => b[1].score - a[1].score)[0];\n\nreturn [{\n  json: {\n    recommendations,\n    bestCategory: bestCategory ? bestCategory[0] : 'None',\n    bestCategoryScore: bestCategory ? bestCategory[1].score : 0,\n    analysisDate: new Date().toISOString(),\n    marketOverview: {\n      averageVolatility: Object.values(recommendations)\n        .filter(r => r.volatilityLevel)\n        .reduce((sum, r) => sum + (r.volatilityLevel === 'High' ? 3 : r.volatilityLevel === 'Medium' ? 2 : 1), 0) / categories.length,\n      dominantTrend: Object.values(recommendations)\n        .filter(r => r.trend)\n        .reduce((acc, r) => {\n          acc[r.trend] = (acc[r.trend] || 0) + 1;\n          return acc;\n        }, {})\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "fe4d8755-6dab-4329-b760-9d22b9236d75",
      "name": "Format HTML Report",
      "type": "n8n-nodes-base.code",
      "disabled": true,
      "position": [
        3728,
        224
      ],
      "parameters": {
        "jsCode": "const recommendations = $input.first().json.recommendations;\nconst bestCategory = $input.first().json.bestCategory;\nconst analysisDate = new Date($input.first().json.analysisDate).toLocaleDateString('en-SG');\n\nlet htmlReport = `\n<!DOCTYPE html>\n<html>\n<head>\n  <style>\n    body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }\n    .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n    h1 { color: #d32f2f; border-bottom: 3px solid #d32f2f; padding-bottom: 10px; }\n    h2 { color: #1976d2; margin-top: 30px; }\n    .category-card { background: #f9f9f9; padding: 20px; margin: 15px 0; border-radius: 8px; border-left: 5px solid #1976d2; }\n    .buy-now { border-left-color: #4caf50; background: #e8f5e9; }\n    .wait { border-left-color: #f44336; background: #ffebee; }\n    .monitor { border-left-color: #ff9800; background: #fff3e0; }\n    .action { font-size: 24px; font-weight: bold; margin-bottom: 10px; }\n    .action.buy { color: #4caf50; }\n    .action.wait { color: #f44336; }\n    .action.monitor { color: #ff9800; }\n    .price { font-size: 32px; font-weight: bold; color: #333; }\n    .metric { display: inline-block; margin: 10px 20px 10px 0; }\n    .metric-label { font-weight: bold; color: #666; }\n    .savings { background: #4caf50; color: white; padding: 10px 15px; border-radius: 5px; display: inline-block; margin-top: 10px; }\n    .reasoning { background: white; padding: 15px; border-radius: 5px; margin-top: 10px; font-size: 14px; color: #555; }\n    .best-pick { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 2px solid #eee; color: #999; font-size: 12px; text-align: center; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>\ud83d\ude97 Singapore COE Price Analysis & Recommendations</h1>\n    <p><strong>Analysis Date:</strong> ${analysisDate}</p>\n    \n    <div class=\"best-pick\">\n      <h2 style=\"color: white; margin-top: 0;\">\u2b50 Best Category to Consider</h2>\n      <p style=\"font-size: 20px; margin: 10px 0;\">${bestCategory}</p>\n      <p>Highest recommendation score based on current market conditions</p>\n    </div>\n`;\n\nObject.entries(recommendations).forEach(([category, rec]) => {\n  if (rec.reasoning && rec.reasoning.includes('Insufficient')) {\n    htmlReport += `\n    <div class=\"category-card\">\n      <h2>${category}</h2>\n      <p>${rec.reasoning}</p>\n    </div>\n    `;\n    return;\n  }\n  \n  const cardClass = rec.action === 'BUY NOW' ? 'buy-now' : rec.action === 'WAIT' ? 'wait' : 'monitor';\n  const actionClass = rec.action === 'BUY NOW' ? 'buy' : rec.action === 'WAIT' ? 'wait' : 'monitor';\n  \n  htmlReport += `\n    <div class=\"category-card ${cardClass}\">\n      <h2>${category}</h2>\n      <div class=\"action ${actionClass}\">${rec.action === 'BUY NOW' ? '\u2705' : rec.action === 'WAIT' ? '\u23f8\ufe0f' : '\ud83d\udc40'} ${rec.action}</div>\n      <div class=\"price\">$${rec.currentPrice.toLocaleString('en-SG')}</div>\n      <div class=\"metric\">\n        <span class=\"metric-label\">Score:</span> ${rec.score}/100\n      </div>\n      <div class=\"metric\">\n        <span class=\"metric-label\">Trend:</span> ${rec.trend}\n      </div>\n      <div class=\"metric\">\n        <span class=\"metric-label\">Volatility:</span> ${rec.volatilityLevel}\n      </div>\n      <p><strong>Timing Advice:</strong> ${rec.timingAdvice}</p>\n      ${rec.potentialSavings > 0 ? `<div class=\"savings\">\ud83d\udcb0 Potential Savings: $${rec.potentialSavings.toLocaleString('en-SG')}</div>` : ''}\n      <div class=\"reasoning\">\n        <strong>Analysis:</strong><br>\n        ${rec.reasoning}\n      </div>\n    </div>\n  `;\n});\n\nhtmlReport += `\n    <div class=\"footer\">\n      <p>This analysis is based on historical COE data and market trends. Actual prices may vary.</p>\n      <p>Data source: LTA OneMotoring Portal | Generated by n8n Automation</p>\n    </div>\n  </div>\n</body>\n</html>\n`;\n\nreturn [{ json: { htmlReport, subject: `COE Analysis Report - ${analysisDate}` } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1576e59a-54ba-452c-938c-547410701201",
      "name": "Send Email Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3952,
        384
      ],
      "parameters": {
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "9e03ad9e-4bbb-4fbc-9ba5-4af04a292f67",
      "name": "Check for Buy Opportunities",
      "type": "n8n-nodes-base.if",
      "position": [
        3392,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.recommendations['Category A'].action }}",
              "rightValue": "BUY NOW"
            },
            {
              "id": "2",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.recommendations['Category B'].action }}",
              "rightValue": "BUY NOW"
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "3229f007-f192-4562-8827-d4ddf27fa55d",
      "name": "Send Telegram Alert",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3728,
        368
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *COE BUY ALERT* \ud83d\udea8\n\nOptimal buying opportunity detected!\n\n{{ Object.entries($json.recommendations)\n  .filter(([_, rec]) => rec.action === 'BUY NOW')\n  .map(([cat, rec]) => `*${cat}*: $${rec.currentPrice.toLocaleString('en-SG')}\\nScore: ${rec.score}/100\\n${rec.timingAdvice}`)\n  .join('\\n\\n') }}\n\nView full report in your email.",
        "chatId": "{{ $('Schedule Trigger - Bi-Weekly COE Scraping').item.json.telegramChatId }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1483eee4-c8b9-459e-bc32-06daa16fed6e",
      "name": "Generate Dashboard Summary",
      "type": "n8n-nodes-base.set",
      "position": [
        3392,
        1328
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "dashboardSummary",
              "type": "string",
              "value": "=## Singapore COE Dashboard - {{ new Date().toLocaleDateString('en-SG') }}\n\n### Current Prices\n{{ Object.entries($('Generate Buy Recommendations').first().json.recommendations)\n  .map(([cat, rec]) => `**${cat}**: $${rec.currentPrice?.toLocaleString('en-SG') || 'N/A'}`)\n  .join('\\n') }}\n\n### Top Recommendation\n**{{ $('Generate Buy Recommendations').first().json.bestCategory }}** (Score: {{ $('Generate Buy Recommendations').first().json.bestCategoryScore }}/100)\n\n### Market Overview\n- Dominant Trend: {{ Object.entries($('Generate Buy Recommendations').first().json.marketOverview.dominantTrend).sort((a,b) => b[1] - a[1])[0][0] }}\n- Average Volatility: {{ $('Generate Buy Recommendations').first().json.marketOverview.averageVolatility > 2.5 ? 'High' : $('Generate Buy Recommendations').first().json.marketOverview.averageVolatility > 1.5 ? 'Medium' : 'Low' }}\n\n### Action Items\n{{ Object.entries($('Generate Buy Recommendations').first().json.recommendations)\n  .map(([cat, rec]) => `- **${cat}**: ${rec.action} - ${rec.timingAdvice}`)\n  .join('\\n') }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e8bc3153-c4ff-4a6a-a99d-48bd76e89479",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        2656,
        752
      ],
      "parameters": {
        "model": "z-ai/glm-4.5",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "06db176e-bf96-42d6-a1b0-7a896a4f3b32",
      "name": "Airtable - Store COE Data",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1120,
        720
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "COE Historical Data"
        },
        "operation": "append"
      },
      "typeVersion": 2
    },
    {
      "id": "852583f1-d3e8-4178-8bd2-041dff8976cd",
      "name": "Data Validation Check",
      "type": "n8n-nodes-base.if",
      "position": [
        848,
        672
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.quotaPremium }}",
              "rightValue": 0
            },
            {
              "id": "2",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.category }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6e63dc80-1123-4e59-bbdb-ada1db7fd8f8",
      "name": "Error Handler - Retry Scraping",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1120,
        912
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b9dad53a-81b4-4bf1-83f5-53561e2e8f04",
      "name": "Scrape with Retry Logic",
      "type": "n8n-nodes-base.code",
      "position": [
        400,
        624
      ],
      "parameters": {
        "jsCode": "const maxRetries = 3;\nconst retryDelay = 5000;\n\nfor (let attempt = 1; attempt <= maxRetries; attempt++) {\n  try {\n    const response = await this.helpers.httpRequest({\n      method: 'GET',\n      url: 'https://onemotoring.lta.gov.sg/content/onemotoring/home/buying/upfront-vehicle-costs/certificate-of-entitlement--coe-.html',\n      timeout: 30000\n    });\n    \n    if (response && response.length > 1000) {\n      return [{ json: { data: response, success: true, attempts: attempt } }];\n    }\n  } catch (error) {\n    if (attempt === maxRetries) {\n      throw new Error(`Scraping failed after ${maxRetries} attempts: ${error.message}`);\n    }\n    await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));\n  }\n}\n\nthrow new Error('Scraping failed: Invalid response');"
      },
      "typeVersion": 2
    },
    {
      "id": "213dea78-48e3-4b49-9d97-2db358bd2a90",
      "name": "Extract Economic Indicators",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1728,
        912
      ],
      "parameters": {
        "url": "https://eservices.mas.gov.sg/api/action/datastore/search.json?resource_id=key-economic-indicators",
        "options": {
          "timeout": 15000
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b78e4004-1b1f-4916-9b2f-16583905b966",
      "name": "Process Economic Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1920,
        912
      ],
      "parameters": {
        "jsCode": "const economicData = $input.first().json;\nconst coeData = $('Calculate Technical Indicators').first().json;\n\nlet gdpGrowth = 0;\nlet interestRate = 0;\n\ntry {\n  if (economicData && economicData.result && economicData.result.records) {\n    const records = economicData.result.records;\n    const latestGdp = records.find(r => r.indicator === 'GDP Growth');\n    const latestRate = records.find(r => r.indicator === 'Prime Lending Rate');\n    \n    gdpGrowth = latestGdp ? parseFloat(latestGdp.value) : 0;\n    interestRate = latestRate ? parseFloat(latestRate.value) : 0;\n  }\n} catch (error) {\n  gdpGrowth = 0;\n  interestRate = 0;\n}\n\nreturn [{\n  json: {\n    ...coeData,\n    economicIndicators: {\n      gdpGrowth,\n      interestRate,\n      impactScore: gdpGrowth > 3 ? 'Positive' : gdpGrowth < 1 ? 'Negative' : 'Neutral'\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "452a530e-af62-4a66-bedc-9416449bf4e2",
      "name": "Enhanced AI Prompt with Economics",
      "type": "n8n-nodes-base.set",
      "position": [
        2400,
        848
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "enhancedPrompt",
              "type": "string",
              "value": "=You are a Singapore automotive market economist specializing in COE price forecasting.\n\n**Historical COE Data:**\n{{ Object.keys($json).filter(k => k !== 'economicIndicators').map(category => {\n  const data = $json[category];\n  return `${category}:\n- Latest: $${data.latestPrice}\n- 4w MA: $${data.ma4?.toFixed(0)} | 12w MA: $${data.ma12?.toFixed(0)} | 26w MA: $${data.ma26?.toFixed(0)}\n- ROC: ${data.rateOfChange?.toFixed(2)}% | Volatility: $${data.volatility?.toFixed(0)}\n- Period: Q${data.quarter}, Month ${data.month} (CNY: ${data.isCNYPeriod}, YE: ${data.isYearEnd})`;\n}).join('\\n\\n') }}\n\n**Economic Context:**\n- GDP Growth: {{ $json.economicIndicators.gdpGrowth }}%\n- Interest Rate: {{ $json.economicIndicators.interestRate }}%\n- Economic Outlook: {{ $json.economicIndicators.impactScore }}\n\n**Task:** Provide 6-round price predictions (3 months) for each category with:\n1. Predicted prices with 95% confidence intervals (lower/upper bounds)\n2. Confidence levels: High (>80%), Medium (60-80%), Low (<60%)\n3. Trend classification: Uptrend/Downtrend/Consolidation\n4. Key risk factors (economic shocks, policy changes, seasonal effects)\n5. Best-case, worst-case, most-likely scenarios\n\n**Output Format (strict JSON):**\n{\n  \"predictions\": {\n    \"Category A\": [\n      {\"round\": 1, \"predictedPrice\": 95000, \"confidence\": \"High\", \"lower\": 92000, \"upper\": 98000, \"scenario\": \"most-likely\"},\n      {\"round\": 1, \"predictedPrice\": 88000, \"confidence\": \"Medium\", \"lower\": 85000, \"upper\": 91000, \"scenario\": \"best-case\"},\n      {\"round\": 1, \"predictedPrice\": 102000, \"confidence\": \"Medium\", \"lower\": 98000, \"upper\": 106000, \"scenario\": \"worst-case\"}\n    ]\n  },\n  \"trends\": {\"Category A\": \"uptrend\"},\n  \"riskFactors\": [\"CNY demand spike\", \"Interest rate hikes\"],\n  \"economicImpact\": \"Moderate upward pressure from GDP growth\"\n}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "68c8abd0-8c25-4591-94c9-4222364d640f",
      "name": "Multi-Scenario Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        2976,
        816
      ],
      "parameters": {
        "jsCode": "const aiPrediction = $input.first().json;\nconst categories = Object.keys(aiPrediction.predictions || {});\n\nconst scenarioAnalysis = {};\n\ncategories.forEach(category => {\n  const predictions = aiPrediction.predictions[category] || [];\n  \n  const mostLikely = predictions.filter(p => p.scenario === 'most-likely');\n  const bestCase = predictions.filter(p => p.scenario === 'best-case');\n  const worstCase = predictions.filter(p => p.scenario === 'worst-case');\n  \n  const avgMostLikely = mostLikely.reduce((sum, p) => sum + p.predictedPrice, 0) / mostLikely.length;\n  const avgBestCase = bestCase.reduce((sum, p) => sum + p.predictedPrice, 0) / bestCase.length;\n  const avgWorstCase = worstCase.reduce((sum, p) => sum + p.predictedPrice, 0) / worstCase.length;\n  \n  scenarioAnalysis[category] = {\n    mostLikely: { avg: Math.round(avgMostLikely), predictions: mostLikely },\n    bestCase: { avg: Math.round(avgBestCase), predictions: bestCase },\n    worstCase: { avg: Math.round(avgWorstCase), predictions: worstCase },\n    savingsPotential: Math.round(avgWorstCase - avgBestCase),\n    optimalRound: bestCase.reduce((min, p) => p.predictedPrice < min.predictedPrice ? p : min, bestCase[0]).round\n  };\n});\n\nreturn [{ json: { scenarioAnalysis, riskFactors: aiPrediction.riskFactors } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "2ca62cf3-3060-4086-be65-4eb6202576f3",
      "name": "ROI Calculator",
      "type": "n8n-nodes-base.code",
      "position": [
        3392,
        656
      ],
      "parameters": {
        "jsCode": "const recommendations = $('Generate Buy Recommendations').first().json.recommendations;\nconst scenarios = $input.first().json.scenarioAnalysis;\n\nconst categories = Object.keys(recommendations).filter(k => !recommendations[k].reasoning?.includes('Insufficient'));\nconst roiAnalysis = {};\n\ncategories.forEach(category => {\n  const rec = recommendations[category];\n  const scenario = scenarios[category];\n  \n  const carPrice = category.includes('A') ? 80000 : category.includes('B') ? 120000 : 50000;\n  const loanRate = 0.028;\n  const loanTenure = 7;\n  const depreciation = carPrice * 0.6;\n  \n  const coeNow = rec.currentPrice;\n  const coeBestCase = scenario.bestCase.avg;\n  const coeWorstCase = scenario.worstCase.avg;\n  \n  const totalCostNow = coeNow + carPrice + (carPrice * loanRate * loanTenure) - depreciation;\n  const totalCostBest = coeBestCase + carPrice + (carPrice * loanRate * loanTenure) - depreciation;\n  const totalCostWorst = coeWorstCase + carPrice + (carPrice * loanRate * loanTenure) - depreciation;\n  \n  roiAnalysis[category] = {\n    buyNowCost: Math.round(totalCostNow),\n    bestCaseWaitCost: Math.round(totalCostBest),\n    worstCaseWaitCost: Math.round(totalCostWorst),\n    potentialSavings: Math.round(totalCostNow - totalCostBest),\n    potentialLoss: Math.round(totalCostWorst - totalCostNow),\n    recommendation: totalCostNow < totalCostBest ? 'Buy Now - Lower Total Cost' : `Wait ${scenario.optimalRound * 2} weeks - Save $${Math.round(totalCostNow - totalCostBest)}`,\n    breakdownNow: {\n      coe: coeNow,\n      carPrice,\n      interestPaid: Math.round(carPrice * loanRate * loanTenure),\n      depreciation: Math.round(depreciation)\n    }\n  };\n});\n\nreturn [{ json: { roiAnalysis } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "053b7e99-50c7-45ce-846c-e4d1f5d6ec2a",
      "name": "Check Significant Price Movement",
      "type": "n8n-nodes-base.if",
      "position": [
        3392,
        944
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ Math.abs($json.recommendations['Category A']?.rateOfChange || 0) }}",
              "rightValue": 10
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "069afeb1-8f92-4475-ac73-8be8a376be8d",
      "name": "Urgent Market Alert",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3728,
        944
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *URGENT: Significant COE Price Movement Detected* \ud83d\udea8\n\n{{ Object.entries($json.recommendations)\n  .filter(([_, rec]) => Math.abs(rec.rateOfChange || 0) > 10)\n  .map(([cat, rec]) => `*${cat}*: ${rec.rateOfChange > 0 ? '\ud83d\udcc8' : '\ud83d\udcc9'} ${Math.abs(rec.rateOfChange).toFixed(1)}% change\\nCurrent: $${rec.currentPrice.toLocaleString('en-SG')}\\nAction: ${rec.action}`)\n  .join('\\n\\n') }}\n\nImmediate review recommended.",
        "chatId": "{{ $('Schedule Trigger - Bi-Weekly COE Scraping').item.json.telegramChatId }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e2540cb4-60ad-4689-8e3e-f791fb8fbd0c",
      "name": "Daily Prediction Update Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1120,
        1104
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "dc79a47d-0b19-4a1c-94e0-30c983fccbf0",
      "name": "Check Market Volatility",
      "type": "n8n-nodes-base.if",
      "position": [
        3392,
        1136
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.marketOverview.averageVolatility }}",
              "rightValue": 2.5
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "df67db2f-8535-4d28-a838-2ebe6d4c4b25",
      "name": "Enhanced Dashboard Report",
      "type": "n8n-nodes-base.code",
      "position": [
        3728,
        576
      ],
      "parameters": {
        "jsCode": "const recommendations = $('Generate Buy Recommendations').first().json.recommendations;\nconst scenarios = $('Multi-Scenario Analysis').first().json.scenarioAnalysis;\nconst roi = $('ROI Calculator').first().json.roiAnalysis;\nconst analysisDate = new Date().toLocaleDateString('en-SG');\n\nlet htmlReport = `\n<!DOCTYPE html>\n<html>\n<head>\n  <style>\n    body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }\n    .container { max-width: 1400px; margin: 0 auto; background: white; padding: 40px; border-radius: 15px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); }\n    h1 { color: #d32f2f; border-bottom: 4px solid #d32f2f; padding-bottom: 15px; font-size: 36px; }\n    h2 { color: #1976d2; margin-top: 35px; font-size: 28px; }\n    h3 { color: #555; margin-top: 25px; font-size: 22px; }\n    .category-card { background: #f9f9f9; padding: 25px; margin: 20px 0; border-radius: 10px; border-left: 6px solid #1976d2; }\n    .buy-now { border-left-color: #4caf50; background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); }\n    .wait { border-left-color: #f44336; background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%); }\n    .monitor { border-left-color: #ff9800; background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); }\n    .action { font-size: 28px; font-weight: bold; margin-bottom: 15px; }\n    .action.buy { color: #4caf50; }\n    .action.wait { color: #f44336; }\n    .action.monitor { color: #ff9800; }\n    .price { font-size: 36px; font-weight: bold; color: #333; margin: 10px 0; }\n    .metric { display: inline-block; margin: 12px 25px 12px 0; font-size: 16px; }\n    .metric-label { font-weight: bold; color: #666; }\n    .savings { background: #4caf50; color: white; padding: 12px 20px; border-radius: 8px; display: inline-block; margin-top: 12px; font-size: 18px; }\n    .roi-box { background: #e3f2fd; padding: 20px; border-radius: 8px; margin-top: 15px; border-left: 4px solid #2196f3; }\n    .scenario-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 20px; }\n    .scenario-card { background: white; padding: 18px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }\n    .scenario-best { border-top: 4px solid #4caf50; }\n    .scenario-likely { border-top: 4px solid #2196f3; }\n    .scenario-worst { border-top: 4px solid #f44336; }\n    .best-pick { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; border-radius: 12px; margin: 25px 0; }\n    .footer { margin-top: 50px; padding-top: 25px; border-top: 2px solid #eee; color: #999; font-size: 13px; text-align: center; }\n    .chart-placeholder { background: #f5f5f5; padding: 40px; text-align: center; color: #999; border-radius: 8px; margin: 20px 0; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>\ud83d\ude97 Singapore COE Comprehensive Market Analysis</h1>\n    <p style=\"font-size: 18px;\"><strong>Analysis Date:</strong> ${analysisDate}</p>\n    \n    <div class=\"best-pick\">\n      <h2 style=\"color: white; margin-top: 0;\">\u2b50 Best Category - Data-Driven Recommendation</h2>\n      <p style=\"font-size: 24px; margin: 12px 0;\">${$('Generate Buy Recommendations').first().json.bestCategory}</p>\n      <p style=\"font-size: 16px;\">Highest overall score: ${$('Generate Buy Recommendations').first().json.bestCategoryScore}/100</p>\n    </div>\n`;\n\nObject.entries(recommendations).forEach(([category, rec]) => {\n  if (rec.reasoning && rec.reasoning.includes('Insufficient')) {\n    htmlReport += `<div class=\"category-card\"><h2>${category}</h2><p>${rec.reasoning}</p></div>`;\n    return;\n  }\n  \n  const cardClass = rec.action === 'BUY NOW' ? 'buy-now' : rec.action === 'WAIT' ? 'wait' : 'monitor';\n  const actionClass = rec.action === 'BUY NOW' ? 'buy' : rec.action === 'WAIT' ? 'wait' : 'monitor';\n  const scenario = scenarios[category];\n  const roiData = roi[category];\n  \n  htmlReport += `\n    <div class=\"category-card ${cardClass}\">\n      <h2>${category}</h2>\n      <div class=\"action ${actionClass}\">${rec.action === 'BUY NOW' ? '\u2705' : rec.action === 'WAIT' ? '\u23f8\ufe0f' : '\ud83d\udc40'} ${rec.action}</div>\n      <div class=\"price\">$${rec.currentPrice.toLocaleString('en-SG')}</div>\n      <div class=\"metric\"><span class=\"metric-label\">Score:</span> ${rec.score}/100</div>\n      <div class=\"metric\"><span class=\"metric-label\">Trend:</span> ${rec.trend}</div>\n      <div class=\"metric\"><span class=\"metric-label\">Volatility:</span> ${rec.volatilityLevel}</div>\n      <p style=\"font-size: 16px; margin-top: 15px;\"><strong>Timing Advice:</strong> ${rec.timingAdvice}</p>\n      \n      <h3>\ud83d\udcb0 Total Cost of Ownership Analysis</h3>\n      <div class=\"roi-box\">\n        <p><strong>Buy Now Total Cost:</strong> $${roiData.buyNowCost.toLocaleString('en-SG')}</p>\n        <p><strong>Best-Case Wait Cost:</strong> $${roiData.bestCaseWaitCost.toLocaleString('en-SG')}</p>\n        <p><strong>Potential Savings:</strong> <span style=\"color: #4caf50; font-weight: bold;\">$${roiData.potentialSavings.toLocaleString('en-SG')}</span></p>\n        <p><strong>Risk (Worst Case):</strong> <span style=\"color: #f44336;\">-$${roiData.potentialLoss.toLocaleString('en-SG')}</span></p>\n        <p style=\"margin-top: 15px; font-size: 18px;\"><strong>ROI Recommendation:</strong> ${roiData.recommendation}</p>\n      </div>\n      \n      <h3>\ud83d\udcca Price Prediction Scenarios (Next 3 Months)</h3>\n      <div class=\"scenario-grid\">\n        <div class=\"scenario-card scenario-best\">\n          <h4 style=\"margin-top: 0; color: #4caf50;\">Best Case \ud83d\udfe2</h4>\n          <p style=\"font-size: 20px; font-weight: bold;\">$${scenario.bestCase.avg.toLocaleString('en-SG')}</p>\n          <p style=\"font-size: 14px; color: #666;\">Optimal Round: ${scenario.optimalRound}</p>\n        </div>\n        <div class=\"scenario-card scenario-likely\">\n          <h4 style=\"margin-top: 0; color: #2196f3;\">Most Likely \ud83d\udd35</h4>\n          <p style=\"font-size: 20px; font-weight: bold;\">$${scenario.mostLikely.avg.toLocaleString('en-SG')}</p>\n          <p style=\"font-size: 14px; color: #666;\">Baseline forecast</p>\n        </div>\n        <div class=\"scenario-card scenario-worst\">\n          <h4 style=\"margin-top: 0; color: #f44336;\">Worst Case \ud83d\udd34</h4>\n          <p style=\"font-size: 20px; font-weight: bold;\">$${scenario.worstCase.avg.toLocaleString('en-SG')}</p>\n          <p style=\"font-size: 14px; color: #666;\">Maximum risk exposure</p>\n        </div>\n      </div>\n      \n      ${rec.potentialSavings > 0 ? `<div class=\"savings\">\ud83d\udcb0 Potential Timing Savings: $${rec.potentialSavings.toLocaleString('en-SG')}</div>` : ''}\n    </div>\n  `;\n});\n\nhtmlReport += `\n    <div class=\"chart-placeholder\">\n      \ud83d\udcc8 Interactive price charts and trend visualization would appear here<br>\n      (Integrate with Chart.js or similar library for production)\n    </div>\n    \n    <div class=\"footer\">\n      <p><strong>Risk Factors:</strong> ${$('Multi-Scenario Analysis').first().json.riskFactors?.join(', ') || 'None identified'}</p>\n      <p>This analysis combines historical data, AI predictions, and economic indicators. Market conditions may change rapidly.</p>\n      <p>Data source: LTA OneMotoring Portal & MAS Economic Indicators | Powered by n8n + AI</p>\n    </div>\n  </div>\n</body>\n</html>\n`;\n\nreturn [{ json: { htmlReport, subject: `COE Comprehensive Analysis - ${analysisDate}` } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "48ef07ff-cf0a-4f8c-b86e-4b3ad76f48b8",
      "name": "Merge Economic Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        2176,
        848
      ],
      "parameters": {
        "mode": "combine",
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "87a9c973-2735-436f-b42c-aa7d9ac6ef28",
      "name": "Alternative Category Suggester",
      "type": "n8n-nodes-base.code",
      "position": [
        3728,
        768
      ],
      "parameters": {
        "jsCode": "const recommendations = $input.first().json.recommendations;\nconst categories = Object.keys(recommendations).filter(k => !recommendations[k].reasoning?.includes('Insufficient'));\n\nconst alternatives = {};\n\ncategories.forEach(primaryCategory => {\n  const primaryRec = recommendations[primaryCategory];\n  const alternativeOptions = [];\n  \n  categories.forEach(altCategory => {\n    if (altCategory === primaryCategory) return;\n    \n    const altRec = recommendations[altCategory];\n    const priceDiff = altRec.currentPrice - primaryRec.currentPrice;\n    const scoreDiff = altRec.score - primaryRec.score;\n    \n    if (scoreDiff > 10 || (scoreDiff > 0 && Math.abs(priceDiff) < 10000)) {\n      alternativeOptions.push({\n        category: altCategory,\n        priceDiff: Math.round(priceDiff),\n        scoreDiff,\n        reasoning: scoreDiff > 15 ? 'Significantly better market conditions' : 'Comparable conditions with different pricing'\n      });\n    }\n  });\n  \n  alternatives[primaryCategory] = alternativeOptions.sort((a, b) => b.scoreDiff - a.scoreDiff);\n});\n\nreturn [{ json: { alternatives } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "9c13a0c9-5903-4649-9ddb-60a31d11e9f0",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        0
      ],
      "parameters": {
        "width": 1408,
        "height": 352,
        "content": "## Introduction\nAutomates Singapore COE price tracking with AI forecasts and buy/wait recommendations. Weekly scraping collects LTA data, enriches with economic indicators, predicts 6-month trends, and alerts users via Telegram/email\u2014helping car buyers and fleet managers make data-driven purchase decisions while avoiding manual tracking.\n\n## How it Works\nWeekly trigger scrapes LTA COE \u2192 validates \u2192 stores in Google Sheets \u2192 calculates indicators \u2192 AI forecasts trends \u2192 multi-scenario analysis \u2192 generates buy/wait signals \u2192 sends actionable alerts.\n\n## Setup Steps\n1. Add OpenAI/NVIDIA API credentials in n8n\n2. Authenticate Google Sheets and create spreadsheet\n3. Configure Telegram bot or Gmail SMTP\n4. Set weekly trigger (Thursday 9AM SGT post-bidding)\n5. Adjust alert thresholds in conditional nodes\n\n\n\n\n\n\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3d015ecf-03a3-4892-83ac-3f053d099a60",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 672,
        "height": 240,
        "content": "## Prerequisites\nOpenAI/NVIDIA API key, Google Sheets access, Telegram bot token or Gmail, basic COE category understanding\n\n## Use Cases\nFirst-time buyers timing purchases, fleet operators coordinating bulk acquisitions"
      },
      "typeVersion": 1
    },
    {
      "id": "e9250a46-284e-4031-beb5-3fe4db34171c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        0
      ],
      "parameters": {
        "color": 6,
        "width": 528,
        "height": 208,
        "content": "## Customization\nAdd SMS alerts via Twilio, integrate loan calculators for total cost analysis\n\n## Benefits\nSaves 5+ hours monthly, captures 10\u201318% price dips, provides predictive insights (potential $10K\u2013$25K savings)\n \n"
      },
      "typeVersion": 1
    },
    {
      "id": "f15a58ad-3b4c-479b-ab7d-5fde49487373",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        368
      ],
      "parameters": {
        "color": 5,
        "width": 224,
        "height": 496,
        "content": "## Weekly COE Check Trigger\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLaunches workflow every Thursday 9AM SGT, right after LTA bidding closes\nWhy: Captures price data at the moment it matters most\u2014fresh results mean your forecast starts with the latest market signal before prices shift"
      },
      "typeVersion": 1
    },
    {
      "id": "9d882ef7-7853-4912-96bb-bd09d2cbc505",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        368
      ],
      "parameters": {
        "color": 5,
        "width": 448,
        "height": 592,
        "content": "## Scrape LTA COE Data\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\nFetches official COE results with retry logic to handle connection issues\nWhy: LTA is the authoritative source; automated scraping eliminates manual checking while retry logic ensures you never miss a bidding round due to network hiccups"
      },
      "typeVersion": 1
    },
    {
      "id": "e61894a5-ab0c-4556-91eb-0e41990e75e6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        368
      ],
      "parameters": {
        "color": 5,
        "height": 480,
        "content": "## Validate Data Completeness\nChecks that all COE categories arrived and flags any anomalies or missing records\nWhy: Bad data breaks forecasts\u2014catching incomplete records here prevents misleading buy/wait signals downstream"
      },
      "typeVersion": 1
    },
    {
      "id": "45d278ef-4c62-4bbd-baf5-6e479d1f0afa",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        368
      ],
      "parameters": {
        "color": 5,
        "width": 288,
        "height": 896,
        "content": "## Store Data\nAppends timestamped COE records to your spreadsheet with category breakdown\nWhy: Creates permanent audit trail; you can always trace back why a recommendation was made, and historical data feeds better AI predictions over time"
      },
      "typeVersion": 1
    },
    {
      "id": "a2381f74-7fa3-4eb8-8537-6a906b577f30",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        384
      ],
      "parameters": {
        "color": 5,
        "width": 214,
        "height": 720,
        "content": "## Fetch Historical Baseline\nRetrieves previous 12+ months of COE data to establish trends\nWhy: AI needs context\u2014knowing if prices are historically high or low makes 6-month forecasts meaningful, not just noise\n"
      },
      "typeVersion": 1
    },
    {
      "id": "58ce40c0-5080-4b0f-8080-333fe72d8fa2",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        384
      ],
      "parameters": {
        "color": 5,
        "width": 1312,
        "height": 720,
        "content": "## Market Indicators & AI Forecasting- Processing\n\nBuilds key indicators (averages, volatility, seasonality, trends) and uses them to predict COE prices 6 months out.\n**Why:** Converts raw data into fast, clear signals and forecasts so users can judge market direction and decide if waiting helps or hurts.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "877a0f53-544d-4193-aabc-0cab7547e3ae",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 352,
        "content": "\n## Merge Economic Indicators\nEnriches COE data with interest rates, vehicle import taxes, fuel costs, and market sentiment\nWhy: COE doesn't exist in a vacuum\u2014economic context explains why prices move; better forecasts come from understanding the full picture"
      },
      "typeVersion": 1
    },
    {
      "id": "9e1a93a8-4fd9-443a-bb9f-35c5cb7461e4",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2896,
        384
      ],
      "parameters": {
        "color": 5,
        "width": 272,
        "height": 672,
        "content": "## Multi-Scenario Analysis\n\nGenerates three outcomes: best case (wait scenario), base case (realistic), worst case (buy now)\nWhy: Removes false certainty\u2014you see the range of possibilities and can decide your own risk tolerance instead of following one rigid signal"
      },
      "typeVersion": 1
    },
    {
      "id": "852fb988-0a1b-49b2-a690-30052e911113",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        16
      ],
      "parameters": {
        "color": 5,
        "width": 416,
        "height": 1488,
        "content": "## Threshold Comparison & COE Dashboard\n\nChecks forecasts against your custom buy/wait rules, then compiles prices, predictions, recommendations, and confidence levels into a single dashboard.\n**Why:** Supports different decision styles\u2014whether it\u2019s \u201cbuy if >15% savings expected\u201d or \u201cwait if volatility <8%\u201d\u2014and gives stakeholders one clear view without digging through spreadsheets.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "20ea90b4-1802-4093-bb10-bc9910441ea0",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3632,
        16
      ],
      "parameters": {
        "color": 5,
        "width": 464,
        "height": 1088,
        "content": "## Send Telegram/Email Alerts\nDelivers your recommendation, key price changes, and next review date via your preferred channel\nWhy: Information reaches you where you check it\u2014whether you're a solo buyer or coordinating fleet-wide purchases, you stay informed without manual monitoring\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "aea095b0-d476-4f66-afab-fe7253eca58e",
  "connections": {
    "ROI Calculator": {
      "main": [
        [
          {
            "node": "Enhanced Dashboard Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Alternative Category Suggester",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format HTML Report": {
      "main": [
        [
          {
            "node": "Send Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Economic Data": {
      "main": [
        [
          {
            "node": "Enhanced AI Prompt with Economics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Validation Check": {
      "main": [
        [
          {
            "node": "Store in Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Airtable - Store COE Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Handler - Retry Scraping",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - COE Analysis",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Process Economic Data": {
      "main": [
        [
          {
            "node": "Merge Economic Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract COE Price Data": {
      "main": [
        [
          {
            "node": "Store in Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Data Validation Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store in Google Sheets": {
      "main": [
        [
          {
            "node": "Retrieve Historical COE Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - COE Analysis": {
      "main": [
        [
          {
            "node": "Generate Buy Recommendations",
            "type": "main",
            "index": 0
          },
          {
            "node": "Multi-Scenario Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Multi-Scenario Analysis": {
      "main": [
        [
          {
            "node": "Generate Buy Recommendations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape with Retry Logic": {
      "main": [
        [
          {
            "node": "Extract COE Price Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enhanced Dashboard Report": {
      "main": [
        [
          {
            "node": "Send Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Buy Opportunities": {
      "main": [
        [
          {
            "node": "Send Telegram Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Economic Indicators": {
      "main": [
        [
          {
            "node": "Process Economic Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Buy Recommendations": {
      "main": [
        [
          {
            "node": "Format HTML Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check for Buy Opportunities",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Dashboard Summary",
            "type": "main",
            "index": 0
          },
          {
            "node": "ROI Calculator",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check Significant Price Movement",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check Market Volatility",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare AI Prediction Prompt": {
      "main": [
        [
          {
            "node": "AI Agent - COE Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Historical COE Data": {
      "main": [
        [
          {
            "node": "Calculate Technical Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Technical Indicators": {
      "main": [
        [
          {
            "node": "Prepare AI Prediction Prompt",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Economic Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Extract Economic Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Prediction Update Trigger": {
      "main": [
        [
          {
            "node": "Retrieve Historical COE Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Significant Price Movement": {
      "main": [
        [
          {
            "node": "Urgent Market Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape COE Data from OneMotoring": {
      "main": [
        [
          {
            "node": "Extract COE Price Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enhanced AI Prompt with Economics": {
      "main": [
        [
          {
            "node": "AI Agent - COE Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger - Bi-Weekly COE Scraping": {
      "main": [
        [
          {
            "node": "Scrape COE Data from OneMotoring",
            "type": "main",
            "index": 0
          },
          {
            "node": "Scrape with Retry Logic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Automates Singapore COE price tracking with AI forecasts and buy/wait recommendations. Weekly scraping collects LTA data, enriches with economic indicators, predicts 6-month trends, and alerts users via Telegram/email—helping car buyers and fleet managers make data-driven…

Source: https://n8n.io/workflows/10625/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This template is perfect for Gumroad creators, solopreneurs, digital product sellers, and freelancers who want to track and thank customers automatically — without spending time on manual work.

Google Sheets, Gumroad Trigger, Telegram +8
Data & Sheets

Lead Generating Web Scraper & CRM Automation. Uses httpRequest, airtable, googleSheets, gmail. Scheduled trigger; 38 nodes.

HTTP Request, Airtable, Google Sheets +4
Data & Sheets

"This workflow builds an AI-powered resume screening system inside n8n. It begins with Gmail and Form triggers that capture incoming resumes, then uploads each file to Google Drive for storage. The re

Gmail Trigger, Google Drive, Information Extractor +5
Data & Sheets

Anyone curating before/after text examples in a spreadsheet and wanting a push-button path to a fine-tuned GPT model—without touching curl. Works with Google Sheets or Airtable.

HTTP Request, OpenAI, Stop And Error +2
Data & Sheets

This workflow automates the time-consuming process of B2B market research. It takes a list of company website URLs from a Google Sheet, uses BrowserAct to scrape their profiles and news sections, anal

Output Parser Structured, OpenRouter Chat, Airtable +3