{
  "id": "4vQkJTdJPHnD0XUa",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Live Flight Fare Tracker \u2013 Instant Price Drop Alerts via SMS & Email",
  "tags": [],
  "nodes": [
    {
      "id": "aa5f054a-6239-46c1-8ab3-492796e1f1c1",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.cron",
      "position": [
        -1440,
        380
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 15
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d308fdf-5c40-46ab-b695-a7c95710ada2",
      "name": "Fetch Flight Data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1220,
        380
      ],
      "parameters": {
        "url": "https://api.aviationstack.com/v1/flights",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "access_key",
              "value": "0987c6845c09876yt"
            },
            {
              "name": "dep_iata",
              "value": "JFK"
            },
            {
              "name": "arr_iata",
              "value": "LAX"
            },
            {
              "name": "limit",
              "value": "10"
            }
          ]
        }
      },
      "credentials": {
        "httpQueryAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "f4675a74-c27d-4b8b-bb8e-624687125b4a",
      "name": "Process Flight Data",
      "type": "n8n-nodes-base.function",
      "position": [
        -1000,
        380
      ],
      "parameters": {
        "functionCode": "// Process flight data and check for fare changes\nconst currentFlights = items[0].json.data || [];\nconst processedFlights = [];\n\nfor (const flight of currentFlights) {\n  if (flight.flight_status === 'scheduled' && flight.departure) {\n    // Extract fare information (you may need to adapt based on API response)\n    const flightInfo = {\n      flight_number: flight.flight.iata,\n      airline: flight.airline.name,\n      departure: flight.departure.airport,\n      arrival: flight.arrival.airport,\n      departure_time: flight.departure.scheduled,\n      arrival_time: flight.arrival.scheduled,\n      // Mock fare data - replace with actual fare from your chosen API\n      current_fare: Math.floor(Math.random() * 500) + 200,\n      route: `${flight.departure.iata}-${flight.arrival.iata}`,\n      timestamp: new Date().toISOString()\n    };\n    \n    processedFlights.push(flightInfo);\n  }\n}\n\nreturn processedFlights.map(flight => ({ json: flight }));"
      },
      "executeOnce": true,
      "typeVersion": 1
    },
    {
      "id": "87b6bde3-fe0a-47a2-9410-ddb8d78cbf77",
      "name": "Check if Alert Needed",
      "type": "n8n-nodes-base.if",
      "position": [
        -340,
        380
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.fare_change }}",
              "operation": "notEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "11309f53-cdc0-4cf9-b060-d55ca3e9ca74",
      "name": "Format Alert Message",
      "type": "n8n-nodes-base.function",
      "position": [
        -80,
        340
      ],
      "parameters": {
        "functionCode": "// Format alert message\nconst flight = items[0].json;\nconst alertType = flight.alert_type;\nconst emoji = alertType === 'PRICE_DROP' ? '\ud83d\udcc9' : '\ud83d\udcc8';\nconst alertColor = alertType === 'PRICE_DROP' ? 'good' : 'warning';\n\nconst message = {\n  email: {\n    subject: `${emoji} Flight Fare Alert: ${flight.flight_number}`,\n    html: `\n      <h2>${emoji} Fare ${alertType.replace('_', ' ')} Alert</h2>\n      <p><strong>Flight:</strong> ${flight.flight_number} (${flight.airline})</p>\n      <p><strong>Route:</strong> ${flight.departure} \u2192 ${flight.arrival}</p>\n      <p><strong>Departure:</strong> ${new Date(flight.departure_time).toLocaleString()}</p>\n      <p><strong>Previous Fare:</strong> $${flight.previous_fare}</p>\n      <p><strong>Current Fare:</strong> $${flight.current_fare}</p>\n      <p><strong>Change:</strong> $${flight.fare_change} (${flight.percentage_change}%)</p>\n      <p style=\"color: ${alertType === 'PRICE_DROP' ? 'green' : 'red'};\"><strong>Recommendation:</strong> ${alertType === 'PRICE_DROP' ? 'Consider booking now!' : 'Price increased - monitor for drops'}</p>\n    `\n  },\n  slack: {\n    text: `${emoji} Flight Fare Alert`,\n    attachments: [\n      {\n        color: alertColor,\n        fields: [\n          {\n            title: \"Flight\",\n            value: `${flight.flight_number} (${flight.airline})`,\n            short: true\n          },\n          {\n            title: \"Route\",\n            value: `${flight.departure} \u2192 ${flight.arrival}`,\n            short: true\n          },\n          {\n            title: \"Previous Fare\",\n            value: `$${flight.previous_fare}`,\n            short: true\n          },\n          {\n            title: \"Current Fare\",\n            value: `$${flight.current_fare}`,\n            short: true\n          },\n          {\n            title: \"Change\",\n            value: `$${flight.fare_change} (${flight.percentage_change}%)`,\n            short: false\n          }\n        ]\n      }\n    ]\n  },\n  sms: `${emoji} FARE ALERT: ${flight.flight_number} ${flight.departure}-${flight.arrival} fare changed from $${flight.previous_fare} to $${flight.current_fare} (${flight.percentage_change}%)`\n};\n\nreturn [{ json: { ...flight, formatted_messages: message } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "026afcab-023a-4ef4-9573-28ba32edc01c",
      "name": "Log Alert Activity",
      "type": "n8n-nodes-base.function",
      "position": [
        460,
        400
      ],
      "parameters": {
        "functionCode": "// Log alert activity\nconst alert = items[0].json;\n\nconst logEntry = {\n  timestamp: new Date().toISOString(),\n  flight_number: alert.flight_number,\n  route: alert.route,\n  alert_type: alert.alert_type,\n  fare_change: alert.fare_change,\n  percentage_change: alert.percentage_change,\n  notification_sent: true\n};\n\nconsole.log('Fare Alert Sent:', logEntry);\n\nreturn [{ json: logEntry }];"
      },
      "typeVersion": 1
    },
    {
      "id": "374b1e8e-15b3-4109-9c46-01406c0e4ca5",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        -580,
        380
      ],
      "parameters": {
        "jsCode": "// Get current flight data from previous node (Process Flight Data)\nconst currentFlightsItems = $('Process Flight Data').all();\n\n// Get stored fare data from Google Sheets node\nconst sheetsData = $('Previous flight data').all();\n\nconsole.log('Current flights items:', currentFlightsItems.length);\nconsole.log('Sheets data items:', sheetsData.length);\n\n// Build lookup object from Google Sheets data (remove duplicates)\nconst storedFares = {};\nconst uniqueSheetRows = new Map();\n\n// Remove duplicates from sheets data first\nfor (const row of sheetsData) {\n  const rowData = row.json;\n  if (rowData.flight_number && rowData.route) {\n    const routeKey = `${rowData.flight_number}_${rowData.route}`;\n    \n    // Keep only the first occurrence of each flight\n    if (!uniqueSheetRows.has(routeKey)) {\n      uniqueSheetRows.set(routeKey, rowData);\n      storedFares[routeKey] = parseFloat(rowData.current_fare || 0);\n    }\n  }\n}\n\nconsole.log('Stored fares lookup:', Object.keys(storedFares));\n\nconst alertFlights = [];\nconst fareUpdates = [];\n\n// Process each current flight item\nfor (const flightItem of currentFlightsItems) {\n  const flightData = flightItem.json;\n  \n  if (!flightData.flight_number || !flightData.route) {\n    console.log('Skipping invalid flight data:', flightData);\n    continue;\n  }\n  \n  const routeKey = `${flightData.flight_number}_${flightData.route}`;\n  const currentFare = parseFloat(flightData.current_fare || 0);\n  const previousFare = storedFares[routeKey];\n  \n  console.log(`Processing ${routeKey}: Current=${currentFare}, Previous=${previousFare}`);\n  \n  if (previousFare && previousFare !== currentFare && currentFare > 0) {\n    const fareChange = currentFare - previousFare;\n    const percentageChange = (fareChange / previousFare) * 100;\n    \n    console.log(`${routeKey}: Change=${fareChange}, Percentage=${percentageChange.toFixed(2)}%`);\n    \n    // Alert if fare decreased by 10% or more, or increased by 15% or more\n    if (percentageChange <= -10 || percentageChange >= 15) {\n      alertFlights.push({\n        ...flightData,\n        previous_fare: previousFare,\n        fare_change: fareChange,\n        percentage_change: Math.round(percentageChange * 100) / 100,\n        alert_type: percentageChange < 0 ? 'PRICE_DROP' : 'PRICE_INCREASE',\n        alert_message: percentageChange < 0 \n          ? `\ud83d\udd25 PRICE DROP: ${Math.abs(percentageChange).toFixed(1)}% decrease!` \n          : `\u26a0\ufe0f PRICE INCREASE: ${percentageChange.toFixed(1)}% increase!`\n      });\n      \n      console.log(`ALERT TRIGGERED for ${routeKey}: ${percentageChange < 0 ? 'DROP' : 'INCREASE'} of ${Math.abs(percentageChange).toFixed(2)}%`);\n    }\n  } else if (!previousFare) {\n    console.log(`New flight detected: ${routeKey}`);\n  } else if (currentFare <= 0) {\n    console.log(`Invalid current fare for ${routeKey}: ${currentFare}`);\n  }\n  \n  // Prepare fare updates for Google Sheets (to update stored fares)\n  if (currentFare > 0) {\n    fareUpdates.push({\n      row_number: uniqueSheetRows.get(routeKey)?.row_number || null,\n      flight_number: flightData.flight_number,\n      airline: flightData.airline || 'Unknown',\n      departure: flightData.departure || 'Unknown',\n      arrival: flightData.arrival || 'Unknown',\n      departure_time: flightData.departure_time || '',\n      arrival_time: flightData.arrival_time || '',\n      current_fare: currentFare,\n      route: flightData.route,\n      timestamp: flightData.timestamp || new Date().toISOString(),\n      last_updated: new Date().toISOString()\n    });\n  }\n}\n\nconsole.log(`Total alerts generated: ${alertFlights.length}`);\nif (alertFlights.length > 0) {\n  console.log(`Alerts for flights:`, alertFlights.map(f => f.flight_number));\n}\n\n// Store fare updates in node context for the Google Sheets update node\n$node[\"Code\"].context = { fareUpdates };\n\n// If no alerts, return empty array but still process\nif (alertFlights.length === 0) {\n  console.log('No fare alerts triggered');\n  return [];\n}\n\n// Return alert flights for notification processing\nreturn alertFlights.map(flight => ({ json: flight }));\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "43084297-fd73-45ae-83b8-3e31db490777",
      "name": "Telegram",
      "type": "n8n-nodes-base.telegram",
      "onError": "continueRegularOutput",
      "position": [
        180,
        480
      ],
      "parameters": {
        "text": "={{ $json.formatted_messages.sms }}",
        "chatId": "123SSHSJNASB",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "retryOnFail": false,
      "typeVersion": 1.2
    },
    {
      "id": "b2048186-6d7a-4b76-9849-bc694592ce39",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        60
      ],
      "parameters": {
        "width": 700,
        "height": 200,
        "content": "## \u2708\ufe0f Flight Fare Tracker & Alert System\n\nThis workflow is designed to monitor flight prices and send alerts when significant fare changes occur (drops or increases). It automates the process of fetching flight data, comparing current fares against historical records, and notifying users via email or Telegram if an alert condition is met. \ud83d\udcc9\ud83d\udcc8"
      },
      "typeVersion": 1
    },
    {
      "id": "06f7bb02-8151-43f9-b5c2-9a75555d0199",
      "name": "Data Ingestion & Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1400,
        560
      ],
      "parameters": {
        "color": 4,
        "width": 600,
        "height": 300,
        "content": "### \ud83d\udcca Data Ingestion & Processing\n\n1.  **Schedule Trigger**: Starts the workflow at regular intervals.\n2.  **Fetch Flight Data**: Retrieves flight information from the AviationStack API, filtering by JFK to LAX route.\n3.  **Process Flight Data**: A Function node that extracts and formats relevant flight details (flight number, airline, departure/arrival times, and a *mock* current fare). This prepares data for comparison.\n4.  **Google Sheets**: Reads existing flight fare data from a Google Sheet, which acts as a historical record."
      },
      "typeVersion": 1
    },
    {
      "id": "5b431dd1-1832-4a03-9df0-880dc17d1924",
      "name": "Fare Comparison & Alert Logic",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -700,
        20
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 280,
        "content": "### \ud83d\udd0d Fare Comparison & Alert Logic\n\n1.  **Code**: This critical node compares the `current_fare` from the \"Process Flight Data\" node with the `previous_fare` fetched from \"Google Sheets\". It calculates fare changes (absolute and percentage) and determines if an alert is needed based on predefined thresholds (e.g., >=10% drop or >=15% increase).\n2.  **Check if Alert Needed**: An If node that checks if the `fare_change` calculated in the Code node is non-zero. If there's a change, it proceeds to format and send alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "a29a641a-f204-463f-8764-91bfaf753f2d",
      "name": "Notification & Logging",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 600,
        "height": 280,
        "content": "### \ud83d\udd14 Notification & Logging\n\n1.  **Format Alert Message**: Prepares the alert message in different formats (email HTML, SMS) using a Function node, based on the `alert_type` (price drop/increase).\n2.  **Gmail**: Sends an email notification with the formatted alert.\n3.  **Telegram**: Sends a Telegram message with the formatted alert.\n4.  **Log Alert Activity**: A final Function node that logs details about the sent alert, useful for auditing or debugging."
      },
      "typeVersion": 1
    },
    {
      "id": "cf96cc07-5679-4ba4-86f7-5a2cfec7dae3",
      "name": "Previous flight data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -780,
        380
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q/edit?usp=drivesdk",
          "cachedResultName": "fare details change logs"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.6
    },
    {
      "id": "ba34df18-2c7c-46ee-9fc4-acfacb7b8fb1",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        180,
        300
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.formatted_messages.email.html }}",
        "options": {},
        "subject": "={{ $json.formatted_messages.email.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "93eb556a-c580-4639-9d7d-0b0baaa6cda4",
  "connections": {
    "Code": {
      "main": [
        [
          {
            "node": "Check if Alert Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram": {
      "main": [
        [
          {
            "node": "Log Alert Activity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Log Alert Activity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch Flight Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Flight Data": {
      "main": [
        [
          {
            "node": "Process Flight Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Flight Data": {
      "main": [
        [
          {
            "node": "Previous flight data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Alert Message": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          },
          {
            "node": "Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Previous flight data": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Alert Needed": {
      "main": [
        [
          {
            "node": "Format Alert Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}