[
  {
    "id": "flow-hvac",
    "type": "tab",
    "label": "HVAC — COP drift",
    "disabled": false,
    "info": "Fits a symbolic model for coefficient-of-performance COP = f(T_supply, T_return, T_outdoor, kW) once on a clean 24-hour window. Then hourly, refits the same template on the last hour of BACnet/MQTT telemetry and alarms when any coefficient drifts > 2σ — a leading indicator of coil fouling, refrigerant loss, or fan wear."
  },
  {
    "id": "hvac-poll",
    "type": "mqtt in",
    "z": "flow-hvac",
    "name": "MQTT hvac/telemetry",
    "topic": "hvac/telemetry",
    "qos": "0",
    "datatype": "json",
    "broker": "mqtt-broker",
    "x": 180,
    "y": 100,
    "wires": [["hvac-buffer"]]
  },
  {
    "id": "hvac-buffer",
    "type": "function",
    "z": "flow-hvac",
    "name": "buffer 1-hour window",
    "func": "// Payload shape: {t_supply, t_return, t_outdoor, kw, cop}\nlet buf = context.get('buf') || [];\nbuf.push(msg.payload);\nif (buf.length < 60) {\n    context.set('buf', buf);\n    return null;\n}\ncontext.set('buf', []);\nconst X = buf.map(r => [r.t_supply, r.t_return, r.t_outdoor, r.kw]);\nconst y = buf.map(r => r.cop);\nmsg.payload = {\n  X, y,\n  variables: ['t_supply', 't_return', 't_outdoor', 'kw']\n};\nreturn msg;\n",
    "outputs": 1,
    "x": 410,
    "y": 100,
    "wires": [["hvac-route"]]
  },
  {
    "id": "hvac-route",
    "type": "switch",
    "z": "flow-hvac",
    "name": "have baseline?",
    "property": "baseline_id",
    "propertyType": "flow",
    "rules": [{ "t": "null" }, { "t": "nnull" }],
    "checkall": "false",
    "outputs": 2,
    "x": 620,
    "y": 100,
    "wires": [["hvac-fit"], ["hvac-shape-drift"]]
  },
  {
    "id": "hvac-fit",
    "type": "formulize-fit",
    "z": "flow-hvac",
    "name": "baseline COP fit",
    "endpoint": "https://api.formulize.dev",
    "rateLimit": "free",
    "timeoutMs": 60000,
    "x": 820,
    "y": 60,
    "wires": [["hvac-stash"]]
  },
  {
    "id": "hvac-stash",
    "type": "function",
    "z": "flow-hvac",
    "name": "stash baseline_id",
    "func": "flow.set('baseline_id', msg.payload.formula_id);\nnode.warn('HVAC baseline formula: ' + msg.payload.expression);\nreturn msg;\n",
    "outputs": 1,
    "x": 1030,
    "y": 60,
    "wires": [[]]
  },
  {
    "id": "hvac-shape-drift",
    "type": "function",
    "z": "flow-hvac",
    "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": 820,
    "y": 140,
    "wires": [["hvac-drift"]]
  },
  {
    "id": "hvac-drift",
    "type": "formulize-drift",
    "z": "flow-hvac",
    "name": "COP drift monitor",
    "endpoint": "https://api.formulize.dev",
    "rateLimit": "free",
    "timeoutMs": 60000,
    "sigmaThreshold": 2.0,
    "x": 1020,
    "y": 140,
    "wires": [["hvac-alarm"]]
  },
  {
    "id": "hvac-alarm",
    "type": "switch",
    "z": "flow-hvac",
    "name": "alarm?",
    "property": "payload.drift_detected",
    "propertyType": "msg",
    "rules": [{ "t": "true" }],
    "checkall": "false",
    "outputs": 1,
    "x": 1220,
    "y": 140,
    "wires": [["hvac-email"]]
  },
  {
    "id": "hvac-email",
    "type": "e-mail",
    "z": "flow-hvac",
    "name": "email facilities team",
    "server": "smtp.example.com",
    "port": "465",
    "secure": true,
    "to": "facilities@example.com",
    "x": 1420,
    "y": 140,
    "wires": []
  },
  {
    "id": "mqtt-broker",
    "type": "mqtt-broker",
    "name": "local broker",
    "broker": "localhost",
    "port": "1883",
    "clientid": "",
    "usetls": false
  }
]
