{
  "id": "sZRsdOerPYjPfRAJ",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Railway Station Queue Predictor \u2014 Form + Smart Wait Time + ChatGPT AI Advisory + Gmail + Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "be16ddb5-255c-4d20-ac28-e8e73fb7f540",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1600,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 536,
        "height": 1372,
        "content": "## Railway Station Queue Predictor \u2014 Form + Smart Wait Time + ChatGPT AI Advisory + Gmail + Sheets\n\nFor Indian train travelers who want to know how early to reach the station and how to skip the worst queues. The traveler fills a short form with their train and station details. A built-in prediction algorithm (no external API) estimates crowd level, platform wait, ticket-counter queue, security time, and the ideal arrival time. A ChatGPT AI model then writes a friendly, plain-English advisory with station-specific tips. The traveler gets a colour-coded email alert, and every prediction is logged to Google Sheets.\n\n## What each node does\n- **1. Form \u2014 Traveler Train Details** collects train number, station, travel date and time, class, e-ticket status, and passenger count\n- **2. Code \u2014 Predict Queue Wait Times** runs the rule-based algorithm and returns crowd score, all wait times, alert level, and the recommended arrival time\n- **3. HTTP \u2014 AI Write Advisory** sends the prediction to a ChatGPT AI model and gets back a short traveler advisory\n- **4. Code \u2014 Parse Advisory and Alert Colors** merges the advisory and assigns the colour-coded alert level\n- **5. Code \u2014 Build HTML Email** builds the full colour-coded alert email\n- **6. Sheets \u2014 Log Prediction** appends the prediction to the log (parallel branch)\n- **7. Gmail \u2014 Send Queue Alert to Traveler** emails the alert to the traveler (parallel branch)\n\n## How the prediction works\nThe algorithm in node 2 scores expected crowding from day of week, time of day, festival season, station size, train type, and class of travel. Alert levels: GREEN (light), YELLOW (moderate), ORANGE (busy), RED (very crowded). It is a rule-based estimate, not live station data.\n\n## Google Sheet setup\nCreate a Google Sheet, add a tab named **Predictions**, and put these column headers in row 1:\nDate, Train Number, Train Name, Station, Travel Date, Departure Time, Class, Passengers, Has E-Ticket, Crowd Score, Alert Level, Platform Wait (min), Ticket Queue (min), Security Time (min), Total Buffer (min), Optimal Arrival, Traveler Email, Advisory Sent\n\n## Set up steps\n1. **OpenAI** \u2014 in **3. HTTP \u2014 AI Write Advisory**, connect your OpenAI API credential. A ChatGPT AI model is used for the advisory\n2. **Google Sheets** \u2014 in **6. Sheets \u2014 Log Prediction**, connect Google Sheets OAuth2 and replace `YOUR_QUEUE_LOG_SHEET_ID` with your sheet ID (from the sheet URL after /d/)\n3. **Gmail** \u2014 in **7. Gmail \u2014 Send Queue Alert to Traveler**, connect Gmail OAuth2. The recipient comes from the form\n4. Activate the workflow, open the Form URL, enter train details, and submit. The alert is emailed and logged instantly"
      },
      "typeVersion": 1
    },
    {
      "id": "e29779ae-ac73-4a1e-bc32-496fb658e411",
      "name": "Section \u2014 Form and Prediction Algorithm",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1008,
        48
      ],
      "parameters": {
        "color": 5,
        "width": 484,
        "height": 324,
        "content": "## Form and Prediction Algorithm\nTraveler fills train details. Code calculates crowd score, platform wait, ticket queue, security time, and the optimal arrival time \u2014 all without any external API."
      },
      "typeVersion": 1
    },
    {
      "id": "2fba56e1-5ac8-4d3d-994b-60065f1c73f7",
      "name": "Section \u2014 AI Advisory and Alert Level",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        48
      ],
      "parameters": {
        "color": 6,
        "width": 452,
        "height": 372,
        "content": "## AI Advisory and Alert Level\nThe ChatGPT AI model writes a friendly traveler advisory with station tips. Code parses the response and assigns the colour-coded alert level."
      },
      "typeVersion": 1
    },
    {
      "id": "2d161391-2d4f-4fa4-9962-63fc1151b407",
      "name": "Section \u2014 Build Email, Log, and Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -144
      ],
      "parameters": {
        "color": 4,
        "width": 660,
        "height": 628,
        "content": "## Build Email, Log, and Send\nCode builds the colour-coded HTML email. Two parallel branches run: Sheets logs the prediction, and Gmail sends the alert to the traveler."
      },
      "typeVersion": 1
    },
    {
      "id": "0fb166ca-0a2d-4ba6-bd80-a53080bbfb2f",
      "name": "1. Form \u2014 Traveler Train Details",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -928,
        192
      ],
      "parameters": {
        "options": {
          "path": "railway-queue-predictor",
          "buttonLabel": "Predict My Queue Times",
          "appendAttribution": false
        },
        "formTitle": "Railway Queue Predictor",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Your Name",
              "placeholder": "e.g. Rahul Kumar",
              "requiredField": true
            },
            {
              "fieldLabel": "Your Email",
              "placeholder": "your@email.com \u2014 advisory will be sent here",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Number",
              "placeholder": "e.g. 12301 or 22691",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Name",
              "placeholder": "e.g. Rajdhani Express, Shatabdi, Duronto, Express",
              "requiredField": true
            },
            {
              "fieldLabel": "Departure Station Code",
              "placeholder": "e.g. NDLS, CSMT, HWH, MAS, SBC, ADI, PUNE, LKO",
              "requiredField": true
            },
            {
              "fieldLabel": "Travel Date",
              "placeholder": "YYYY-MM-DD e.g. 2025-07-15",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Departure Time",
              "placeholder": "HH:MM in 24hr format e.g. 06:30 or 17:45",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Class of Travel",
              "fieldOptions": {
                "values": [
                  {
                    "option": "SL \u2014 Sleeper Class"
                  },
                  {
                    "option": "3A \u2014 AC 3 Tier"
                  },
                  {
                    "option": "2A \u2014 AC 2 Tier"
                  },
                  {
                    "option": "1A \u2014 AC First Class"
                  },
                  {
                    "option": "CC \u2014 Chair Car"
                  },
                  {
                    "option": "2S \u2014 Second Sitting"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Do you have IRCTC e-ticket on app?",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Yes \u2014 I have digital ticket on IRCTC app"
                  },
                  {
                    "option": "No \u2014 I need to collect physical ticket or PRS printout"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Number of Passengers",
              "placeholder": "e.g. 1, 2, 4",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Enter your train details to get a predicted crowd and wait time report for your departure station. You will receive a personalized advisory email with tips to avoid queues."
      },
      "typeVersion": 2.2
    },
    {
      "id": "ed593746-0cf1-47df-8bcb-f46871f38d3f",
      "name": "2. Code \u2014 Predict Queue Wait Times",
      "type": "n8n-nodes-base.code",
      "position": [
        -688,
        192
      ],
      "parameters": {
        "jsCode": "// RAILWAY STATION QUEUE PREDICTION ALGORITHM\n// Predicts crowd levels and wait times based on multiple factors\n// No external API needed \u2014 rule-based scoring system\n\nconst row = $json;\n\n// --- Extract form inputs ---\nconst travelerName  = (row['Your Name']              || '').trim();\nconst email         = (row['Your Email']             || '').trim();\nconst trainNo       = (row['Train Number']           || '').trim();\nconst trainName     = (row['Train Name']             || '').trim();\nconst stationCode   = (row['Departure Station Code'] || '').trim().toUpperCase();\nconst travelDate    = (row['Travel Date']            || '').trim();\nconst departureTime = (row['Train Departure Time']   || '').trim();\nconst travelClass   = (row['Class of Travel']        || '').trim();\nconst hasETicket    = (row['Do you have IRCTC e-ticket on app?'] || '').startsWith('Yes');\nconst passengers    = parseInt(row['Number of Passengers']) || 1;\n\nif (!email || !trainNo || !stationCode || !travelDate || !departureTime) {\n  throw new Error('Train number, station code, travel date, departure time, and email are all required.');\n}\n\n// --- Parse travel date and time ---\nconst travelDateObj = new Date(travelDate);\nconst dayOfWeek     = travelDateObj.getDay(); // 0=Sun, 6=Sat\nconst month         = travelDateObj.getMonth() + 1; // 1-12\nconst [depHour, depMin] = departureTime.split(':').map(Number);\nconst depMinutes    = (depHour * 60) + (depMin || 0);\n\n// --- FACTOR 1: Day of week multiplier ---\nconst dayMultiplier =\n  dayOfWeek === 0 ? 1.35 :\n  dayOfWeek === 1 ? 1.25 :\n  dayOfWeek === 5 ? 1.20 :\n  dayOfWeek === 6 ? 1.30 :\n  1.0;\n\n// --- FACTOR 2: Time of day multiplier ---\nconst timeMultiplier =\n  (depMinutes >= 300 && depMinutes < 540)  ? 1.35 :\n  (depMinutes >= 960 && depMinutes < 1260) ? 1.30 :\n  (depMinutes >= 1260 || depMinutes < 300) ? 1.15 :\n  1.0;\n\n// --- FACTOR 3: Season/festival multiplier ---\nconst seasonMultiplier =\n  (month === 10 || month === 11) ? 1.40 :\n  (month === 12 || month === 1)  ? 1.25 :\n  (month === 5  || month === 6)  ? 1.30 :\n  month === 3                    ? 1.20 :\n  1.0;\n\n// --- FACTOR 4: Station size multiplier ---\nconst stationMultipliers = {\n  'NDLS': 1.50, 'DLI':  1.40, 'CSMT': 1.45, 'BCT':  1.35,\n  'HWH':  1.40, 'SDAH': 1.25, 'MAS':  1.35, 'SBC':  1.30,\n  'ADI':  1.30, 'PUNE': 1.25, 'LKO':  1.30, 'PNBE': 1.35,\n  'JP':   1.25, 'HYB':  1.30, 'SC':   1.25\n};\nconst stationMult = stationMultipliers[stationCode] || 1.15;\n\n// --- FACTOR 5: Train type multiplier ---\nconst trainNameLower = trainName.toLowerCase();\nconst trainMultiplier =\n  (trainNameLower.includes('rajdhani') || trainNameLower.includes('shatabdi') || trainNameLower.includes('tejas') || trainNameLower.includes('vande')) ? 0.75 :\n  (trainNameLower.includes('duronto') || trainNameLower.includes('gatimaan')) ? 0.80 :\n  (trainNameLower.includes('superfast') || trainNameLower.includes('sf')) ? 1.05 :\n  (trainNameLower.includes('express')) ? 1.10 :\n  1.20;\n\n// --- FACTOR 6: Class multiplier ---\nconst classCode       = travelClass.split('\u2014')[0].trim();\nconst classMultiplier =\n  classCode === 'SL' ? 1.40 :\n  classCode === '2S' ? 1.30 :\n  classCode === 'CC' ? 1.10 :\n  classCode === '3A' ? 0.90 :\n  classCode === '2A' ? 0.80 :\n  classCode === '1A' ? 0.70 :\n  1.0;\n\n// --- COMBINED CROWD SCORE ---\nconst baseCrowdScore = 50;\nconst rawScore = baseCrowdScore * dayMultiplier * timeMultiplier * seasonMultiplier * stationMult * trainMultiplier * classMultiplier;\nconst crowdScore = Math.min(Math.round(rawScore), 100);\n\n// --- WAIT TIME CALCULATIONS (in minutes) ---\nconst platformWait = Math.round((crowdScore / 100) * 25 + 5);\nconst ticketQueue = hasETicket ? 0 : Math.round((crowdScore / 100) * 35 + 5);\nconst hasSecurityCheck = ['NDLS', 'DLI', 'CSMT', 'HWH', 'MAS', 'SBC'].includes(stationCode);\nconst securityTime = hasSecurityCheck ? Math.round((crowdScore / 100) * 20 + 5) : 0;\nconst platformWalk = stationMult > 1.35 ? 12 : stationMult > 1.25 ? 8 : 5;\nconst totalBuffer = platformWait + ticketQueue + securityTime + platformWalk + (passengers > 3 ? 10 : 0);\n\n// --- Optimal arrival time ---\nconst optimalArrivalMinutes = depMinutes - totalBuffer;\nconst optHour = Math.floor(((optimalArrivalMinutes % 1440) + 1440) % 1440 / 60);\nconst optMin  = Math.floor(((optimalArrivalMinutes % 1440) + 1440) % 1440 % 60);\nconst optimalArrival = String(optHour).padStart(2,'0') + ':' + String(optMin).padStart(2,'0');\n\n// --- ALERT LEVEL ---\nconst alertLevel =\n  crowdScore >= 80 ? 'RED' :\n  crowdScore >= 60 ? 'ORANGE' :\n  crowdScore >= 40 ? 'YELLOW' :\n  'GREEN';\n\n// --- BUILD CONTEXT FOR AI ---\nconst predictionContext = [\n  'Traveler: ' + travelerName,\n  'Train: ' + trainNo + ' ' + trainName,\n  'Station: ' + stationCode,\n  'Travel Date: ' + travelDate,\n  'Day of Week: ' + ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][dayOfWeek],\n  'Departure Time: ' + departureTime,\n  'Class: ' + travelClass,\n  'Passengers: ' + passengers,\n  'Has e-Ticket: ' + (hasETicket ? 'Yes' : 'No'),\n  '',\n  'PREDICTION RESULTS:',\n  'Crowd Score: ' + crowdScore + '/100',\n  'Alert Level: ' + alertLevel,\n  'Platform Wait: ' + platformWait + ' min',\n  'Ticket Counter Queue: ' + (hasETicket ? 'Skipped (e-ticket)' : ticketQueue + ' min'),\n  'Security Check: ' + (hasSecurityCheck ? securityTime + ' min' : 'Not required at this station'),\n  'Platform Walk: ' + platformWalk + ' min',\n  'Total Buffer Needed: ' + totalBuffer + ' minutes',\n  'Recommended Arrival: ' + optimalArrival + ' (arrive by this time)',\n  '',\n  'Factors driving this prediction:',\n  'Day multiplier: ' + dayMultiplier + 'x (' + ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][dayOfWeek] + ')',\n  'Time of day: ' + timeMultiplier + 'x (departure at ' + departureTime + ')',\n  'Season: ' + seasonMultiplier + 'x (month ' + month + ')',\n  'Station size: ' + stationMult + 'x (' + stationCode + ')',\n  'Train type: ' + trainMultiplier + 'x (' + trainName + ')',\n  'Class: ' + classMultiplier + 'x (' + classCode + ')'\n].join('\\n');\n\nconst loggedAt = new Date().toISOString();\n\nreturn [{\n  json: {\n    travelerName, email, trainNo, trainName, stationCode,\n    travelDate, departureTime, travelClass, classCode,\n    hasETicket, passengers, dayOfWeek,\n    crowdScore, alertLevel,\n    platformWait, ticketQueue, securityTime,\n    platformWalk, totalBuffer, optimalArrival,\n    hasSecurityCheck,\n    predictionContext,\n    loggedAt\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9d90dfa5-5125-4296-91c5-5ebec9534f39",
      "name": "3. HTTP \u2014 AI Write Advisory",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -448,
        192
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0.5,\n  \"max_tokens\": 800,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a helpful railway travel assistant for Indian train travelers. Write a friendly, practical travel advisory in simple English. Be specific, actionable, and conversational. Do not use markdown formatting \u2014 plain text paragraphs only.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify(\n        \"Write a short traveler advisory based on this queue prediction.\\n\\n\" + $json.predictionContext +\n        \"\\n\\nInclude these points in your advisory (use plain text, no markdown, no bullets, no headers):\\n\" +\n        \"1. One opening sentence mentioning their train and the crowd level prediction.\\n\" +\n        \"2. A specific recommendation on what time to arrive and why.\\n\" +\n        \"3. One practical tip for their class of travel (\" + $json.classCode + \") at \" + $json.stationCode + \" station to save time.\\n\" +\n        \"4. One tip about the ticket counter situation based on whether they have an e-ticket or not.\\n\" +\n        \"5. One general safety or comfort tip for travelling with \" + $json.passengers + \" passengers.\\n\" +\n        \"Keep the entire advisory under 120 words. Be warm and helpful.\"\n      ) }}\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c10c5399-6ff7-4deb-9141-7593eefacb2a",
      "name": "4. Code \u2014 Parse Advisory and Alert Colors",
      "type": "n8n-nodes-base.code",
      "position": [
        -208,
        192
      ],
      "parameters": {
        "jsCode": "// Extract AI advisory text and merge with prediction data\nconst resp       = $input.first().json;\nconst prediction = $('2. Code \u2014 Predict Queue Wait Times').first().json;\n\nconst advisory = resp?.choices?.[0]?.message?.content || 'Please arrive at least ' + prediction.totalBuffer + ' minutes before your train departure. Check the station board for your platform number.';\n\n// Alert level colors for email\nconst alertColor =\n  prediction.alertLevel === 'RED'    ? '#e74c3c' :\n  prediction.alertLevel === 'ORANGE' ? '#e67e22' :\n  prediction.alertLevel === 'YELLOW' ? '#f39c12' :\n  '#27ae60';\n\nconst alertBg =\n  prediction.alertLevel === 'RED'    ? '#fdf0f0' :\n  prediction.alertLevel === 'ORANGE' ? '#fdf5ec' :\n  prediction.alertLevel === 'YELLOW' ? '#fefde7' :\n  '#f0fdf4';\n\nconst alertText =\n  prediction.alertLevel === 'RED'    ? 'Very Crowded \u2014 Arrive 90+ minutes early' :\n  prediction.alertLevel === 'ORANGE' ? 'Busy Station \u2014 Arrive 60-90 minutes early' :\n  prediction.alertLevel === 'YELLOW' ? 'Moderate Crowd \u2014 Arrive 45-60 minutes early' :\n  'Light Crowd \u2014 Arrive 30 minutes early is sufficient';\n\nreturn [{\n  json: {\n    ...prediction,\n    advisory: advisory.trim(),\n    alertColor,\n    alertBg,\n    alertText\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "8185aa7b-1c5a-4e1c-9db0-4a0ee7bd73bc",
      "name": "5. Code \u2014 Build HTML Email",
      "type": "n8n-nodes-base.code",
      "position": [
        48,
        192
      ],
      "parameters": {
        "jsCode": "// Build the complete HTML email for the traveler\nconst d = $input.first().json;\n\nconst fmtMin = (m) => m === 0 ? 'Skipped' : m + ' min';\n\nconst htmlEmail =\n  '<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>'\n  + '<body style=\"font-family:Arial,sans-serif;max-width:640px;margin:0 auto;background:#f5f5f5;\">'\n\n  + '<div style=\"background:#1a237e;padding:24px 28px;border-radius:8px 8px 0 0;\">'\n  + '<h1 style=\"color:#fff;font-size:19px;margin:0;\">Railway Queue Predictor</h1>'\n  + '<p style=\"color:#c5cae9;font-size:13px;margin:6px 0 0;\">' + d.travelerName + ' \u2014 Train ' + d.trainNo + ' ' + d.trainName + '</p>'\n  + '</div>'\n\n  + '<div style=\"background:' + d.alertColor + ';padding:16px 24px;\">'\n  + '<div style=\"color:#fff;font-size:16px;font-weight:700;\">' + d.alertLevel + ' \u2014 ' + d.alertText + '</div>'\n  + '<div style=\"color:rgba(255,255,255,0.9);font-size:13px;margin-top:2px;\">Crowd Score: ' + d.crowdScore + '/100</div>'\n  + '</div>'\n\n  + '<div style=\"background:#283593;padding:10px 24px;display:flex;gap:20px;flex-wrap:wrap;\">'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.travelDate + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.stationCode + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">Departs ' + d.departureTime + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.classCode + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.passengers + ' passenger(s)</span>'\n  + '</div>'\n\n  + '<div style=\"background:#fff;padding:20px 24px;\">'\n\n  + '<div style=\"background:' + d.alertBg + ';border:2px solid ' + d.alertColor + ';border-radius:8px;padding:16px 20px;text-align:center;margin-bottom:20px;\">'\n  + '<div style=\"font-size:12px;color:#666;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px;\">Recommended Arrival Time</div>'\n  + '<div style=\"font-size:32px;font-weight:700;color:' + d.alertColor + ';\">' + d.optimalArrival + '</div>'\n  + '<div style=\"font-size:13px;color:#555;margin-top:4px;\">Arrive by this time \u2014 total buffer needed: <strong>' + d.totalBuffer + ' minutes</strong></div>'\n  + '</div>'\n\n  + '<h2 style=\"font-size:15px;color:#1a237e;border-bottom:2px solid #eee;padding-bottom:8px;margin:0 0 14px;\">Queue Time Breakdown</h2>'\n  + '<table style=\"width:100%;border-collapse:collapse;font-size:14px;margin-bottom:20px;\">'\n  + '<tr style=\"background:#e8eaf6;\"><th style=\"padding:9px 12px;text-align:left;font-size:12px;color:#3f51b5;\">Queue Type</th><th style=\"padding:9px 12px;text-align:right;font-size:12px;color:#3f51b5;\">Predicted Wait</th><th style=\"padding:9px 12px;text-align:right;font-size:12px;color:#3f51b5;\">Status</th></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Platform Walk</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + d.platformWalk + ' min</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">Always required</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Security Check</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + fmtMin(d.securityTime) + '</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">' + (d.hasSecurityCheck ? 'Required at ' + d.stationCode : 'Not required') + '</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Ticket Counter</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + fmtMin(d.ticketQueue) + '</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:' + (d.hasETicket ? '#27ae60' : '#e74c3c') + ';\">' + (d.hasETicket ? 'E-ticket \u2014 skip queue' : 'Physical ticket needed') + '</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Platform Wait</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + d.platformWait + ' min</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">Find seat, settle luggage</td></tr>'\n  + '<tr style=\"background:#f0f4ff;\"><td style=\"padding:9px 12px;font-weight:700;color:#1a237e;\">Total Buffer</td><td style=\"padding:9px 12px;text-align:right;font-weight:700;color:' + d.alertColor + ';\">' + d.totalBuffer + ' min</td><td style=\"padding:9px 12px;text-align:right;font-size:12px;color:' + d.alertColor + ';font-weight:600;\">' + d.alertLevel + '</td></tr>'\n  + '</table>'\n\n  + '<div style=\"background:#e8eaf6;border-left:4px solid #3f51b5;padding:14px;border-radius:0 6px 6px 0;margin-bottom:16px;\">'\n  + '<h2 style=\"margin:0 0 8px;font-size:14px;color:#1a237e;\">Travel Advisory</h2>'\n  + '<p style=\"margin:0;font-size:14px;color:#333;line-height:1.7;\">' + d.advisory + '</p>'\n  + '</div>'\n\n  + '<div style=\"background:#e8f5e9;border-left:4px solid #43a047;padding:12px 14px;border-radius:0 6px 6px 0;\">'\n  + '<h2 style=\"margin:0 0 8px;font-size:13px;color:#1b5e20;\">Quick Tips</h2>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Keep your ticket and ID ready before entering the station</p>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Check the NTES app for real-time train location and platform number</p>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Platform number is usually announced 20-30 minutes before departure</p>'\n  + (d.hasETicket ? '<p style=\"margin:0;font-size:13px;color:#27ae60;font-weight:600;\">- You have an e-ticket \u2014 go straight to your platform and skip the counter</p>' : '<p style=\"margin:0;font-size:13px;color:#e74c3c;font-weight:600;\">- You need a physical ticket \u2014 reach early to avoid the counter queue</p>')\n  + '</div>'\n\n  + '</div>'\n\n  + '<div style=\"background:#1a237e;padding:12px 24px;text-align:center;border-radius:0 0 8px 8px;\">'\n  + '<p style=\"color:#c5cae9;font-size:11px;margin:0;\">Railway Queue Predictor \u2014 n8n + ChatGPT AI</p>'\n  + '<p style=\"color:#7986cb;font-size:11px;margin:4px 0 0;\">Prediction based on general patterns. Always allow extra time for unexpected delays.</p>'\n  + '</div>'\n  + '</body></html>';\n\nreturn [{\n  json: {\n    ...d,\n    htmlEmail,\n    emailSubject: d.alertLevel + ' Queue Alert \u2014 Train ' + d.trainNo + ' | Arrive by ' + d.optimalArrival + ' | ' + d.stationCode\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bc2d4ac8-8376-466d-b475-b174fad1e24d",
      "name": "6. Sheets \u2014 Log Prediction",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        288,
        32
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.loggedAt.split('T')[0] }}",
            "Class": "={{ $json.classCode }}",
            "Station": "={{ $json.stationCode }}",
            "Passengers": "={{ $json.passengers }}",
            "Train Name": "={{ $json.trainName }}",
            "Alert Level": "={{ $json.alertLevel }}",
            "Crowd Score": "={{ $json.crowdScore }}",
            "Travel Date": "={{ $json.travelDate }}",
            "Has E-Ticket": "={{ $json.hasETicket ? 'Yes' : 'No' }}",
            "Train Number": "={{ $json.trainNo }}",
            "Advisory Sent": "Yes",
            "Departure Time": "={{ $json.departureTime }}",
            "Traveler Email": "={{ $json.email }}",
            "Optimal Arrival": "={{ $json.optimalArrival }}",
            "Ticket Queue (min)": "={{ $json.ticketQueue }}",
            "Total Buffer (min)": "={{ $json.totalBuffer }}",
            "Platform Wait (min)": "={{ $json.platformWait }}",
            "Security Time (min)": "={{ $json.securityTime }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Train Number",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Train Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Train Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Train Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Station",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Station",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Travel Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Travel Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Departure Time",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Departure Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Class",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Class",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Passengers",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Passengers",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Has E-Ticket",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Has E-Ticket",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Crowd Score",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Crowd Score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Alert Level",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Alert Level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Platform Wait (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Platform Wait (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket Queue (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Ticket Queue (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Security Time (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Security Time (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Buffer (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Total Buffer (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Optimal Arrival",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Optimal Arrival",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Traveler Email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Traveler Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Advisory Sent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Advisory Sent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Predictions"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_QUEUE_LOG_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5c57ee84-93ca-4eb1-81e8-bec30f82408d",
      "name": "7. Gmail \u2014 Send Queue Alert to Traveler",
      "type": "n8n-nodes-base.gmail",
      "position": [
        288,
        224
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "={{ $json.htmlEmail }}",
        "options": {
          "senderName": "Railway Queue Predictor"
        },
        "subject": "={{ $json.emailSubject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "495ddbc1-3420-4ce4-972f-c4f44c63a72f",
  "nodeGroups": [],
  "connections": {
    "5. Code \u2014 Build HTML Email": {
      "main": [
        [
          {
            "node": "6. Sheets \u2014 Log Prediction",
            "type": "main",
            "index": 0
          },
          {
            "node": "7. Gmail \u2014 Send Queue Alert to Traveler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP \u2014 AI Write Advisory": {
      "main": [
        [
          {
            "node": "4. Code \u2014 Parse Advisory and Alert Colors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Form \u2014 Traveler Train Details": {
      "main": [
        [
          {
            "node": "2. Code \u2014 Predict Queue Wait Times",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Code \u2014 Predict Queue Wait Times": {
      "main": [
        [
          {
            "node": "3. HTTP \u2014 AI Write Advisory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code \u2014 Parse Advisory and Alert Colors": {
      "main": [
        [
          {
            "node": "5. Code \u2014 Build HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}