{
  "name": "Anomaly-to-ticket pipeline",
  "nodes": [
    {
      "parameters": {
        "topics": "units/+/readings",
        "options": {
          "jsonParseBody": true
        }
      },
      "id": "9f9e8b38-cdf2-4a12-a708-12a3d221aa66",
      "name": "Sensor readings",
      "type": "n8n-nodes-base.mqttTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "credentials": {
        "mqtt": {
          "id": "REPLACE_ME",
          "name": "MQTT broker"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const store = $getWorkflowStaticData('global');\nconst r = $json.message;\nconst id = r.unit_id;\nconst w = (store[id] = store[id] || []);\nconst mean = w.length ? w.reduce((a, b) => a + b, 0) / w.length : r.value;\nconst sd = Math.sqrt(w.reduce((a, b) => a + (b - mean) ** 2, 0) / Math.max(w.length, 1)) || 1;\nconst z = (r.value - mean) / sd;\nw.push(r.value); if (w.length > 200) w.shift();\nreturn [{ json: { unit: id, metric: r.metric, value: r.value, z, recent: w.slice(-12), anomaly: w.length > 30 && Math.abs(z) >= 3.5 } }];"
      },
      "id": "f34b06c4-d977-4f29-adb6-db7f5bb920e9",
      "name": "Score against history",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "a1",
              "leftValue": "={{ $json.anomaly }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "46ef2b3c-c619-4703-aaba-21dfc146a52c",
      "name": "Anomalous?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        784,
        300
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Unit {{ $json.unit }} reported {{ $json.metric }} = {{ $json.value }} (z-score {{ $json.z.toFixed(1) }}). Recent readings: {{ $json.recent.join(\", \") }}. In two sentences, state what changed and the most likely field checks."
      },
      "id": "fddbcd71-d7cb-45b3-a346-98540eb4e2aa",
      "name": "Write incident summary",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        1056,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "model": "llama3.1:8b",
        "options": {
          "temperature": 0.2
        }
      },
      "id": "d7531bca-4083-4034-ac9e-dff7688ac276",
      "name": "Local chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        1056,
        534
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "s1",
                    "leftValue": "={{ Math.abs($(\"Score against history\").item.json.z) }}",
                    "rightValue": 6,
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "critical"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "s2",
                    "leftValue": "={{ Math.abs($(\"Score against history\").item.json.z) }}",
                    "rightValue": 6,
                    "operator": {
                      "type": "number",
                      "operation": "lt"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "warning"
            }
          ]
        },
        "options": {}
      },
      "id": "28e95ede-730b-4890-a7fb-2b4d96d7b9d8",
      "name": "By severity",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1328,
        300
      ]
    },
    {
      "parameters": {
        "resource": "issue",
        "operation": "create",
        "project": {
          "__rl": true,
          "value": "OPS",
          "mode": "id"
        },
        "issueType": {
          "__rl": true,
          "value": "Incident",
          "mode": "id"
        },
        "summary": "=Critical anomaly on {{ $(\"Score against history\").item.json.unit }}",
        "additionalFields": {
          "priority": {
            "__rl": true,
            "value": "Highest",
            "mode": "name"
          },
          "description": "={{ $json.text || \"Summary unavailable. Raw reading: \" + JSON.stringify($(\"Score against history\").item.json) }}"
        }
      },
      "id": "a1528058-d4d8-4966-a4aa-63e05930d656",
      "name": "Critical ticket",
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1,
      "position": [
        1600,
        212
      ],
      "credentials": {
        "jiraSoftwareCloudApi": {
          "id": "REPLACE_ME",
          "name": "Jira"
        }
      }
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "field-ops",
          "mode": "name"
        },
        "text": "=Critical anomaly on {{ $(\"Score against history\").item.json.unit }}. Ticket: {{ $json.key }}",
        "otherOptions": {}
      },
      "id": "692dfe9a-ec6c-4787-af62-492f53ab7919",
      "name": "Page the team",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1872,
        212
      ],
      "credentials": {
        "slackApi": {
          "id": "REPLACE_ME",
          "name": "Slack"
        }
      }
    },
    {
      "parameters": {
        "resource": "issue",
        "operation": "create",
        "project": {
          "__rl": true,
          "value": "OPS",
          "mode": "id"
        },
        "issueType": {
          "__rl": true,
          "value": "Task",
          "mode": "id"
        },
        "summary": "=Anomaly on {{ $(\"Score against history\").item.json.unit }}",
        "additionalFields": {
          "description": "={{ $json.text || \"Summary unavailable. Raw reading: \" + JSON.stringify($(\"Score against history\").item.json) }}"
        }
      },
      "id": "3e47d468-3827-4f13-a025-f6f118877295",
      "name": "Warning ticket",
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1,
      "position": [
        1600,
        431
      ],
      "credentials": {
        "jiraSoftwareCloudApi": {
          "id": "REPLACE_ME",
          "name": "Jira"
        }
      }
    },
    {
      "parameters": {},
      "id": "8ed7436c-9dd6-45be-addb-49c010ebd9eb",
      "name": "Failed run",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        240,
        767
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "automation-alerts",
          "mode": "name"
        },
        "text": "=Workflow failed: {{ $json.workflow.name }} at {{ $json.execution.lastNodeExecuted }}",
        "otherOptions": {}
      },
      "id": "a22c19a4-560f-454e-ae6d-051301535a98",
      "name": "Report failure",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        512,
        767
      ],
      "credentials": {
        "slackApi": {
          "id": "REPLACE_ME",
          "name": "Slack"
        }
      }
    }
  ],
  "connections": {
    "Sensor readings": {
      "main": [
        [
          {
            "node": "Score against history",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score against history": {
      "main": [
        [
          {
            "node": "Anomalous?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anomalous?": {
      "main": [
        [
          {
            "node": "Write incident summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write incident summary": {
      "main": [
        [
          {
            "node": "By severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Local chat model": {
      "ai_languageModel": [
        [
          {
            "node": "Write incident summary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "By severity": {
      "main": [
        [
          {
            "node": "Critical ticket",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Warning ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Critical ticket": {
      "main": [
        [
          {
            "node": "Page the team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Failed run": {
      "main": [
        [
          {
            "node": "Report failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "A sensor misbehaves, the right person gets a ticket that already explains why."
  },
  "tags": []
}