{
  "id": "t7c5EWQNzBVrEayH",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Data quality checker",
  "tags": [],
  "nodes": [
    {
      "id": "07c0c9f5-4958-470b-8df6-9920a9a39190",
      "name": "Groq Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        1296,
        -64
      ],
      "parameters": {
        "model": "openai/gpt-oss-safeguard-20b",
        "options": {}
      },
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "80ec759f-3af8-41fb-99cd-7b5400daf1a9",
      "name": "Start Data Check",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        -224
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "6604b97e-6b47-46bb-ad0d-61230b7440c0",
      "name": "Get Pricing Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        208,
        -224
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit?usp=drivesdk",
          "cachedResultName": "Data quality checker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "2417d119-4ce6-4605-b033-6e962b86de14",
      "name": "Process Each Row",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        400,
        -208
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "2c77d75e-616f-4107-b1f1-97dfa33d0a52",
      "name": "Check Price Issues",
      "type": "n8n-nodes-base.code",
      "position": [
        608,
        -160
      ],
      "parameters": {
        "jsCode": "const price = Number($json.price);\nconst prev = Number($json.previous_price);\n\nlet anomaly = \"\";\nlet status = \"OK\"; // default\n\nif (!price) {\n  anomaly = \"Missing Value\";\n  status = \"FLAGGED\";\n} else if (prev && prev !== 0) {\n  const change = (price - prev) / prev;\n\n  if (change > 0.2) {\n    anomaly = \"Sudden Spike\";\n    status = \"FLAGGED\";\n  } else if (change < -0.2) {\n    anomaly = \"Sudden Drop\";\n    status = \"FLAGGED\";\n  }\n}\n\nreturn [{ ...$json, anomaly, status }];"
      },
      "typeVersion": 2
    },
    {
      "id": "462bf36c-14cd-4893-9b6b-228861bd9dd8",
      "name": "Is Issue Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        832,
        -240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d8b332bf-1a5e-484c-a126-14294de01208",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.anomaly }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b3f249b0-0448-43cd-a3fe-8dd4e3375f13",
      "name": "Generate Issue Reason",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1264,
        -256
      ],
      "parameters": {
        "text": "=Anomaly: {{$json.anomaly}}\nPrice:{{ $json.price }}\n\nGive short reason in 3-4 lines",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "e7805735-cce8-467e-b9fe-799c21289add",
      "name": "Prepare Flag Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1664,
        -192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "fd821e8a-fc38-48d9-a898-d80cfad36a36",
              "name": "reason ",
              "type": "string",
              "value": "={{ $json.output }}"
            },
            {
              "id": "b48c5a91-6149-477d-9145-7a135ee8e13a",
              "name": "status",
              "type": "string",
              "value": "={{ $('Check Price Issues').item.json.status }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "30053d26-5476-453b-a9ea-e8447420a287",
      "name": "Update Flagged Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1888,
        -96
      ],
      "parameters": {
        "columns": {
          "value": {
            "reason": "={{ $json['reason '] }}",
            "status": "={{ $json.status }}",
            "row_number": "={{ $('Check Price Issues').item.json.row_number }}"
          },
          "schema": [
            {
              "id": "price",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "previous_price",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "previous_price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reason",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "reason",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit?usp=drivesdk",
          "cachedResultName": "Data quality checker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "8b709dda-0a0e-46cb-a89d-8a81dfe88a50",
      "name": "Update Normal Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1040,
        -144
      ],
      "parameters": {
        "columns": {
          "value": {
            "reason": "Price change is within normal range. No anomaly detected.",
            "status": "={{ $json.status }}",
            "row_number": "={{ $json.row_number }}"
          },
          "schema": [
            {
              "id": "price",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "previous_price",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "previous_price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reason",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "reason",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1D6mxmcUrhNKZwD2bH_OKe3IZmqYOm8x9d6EAXs_kQBY/edit?usp=drivesdk",
          "cachedResultName": "Data quality checker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "2856c0b5-f249-4254-9c20-04f636f904e8",
      "name": "Send Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2080,
        -240
      ],
      "parameters": {
        "text": "= Anomaly Detected!  Price: {{ $('Get Pricing Data').item.json.price }} Previous Price: {{ $('Get Pricing Data').item.json.previous_price }}  Issue: {{ $('Check Price Issues').item.json.anomaly }}  Reason: {{$json.reason}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AQLRM87RS",
          "cachedResultName": "data-quality-checker"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "27c34aca-24ab-4518-9d68-7df7874e79c6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -432,
        -672
      ],
      "parameters": {
        "width": 352,
        "height": 944,
        "content": "# Data Quality Checker\n# How It Works\n\nThis workflow monitors pricing data from Google Sheets and detects anomalies automatically. It reads each row, checks for missing values or sudden price changes and marks them as \u201cFLAGGED\u201d if an issue is found. An AI model generates a short explanation for the anomaly. Flagged rows are updated in the sheet with status and reason and a Slack alert is sent. If no issue is detected, the row is marked \u201cOK\u201d with a normal status message.\n \n# Setup Steps\n## Prepare Google Sheet\nEnsure columns: price, previous_price, status, reason, row_number.\n\n## Connect Accounts in n8n\nAdd Google Sheets, Slack and Groq AI credentials.\n\n## Start Workflow\nUse trigger (manual/webhook).\n\n## Process Data\nCheck anomalies and generate AI reason.\n\n## Update & Alert\nUpdate sheet and send Slack alert for flagged rows."
      },
      "typeVersion": 1
    },
    {
      "id": "da9c0c7e-ece0-4e3a-9606-b8a554291e45",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 512,
        "content": "## Data Fetch & Validation\n\nThe workflow starts by triggering the process, fetches price data from the sheet, processes each row individually and checks for issues like missing values or sudden price changes. This ensures all data is ready for further anomaly detection."
      },
      "typeVersion": 1
    },
    {
      "id": "9cd0c525-6cc7-4db3-bc44-6a9fb3165a6d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 512,
        "content": "## Anomaly Handling & Updates\n\nThe workflow checks each price for anomalies using the \u201cIf\u201d node. Detected issues are sent to the AI node to generate a short reason and if the value is ok then the Google Sheet is updated with the status ok. "
      },
      "typeVersion": 1
    },
    {
      "id": "bff886f0-cd45-46bd-a881-db932e6963be",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1616,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 528,
        "content": "## Flagging & Alerts\n\nThis part of the workflow prepares flagged data with reasons, updates the corresponding rows in the Google Sheet and sends a Slack alert for any anomalies. It ensures all issues are recorded."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "435934de-b0e3-4d0b-ba4f-217c29186d9b",
  "connections": {
    "Groq Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Issue Reason",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Is Issue Found?": {
      "main": [
        [
          {
            "node": "Generate Issue Reason",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Normal Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Pricing Data": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Row": {
      "main": [
        [],
        [
          {
            "node": "Check Price Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Data Check": {
      "main": [
        [
          {
            "node": "Get Pricing Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Flag Data": {
      "main": [
        [
          {
            "node": "Update Flagged Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Normal Row": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Price Issues": {
      "main": [
        [
          {
            "node": "Is Issue Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Flagged Row": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Issue Reason": {
      "main": [
        [
          {
            "node": "Prepare Flag Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}