[
  {
    "id": "flow-predmaint",
    "type": "tab",
    "label": "Predictive Maintenance — bearing temp",
    "disabled": false,
    "info": "Reads bearing temperature + shaft speed off Modbus TCP, buffers 30-minute windows, fits a symbolic model once, then drift-monitors on every subsequent window. When drift > 2σ, posts a Slack alert with the current vs baseline equation."
  },
  {
    "id": "modbus-poll",
    "type": "inject",
    "z": "flow-predmaint",
    "name": "poll every 10s (stub for Modbus TCP)",
    "props": [{ "p": "payload" }],
    "repeat": "10",
    "crontab": "",
    "once": false,
    "topic": "",
    "payload": "{\"temp_c\": 62.4, \"speed_rpm\": 1780}",
    "payloadType": "json",
    "x": 190,
    "y": 100,
    "wires": [["window-buffer"]]
  },
  {
    "id": "window-buffer",
    "type": "function",
    "z": "flow-predmaint",
    "name": "buffer 30-min window",
    "func": "// Accumulate up to 180 samples (30 min @ 10 s) then flush.\nlet buf = context.get('buf') || [];\nbuf.push([msg.payload.temp_c, msg.payload.speed_rpm]);\nif (buf.length < 180) {\n    context.set('buf', buf);\n    return null;\n}\nconst X = buf.map(r => [r[1]]);          // speed\nconst y = buf.map(r => r[0]);            // temp\ncontext.set('buf', []);\nmsg.payload = { X, y, variables: ['speed_rpm'] };\nreturn msg;\n",
    "outputs": 1,
    "x": 420,
    "y": 100,
    "wires": [["route-fit-or-drift"]]
  },
  {
    "id": "route-fit-or-drift",
    "type": "switch",
    "z": "flow-predmaint",
    "name": "have baseline?",
    "property": "baseline_id",
    "propertyType": "flow",
    "rules": [
      { "t": "null" },
      { "t": "nnull" }
    ],
    "checkall": "false",
    "outputs": 2,
    "x": 640,
    "y": 100,
    "wires": [["fit"], ["attach-baseline"]]
  },
  {
    "id": "fit",
    "type": "formulize-fit",
    "z": "flow-predmaint",
    "name": "baseline fit",
    "endpoint": "https://api.formulize.dev",
    "rateLimit": "free",
    "timeoutMs": 60000,
    "x": 830,
    "y": 60,
    "wires": [["stash-baseline"]]
  },
  {
    "id": "stash-baseline",
    "type": "function",
    "z": "flow-predmaint",
    "name": "flow.baseline_id = payload.formula_id",
    "func": "flow.set('baseline_id', msg.payload.formula_id);\nreturn msg;\n",
    "outputs": 1,
    "x": 1080,
    "y": 60,
    "wires": [[]]
  },
  {
    "id": "attach-baseline",
    "type": "function",
    "z": "flow-predmaint",
    "name": "shape drift input",
    "func": "msg.payload = {\n  formula_id: flow.get('baseline_id'),\n  current_X: msg.payload.X,\n  current_y: msg.payload.y,\n  sigma_threshold: 2.0\n};\nreturn msg;\n",
    "outputs": 1,
    "x": 830,
    "y": 140,
    "wires": [["drift"]]
  },
  {
    "id": "drift",
    "type": "formulize-drift",
    "z": "flow-predmaint",
    "name": "coefficient drift",
    "endpoint": "https://api.formulize.dev",
    "rateLimit": "free",
    "timeoutMs": 60000,
    "sigmaThreshold": 2.0,
    "x": 1030,
    "y": 140,
    "wires": [["only-if-drift"]]
  },
  {
    "id": "only-if-drift",
    "type": "switch",
    "z": "flow-predmaint",
    "name": "drift_detected?",
    "property": "payload.drift_detected",
    "propertyType": "msg",
    "rules": [{ "t": "true" }],
    "checkall": "false",
    "outputs": 1,
    "x": 1230,
    "y": 140,
    "wires": [["format-slack"]]
  },
  {
    "id": "format-slack",
    "type": "function",
    "z": "flow-predmaint",
    "name": "format Slack message",
    "func": "const p = msg.payload;\nmsg.payload = {\n  text: `:warning: Bearing drift ${p.delta_sigmas.toFixed(2)}σ on coefficient '${p.drifted_coefficient}'.\\n` +\n        `Baseline: \\`${JSON.stringify(p.baseline_coefficients)}\\`\\n` +\n        `Current:  \\`${JSON.stringify(p.current_coefficients)}\\`\\n` +\n        `Current expression: \\`${p.current_expression}\\``\n};\nreturn msg;\n",
    "outputs": 1,
    "x": 1420,
    "y": 140,
    "wires": [["slack-webhook"]]
  },
  {
    "id": "slack-webhook",
    "type": "http request",
    "z": "flow-predmaint",
    "name": "Slack webhook",
    "method": "POST",
    "ret": "txt",
    "url": "https://hooks.slack.com/services/REPLACE/ME/WITH_YOUR_WEBHOOK",
    "x": 1620,
    "y": 140,
    "wires": [[]]
  }
]
