{
  "id": "lpWAQLOGAQF6OhbA",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Intelligent Data Ops Pipeline (ETL + AI Monitoring)",
  "tags": [
    {
      "id": "3uG7zkKJQ0LZ9hAf",
      "name": "pdf-intake",
      "createdAt": "2026-07-13T21:35:12.540Z",
      "updatedAt": "2026-07-13T21:35:12.540Z"
    },
    {
      "id": "GIjZiysRVWgZ5UnE",
      "name": "tax-verification",
      "createdAt": "2026-07-13T21:35:12.514Z",
      "updatedAt": "2026-07-13T21:35:12.514Z"
    },
    {
      "id": "PROuusbhiGmTHZcx",
      "name": "google-sheets",
      "createdAt": "2026-07-13T21:35:12.592Z",
      "updatedAt": "2026-07-13T21:35:12.592Z"
    },
    {
      "id": "V8Tg5cfQ08GamLPG",
      "name": "lovable",
      "createdAt": "2026-07-13T21:35:12.566Z",
      "updatedAt": "2026-07-13T21:35:12.566Z"
    },
    {
      "id": "rEPUTvNtc6oTOvzp",
      "name": "groq",
      "createdAt": "2026-07-13T21:35:12.485Z",
      "updatedAt": "2026-07-13T21:35:12.485Z"
    }
  ],
  "nodes": [
    {
      "id": "1764186e-72b8-4017-82a2-ffca838382a4",
      "name": "Hourly Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -4736,
        2960
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "af39ea3c-bbac-4c6e-9e83-747369b3a56c",
      "name": "Fetch Stripe Payments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -4528,
        2960
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 485725857,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE/edit#gid=485725857",
          "cachedResultName": "stripe_logs_sample.csv"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE/edit?usp=drivesdk",
          "cachedResultName": "stripe_logs_sample"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "73e5fd7c-675e-4326-8701-a1f2eb1dbea7",
      "name": "Clean & Normalize",
      "type": "n8n-nodes-base.code",
      "position": [
        -4304,
        2960
      ],
      "parameters": {
        "jsCode": "const rows = $input.all();\n\nreturn rows.map(row => {\n  const r = row.json;\n  return {\n    json: {\n      transaction_id: r.transaction_id,\n      customer_id:    r.customer_id,\n      customer_email: r.customer_email,\n      amount_usd:     parseFloat(r.amount) / 100,  // cents \u2192 dollars\n      currency:       r.currency,\n      status:         r.status,\n      payment_method: r.payment_method,\n      country:        r.country,\n      risk_score:     parseInt(r.risk_score),\n      created:        r.created,\n      description:    r.description,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "785db0fc-bf94-4fd7-af56-8ab7696affc8",
      "name": "Store Analytics Record",
      "type": "n8n-nodes-base.supabase",
      "onError": "continueErrorOutput",
      "position": [
        -4080,
        2960
      ],
      "parameters": {
        "tableId": "analytics_data",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "amount",
              "fieldValue": "={{ $input.item.json.amount_usd }}"
            },
            {
              "fieldId": "customer_id",
              "fieldValue": "={{ $input.item.json.customer_id }}"
            },
            {
              "fieldId": "created_at",
              "fieldValue": "={{ $json.created }}"
            },
            {
              "fieldId": "risk_score",
              "fieldValue": "={{ $json.risk_score }}"
            },
            {
              "fieldId": "country",
              "fieldValue": "={{ $json.country }}"
            },
            {
              "fieldId": "status",
              "fieldValue": "={{ $json.status }}"
            },
            {
              "fieldId": "transaction_id",
              "fieldValue": "={{ $json.transaction_id }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f75205bb-0ddb-4def-8b3e-4b5b4b88d97d",
      "name": "Log Error to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -3408,
        3440
      ],
      "parameters": {
        "columns": {
          "value": {
            "severity": "={{ ['Detect Anomalies (AI)', 'Log Incident'].includes($prevNode.name) ? 'high' : 'medium' }}",
            "node_name": "={{ $prevNode.name }}",
            "timestamp": "={{ new Date().toISOString() }}",
            "error_message": "={{ \n  $json['error']?.includes('duplicate') || $json['message']?.includes('duplicate') \n  ? 'Duplicate ignored \u2014 transaction already exists' \n  : $json['error'] ?? $json['message'] ?? 'Unknown error' \n}}",
            "transaction_id": "={{ $('Clean & Normalize').first()?.json?.transaction_id ?? $input.first()?.json?.transaction_id ?? 'N/A' }}"
          },
          "schema": [
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "node_name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "node_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "error_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "transaction_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "transaction_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "severity",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "severity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 673306969,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE/edit#gid=673306969",
          "cachedResultName": "error_logs"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1BLAZCUAcbZb9zHu0QO5Bo4vDa0UmqMcoTektmYu7jsE/edit?usp=drivesdk",
          "cachedResultName": "stripe_logs_sample"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "32fe11c0-4750-4dc7-b8d3-2d3f7c57e3f5",
      "name": "Build AI Analysis Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        -3840,
        2960
      ],
      "parameters": {
        "jsCode": "const rows = $('Store Analytics Record').all().map(i => i.json);\n\nconst prompt = `Analyze these transactions for anomalies:\n${JSON.stringify(rows, null, 2)}\n\nReturn only this JSON structure, no extra text:\n{\n  \"anomaly\": true,\n  \"type\": \"spike | drop | fraud_pattern | none\",\n  \"severity\": \"low | medium | high | none\",\n  \"explanation\": \"one sentence summary\"\n}`;\n\nreturn [{\n  json: {\n    model: \"llama-3.3-70b-versatile\",\n    temperature: 0,\n    max_tokens: 500,\n    messages: [\n      {\n        role: \"system\",\n        content: \"You are a data analyst. Respond only with valid JSON, no extra text, no markdown.\"\n      },\n      {\n        role: \"user\",\n        content: prompt\n      }\n    ]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "074c8599-f79b-49c6-a614-c4ae392e72d8",
      "name": "Detect Anomalies (AI)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -3616,
        2960
      ],
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "groqApi"
      },
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "e811e167-a4cd-4d7b-b03e-feedf4575549",
      "name": "Parse AI Result",
      "type": "n8n-nodes-base.code",
      "position": [
        -3408,
        2960
      ],
      "parameters": {
        "jsCode": "const res = $input.item.json ?? {};\n\n// Groq (OpenAI-compatible) returns the text under choices[0].message.content.\nconst raw = res?.choices?.[0]?.message?.content;\n\nif (typeof raw !== 'string' || raw.trim() === '') {\n  // No usable AI content \u2014 emit a safe \"no anomaly\" record instead of crashing.\n  return [{\n    json: {\n      anomaly: false,\n      type: 'none',\n      severity: 'none',\n      explanation: 'No analysis returned by the AI model.'\n    }\n  }];\n}\n\n// Strip markdown fences if Groq adds them despite instructions.\nconst cleaned = raw.replace(/```json|```/g, '').trim();\n\nlet parsed;\ntry {\n  parsed = JSON.parse(cleaned);\n} catch (e) {\n  parsed = {\n    anomaly: false,\n    type: 'none',\n    severity: 'none',\n    explanation: 'Could not parse AI response as JSON: ' + cleaned.slice(0, 200)\n  };\n}\n\nreturn [{ json: parsed }];"
      },
      "typeVersion": 2
    },
    {
      "id": "3472b891-abcb-4c0b-85ee-e8fe1b438702",
      "name": "Anomaly Detected?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3184,
        2960
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "86edbfe4-0f6d-405a-ba66-43151ac58381",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.anomaly }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "91401d77-3f5d-4bcb-aa7e-42b4d7c06a05",
      "name": "Log Incident",
      "type": "n8n-nodes-base.supabase",
      "onError": "continueErrorOutput",
      "position": [
        -2944,
        2960
      ],
      "parameters": {
        "tableId": "incident_logs",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "anomaly_type",
              "fieldValue": "={{ $('Parse AI Result').item.json.type }}"
            },
            {
              "fieldId": "record_id",
              "fieldValue": "={{ $('Store Analytics Record').item.json.id }}"
            },
            {
              "fieldId": "ai_label",
              "fieldValue": "={{ $('Parse AI Result').item.json.explanation }}"
            },
            {
              "fieldId": "severity",
              "fieldValue": "={{ $json.severity }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a399c6f6-9abb-48d5-a266-54ab739e0a7c",
      "name": "Send Slack Alert",
      "type": "n8n-nodes-base.slack",
      "onError": "continueErrorOutput",
      "position": [
        -2720,
        2960
      ],
      "parameters": {
        "text": "={{ '\ud83d\udea8 Anomaly detected: ' + $('Parse AI Result').item.json.type + ' | Severity: ' + $('Parse AI Result').item.json.severity + '\\n' + $('Parse AI Result').item.json.explanation }} ",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B0VR1LN3B",
          "cachedResultName": "anomally-alerts"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "f4616600-c298-4ee9-8c41-21dbe8af1515",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4800,
        2384
      ],
      "parameters": {
        "color": 4,
        "width": 2200,
        "height": 320,
        "content": "## \ud83e\udde0 Intelligent Data Ops Pipeline (ETL + AI Monitoring)\n\nAn hourly ETL pipeline that ingests Stripe payment data, normalizes it, stores it in Supabase, and uses an LLM (Groq) to detect anomalies. Detected anomalies are logged as incidents and pushed to Slack. Every step has error handling that logs failures to a Google Sheet.\n\n**Setup required:** (1) Google Sheets \u2014 source `stripe_logs_sample` sheet + `error_logs` tab \u00b7 (2) Supabase \u2014 tables `analytics_data` and `incident_logs` \u00b7 (3) Groq API credential \u00b7 (4) Slack \u2014 channel for anomaly alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "0851d916-78dc-43dc-8075-fa52dd789c1b",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4800,
        2752
      ],
      "parameters": {
        "color": 5,
        "width": 840,
        "height": 460,
        "content": "## 1 \u00b7 Extract, Clean & Load\n\nRuns hourly. Pulls raw Stripe rows from Google Sheets, normalizes fields (cents \u2192 USD, typed values), then writes each record to the Supabase `analytics_data` table."
      },
      "typeVersion": 1
    },
    {
      "id": "d8d9ab65-0136-4d5a-a4bf-24f106291eb7",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3920,
        2752
      ],
      "parameters": {
        "color": 6,
        "width": 860,
        "height": 460,
        "content": "## 2 \u00b7 AI Anomaly Detection\n\nBuilds an analysis prompt from the stored records, sends it to Groq (llama-3.3-70b), parses the JSON response, and branches on whether an anomaly was found."
      },
      "typeVersion": 1
    },
    {
      "id": "66c9ac21-8c58-43ef-a029-38dc3cf1b3c3",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3024,
        2752
      ],
      "parameters": {
        "color": 3,
        "width": 420,
        "height": 460,
        "content": "## 3 \u00b7 Log Incident & Alert\n\nWhen an anomaly is detected, the incident is written to Supabase `incident_logs` and a formatted alert is sent to the Slack channel."
      },
      "typeVersion": 1
    },
    {
      "id": "3eed6a43-d307-4714-b4dd-695016e11849",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3600,
        3280
      ],
      "parameters": {
        "color": 2,
        "width": 520,
        "height": 300,
        "content": "## 4 \u00b7 Error Handling\n\nEvery critical node routes failures here via its error output. Errors (including deduped duplicates) are appended to the `error_logs` sheet with node name, timestamp and severity."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "f987190e-78ce-4acd-a325-02db7e85b900",
  "nodeGroups": [],
  "connections": {
    "Log Incident": {
      "main": [
        [
          {
            "node": "Send Slack Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Error to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hourly Schedule": {
      "main": [
        [
          {
            "node": "Fetch Stripe Payments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Result": {
      "main": [
        [
          {
            "node": "Anomaly Detected?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Alert": {
      "main": [
        [],
        [
          {
            "node": "Log Error to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anomaly Detected?": {
      "main": [
        [
          {
            "node": "Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean & Normalize": {
      "main": [
        [
          {
            "node": "Store Analytics Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Anomalies (AI)": {
      "main": [
        [
          {
            "node": "Parse AI Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Error to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Stripe Payments": {
      "main": [
        [
          {
            "node": "Clean & Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Analytics Record": {
      "main": [
        [
          {
            "node": "Build AI Analysis Prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Error to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build AI Analysis Prompt": {
      "main": [
        [
          {
            "node": "Detect Anomalies (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}