{
  "name": "Disaster NLP \u2014 Alert Routing Workflow",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "id": "node-schedule",
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "notes": "Triggers the workflow every 5 minutes. No Twitter API needed \u2014 we poll our own FastAPI server instead."
    },
    {
      "parameters": {
        "url": "http://localhost:8000/clusters",
        "options": {
          "timeout": 10000
        }
      },
      "id": "node-http",
      "name": "GET /clusters",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        460,
        300
      ],
      "notes": "Fetches the GeoJSON FeatureCollection of all active disaster event clusters from our FastAPI server."
    },
    {
      "parameters": {
        "jsCode": "// \u2500\u2500 Node 3: Parse GeoJSON and extract critical/high clusters \u2500\u2500\n// This JavaScript runs inside n8n.\n// 'items' is the response from the HTTP Request node above.\n\nconst geojson = items[0].json;\nconst features = geojson.features || [];\n\n// Filter for CRITICAL and HIGH severity clusters only\nconst alertClusters = features.filter(f => {\n  const sev = (f.properties.severity || '').toLowerCase();\n  return sev === 'critical' || sev === 'high';\n});\n\nif (alertClusters.length === 0) {\n  // Return a flag so the IF node can stop processing\n  return [{ json: { has_alerts: false, count: 0, clusters: [] } }];\n}\n\n// Build a summary for each alert cluster\nconst summaries = alertClusters.map(f => ({\n  cluster_id:    f.properties.cluster_id,\n  severity:      f.properties.severity,\n  tweet_count:   f.properties.tweet_count,\n  label:         f.properties.dominant_label,\n  lat:           f.geometry.coordinates[1],\n  lon:           f.geometry.coordinates[0],\n  hashtags:      (f.properties.top_hashtags || []).join(', '),\n  sample_tweet:  (f.properties.sample_tweets || [''])[0],\n}));\n\nreturn [{\n  json: {\n    has_alerts: true,\n    count:      summaries.length,\n    critical_count: alertClusters.filter(f => f.properties.severity === 'critical').length,\n    clusters:   summaries,\n  }\n}];"
      },
      "id": "node-parse",
      "name": "Parse & Filter Critical Clusters",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-1",
              "leftValue": "={{ $json.has_alerts }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-if",
      "name": "Any Critical Alerts?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "notes": "Only continues to alert nodes if there are critical/high severity clusters. Prevents spam when no disaster is happening."
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-1",
              "name": "alert_message",
              "value": "=\ud83d\udea8 DISASTER ALERT \u2014 {{ $json.count }} active event cluster(s) detected!\n\n\u26a0\ufe0f CRITICAL clusters: {{ $json.critical_count }}\n\nTop event:\n  Cluster #{{ $json.clusters[0].cluster_id }}\n  Severity: {{ $json.clusters[0].severity.toUpperCase() }}\n  Category: {{ $json.clusters[0].label }}\n  Tweets:   {{ $json.clusters[0].tweet_count }}\n  Location: {{ $json.clusters[0].lat }}, {{ $json.clusters[0].lon }}\n  Hashtags: {{ $json.clusters[0].hashtags }}\n  Sample:   {{ $json.clusters[0].sample_tweet }}\n\nGenerated at: {{ $now.toISO() }}\nDashboard: http://localhost:8080\nAPI Docs:  http://localhost:8000/docs",
              "type": "string"
            },
            {
              "id": "assign-2",
              "name": "alert_title",
              "value": "=\ud83d\udea8 Disaster Alert: {{ $json.count }} Active Cluster(s)",
              "type": "string"
            },
            {
              "id": "assign-3",
              "name": "total_clusters",
              "value": "={{ $json.count }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "node-format",
      "name": "Format Alert Message",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.3,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://webhook.site/YOUR-UNIQUE-ID-HERE",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "title",
              "value": "={{ $json.alert_title }}"
            },
            {
              "name": "message",
              "value": "={{ $json.alert_message }}"
            },
            {
              "name": "severity",
              "value": "critical"
            },
            {
              "name": "source",
              "value": "disaster_nlp_nit_trichy"
            },
            {
              "name": "timestamp",
              "value": "={{ $now.toISO() }}"
            }
          ]
        },
        "options": {
          "timeout": 10000
        }
      },
      "id": "node-webhook",
      "name": "Send to webhook.site",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1340,
        180
      ],
      "notes": "SETUP REQUIRED: Go to https://webhook.site (free, no account), copy your unique URL, paste it above replacing 'YOUR-UNIQUE-ID-HERE'. You will see the alert arrive in real time on that page."
    },
    {
      "parameters": {
        "webhookUri": "YOUR_SLACK_WEBHOOK_URL_HERE",
        "text": "={{ $json.alert_message }}",
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "id": "node-slack",
      "name": "Slack Notification (optional)",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        1340,
        340
      ],
      "disabled": true,
      "notes": "OPTIONAL: To enable Slack alerts:\n1. Go to https://api.slack.com/apps (free)\n2. Create a new app \u2192 'Incoming Webhooks'\n3. Activate and add to a channel\n4. Copy the webhook URL and paste above\n5. Enable this node (right-click \u2192 Enable)\n\nSlack incoming webhooks are completely free."
    },
    {
      "parameters": {
        "jsCode": "// No critical alerts \u2014 log and continue\nconsole.log('No critical/high severity clusters found at', new Date().toISOString());\nreturn [{ json: { status: 'no_alerts', checked_at: new Date().toISOString() } }];"
      },
      "id": "node-no-alert",
      "name": "Log: No Alerts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        380
      ],
      "notes": "When no critical events are found, we just log it. No notification is sent."
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "GET /clusters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET /clusters": {
      "main": [
        [
          {
            "node": "Parse & Filter Critical Clusters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Filter Critical Clusters": {
      "main": [
        [
          {
            "node": "Any Critical Alerts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any Critical Alerts?": {
      "main": [
        [
          {
            "node": "Format Alert Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log: No Alerts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Alert Message": {
      "main": [
        [
          {
            "node": "Send to webhook.site",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack Notification (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "staticData": null,
  "tags": [
    "disaster-nlp",
    "nit-trichy",
    "alert-routing"
  ],
  "triggerCount": 0,
  "updatedAt": "2024-01-15T00:00:00.000Z",
  "versionId": "1"
}