{
  "id": "3nzbPNbVwu4M4F4g",
  "meta": {
    "instanceId": "b7b2db9d6a461f902b25d685e4550cab3c88fcbb3254a8654e06246b67e4ff5c",
    "templateCredsSetupCompleted": true
  },
  "name": "Product Health Monitoring & Incident Management system final",
  "tags": [
    {
      "id": "6YEf66QgUoe2H0xV",
      "name": "postgresql",
      "createdAt": "2025-11-20T18:05:52.192Z",
      "updatedAt": "2025-11-20T18:05:52.192Z"
    },
    {
      "id": "S94PaeZJiPKn5Ei9",
      "name": "gmail",
      "createdAt": "2025-08-14T09:57:18.678Z",
      "updatedAt": "2025-08-14T09:57:18.678Z"
    },
    {
      "id": "SenQnwINjVwewgLY",
      "name": "slack",
      "createdAt": "2025-11-20T18:05:28.955Z",
      "updatedAt": "2025-11-20T18:05:28.955Z"
    },
    {
      "id": "T75wN7b2Y1Q2X3nH",
      "name": "alert",
      "createdAt": "2025-11-20T18:06:09.140Z",
      "updatedAt": "2025-11-20T18:06:09.140Z"
    },
    {
      "id": "Unbn35P90TtJuAQC",
      "name": "email",
      "createdAt": "2025-08-14T09:57:13.394Z",
      "updatedAt": "2025-08-14T09:57:13.394Z"
    },
    {
      "id": "bX1tZbypCr5HBJMz",
      "name": "product management",
      "createdAt": "2025-11-20T18:06:00.432Z",
      "updatedAt": "2025-11-20T18:06:00.432Z"
    },
    {
      "id": "erKAjTLp0jWx08QX",
      "name": "notions",
      "createdAt": "2025-11-16T23:07:16.962Z",
      "updatedAt": "2025-11-16T23:07:16.962Z"
    },
    {
      "id": "qCgJLisgQ1EsyJ62",
      "name": "sql",
      "createdAt": "2025-11-20T18:05:47.931Z",
      "updatedAt": "2025-11-20T18:05:47.931Z"
    }
  ],
  "nodes": [
    {
      "id": "b26501bd-bb30-4601-87bd-b91fe451e5ca",
      "name": "log incident",
      "type": "n8n-nodes-base.postgres",
      "position": [
        624,
        0
      ],
      "parameters": {
        "query": "INSERT INTO incidents (\n  metric_name,\n  dimension,\n  dimension_value,\n  date_start,\n  baseline_value,\n  actual_value,\n  delta_pct,\n  severity,\n  raw_context\n)\nVALUES (\n  '{{$json.metric_name}}',\n  'global',\n  'all',\n  '{{$json.date}}',\n  {{$json.baseline_value}},\n  {{$json.actual_value}},\n  {{$json.delta_pct}},\n  '{{$json.severity}}',\n  '{{ JSON.stringify($json) }}'::jsonb\n)\nRETURNING incident_id;\n",
        "options": {
          "queryReplacement": "={{ $json.metric_name }}, {{$json.date}}, {{$json.baseline_value}}, {{$json.actual_value}}, {{$json.delta_pct}}, {{$json.severity}}, {{ JSON.stringify($json) }}"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "761d5140-0439-4487-8246-f68109d3ca51",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        -720
      ],
      "parameters": {
        "width": 496,
        "height": 1264,
        "content": "## How it works\nThis workflow monitors product health by automatically scanning key revenue and usage metrics, detecting anomalies, and alerting the team only when something looks wrong.\n\nEvery day, the system queries your Postgres (Supabase) database for recent product metrics (e.g. churn MRR, feature usage). A JavaScript step applies a simple statistical model (baseline window + z-score / delta %) to decide whether the latest value is abnormal compared to the last few days.\n\nIf no anomaly is detected, the workflow stops silently and nothing is sent — no noise, no false alerts.\n\nIf an anomaly is detected, the workflow creates a structured incident in the incidents table (with metric name, baseline, actual value, delta %, severity and raw JSON context). It then sends a Slack alert and an email with all the key details so the product team can react quickly. Optionally, the incident can also be mirrored into a Notion database to keep a lightweight incident log for product documentation or postmortems.\n\nAdditional workflows can be plugged in on top of this foundation (e.g. root cause analysis, daily incident recap) by simply reading from the same incidents table.\n\n## Setup steps:\n\n- Configure your Postgres (Supabase) credentials in n8n and run the provided SQL script to create the core tables (accounts, revenue_events, product_usage_events, incidents, system_logs) and sample data.\n\n- In the Postgres nodes, point the connection to your Supabase database and verify that a simple SELECT 1; query works.\n\n- Add your Slack credentials (or bot token) in n8n and set the target channel for alerts (e.g. #product-health-alerts).\n\n- Add your email credentials (Gmail or SMTP) and set the “From” and “To” addresses for incident alerts.\n\n- (Optional) Add your Notion integration token and database ID if you want each incident to also be logged as a Notion page.\n\n- Configure your AI provider API key (OpenAI or compatible) if you plan to add an automated “root cause summary” workflow on top of this system.\n\n- Adjust the cron triggers for the health checks (e.g. once per day in the morning) according to your monitoring needs.\n\n- Run a test by forcing an anomaly (or inserting a fake incident) and confirm that:\n\n- a row is created in the incidents table,\n\n- the Slack alert is posted,\n\n- the incident email is received, \n\n- and the optional Notion entry is created if configured."
      },
      "typeVersion": 1
    },
    {
      "id": "e6b2f6e1-8fdd-408a-a6c4-cff35d6446fd",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 1504,
        "height": 320,
        "content": "## 1) Daily revenue health\nChecks for MRR anomalies. Logs incident + notifies Product Team."
      },
      "typeVersion": 1
    },
    {
      "id": "6d5d041c-5253-4489-9078-8e5ef64654ec",
      "name": "daily usage metrics",
      "type": "n8n-nodes-base.postgres",
      "position": [
        208,
        320
      ],
      "parameters": {
        "query": "SELECT\n  date,\n  active_accounts,\n  accounts_viewing_dashboard,\n  accounts_using_alerts\nFROM daily_usage_metrics\nWHERE date >= current_date - INTERVAL '30 days'\nORDER BY date;\n",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "bba1ffc6-b558-4bec-a68d-5238c492b8b2",
      "name": "anomalies",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        320
      ],
      "parameters": {
        "jsCode": "const rows = items.map(item => item.json);\nif (rows.length < 8) return [];\n\nrows.sort((a, b) => new Date(a.date) - new Date(b.date));\n\nconst values = rows.map(r => parseFloat(r.accounts_using_alerts || 0));\nconst lastIndex = rows.length - 1;\nconst lastRow = rows[lastIndex];\nconst lastValue = values[lastIndex];\n\nconst window = 7;\nconst baselineValues = values.slice(lastIndex - window, lastIndex);\n\nconst mean = baselineValues.reduce((s, v) => s + v, 0) / baselineValues.length;\nconst variance = baselineValues\n  .map(v => Math.pow(v - mean, 2))\n  .reduce((s, v) => s + v, 0) / baselineValues.length;\nconst std = Math.sqrt(variance);\n\nif (std === 0) return [];\n\nconst z = (lastValue - mean) / std;\nconst deltaPct = mean === 0 ? null : ((lastValue - mean) / mean) * 100;\n\n// Ici on veut détecter UNE BAISSE (usage qui s'effondre)\nif (z < -2) {\n  const anomaly = {\n    date: lastRow.date,\n    metric_name: 'accounts_using_alerts',\n    baseline_value: mean,\n    actual_value: lastValue,\n    z_score: z,\n    delta_pct: deltaPct,\n    severity:\n      deltaPct !== null && deltaPct < -50 ? 'high'\n      : deltaPct !== null && deltaPct < -25 ? 'medium'\n      : 'low',\n  };\n  return [{ json: anomaly }];\n}\n\nreturn [];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9ff740fb-45fc-428b-9695-6d88d3f9ee60",
      "name": "insert incidents",
      "type": "n8n-nodes-base.postgres",
      "position": [
        624,
        320
      ],
      "parameters": {
        "query": "INSERT INTO incidents (\n  metric_name,\n  dimension,\n  dimension_value,\n  date_start,\n  date_end,\n  baseline_value,\n  actual_value,\n  delta_pct,\n  severity,\n  raw_context\n)\nVALUES (\n  $1, NULL, NULL, $2, $2, $3, $4, $5, $6, $7\n)\nRETURNING incident_id;\n",
        "options": {
          "queryReplacement": "={{ $json.metric_name }}, {{ $json.date }}, {{ $json.baseline_value }}, {{ $json.actual_value }}, {{ $json.delta_pct }}, {{ $json.severity }}, {{ JSON.stringify($json) }}"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "c1dd8022-c920-4be4-94b9-60d8145c2a92",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 1504,
        "height": 320,
        "content": "## 2) Daily usage health\nMonitors feature adoption. Detects unusual drops in usage patterns."
      },
      "typeVersion": 1
    },
    {
      "id": "68871ce8-020e-4afc-95c3-f7822f4c6ecb",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 2160,
        "height": 320,
        "content": "##  3) Root Cause & Summary\nEnriches incidents with country/plan breakdown + AI-generated hypothesis."
      },
      "typeVersion": 1
    },
    {
      "id": "70091a50-cdf2-42d4-bc03-4d4381ee2b6b",
      "name": "select open incident",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1680,
        0
      ],
      "parameters": {
        "query": "SELECT *\nFROM incidents\nWHERE status = 'open'\nORDER BY detected_at\nLIMIT 1;\n",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6,
      "alwaysOutputData": true
    },
    {
      "id": "fb59e52b-1a98-48ce-92f3-2cc0f3d42569",
      "name": "revenue by country",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2176,
        -112
      ],
      "parameters": {
        "query": "SELECT a.country, SUM(r.mrr_delta) AS churn_mrr\nFROM revenue_events r\nJOIN accounts a ON a.account_id = r.account_id\nWHERE r.event_type = 'churn'\n  AND r.event_date BETWEEN $1::date - INTERVAL '3 days'\n                      AND $1::date\nGROUP BY a.country\nORDER BY churn_mrr DESC;\n",
        "options": {
          "queryReplacement": "=\"queryReplacement\": \"$1 → {{ $json.date_start }}\""
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "24289482-8a7a-4244-ad54-12b902dabb10",
      "name": "revenue by plan",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2176,
        32
      ],
      "parameters": {
        "query": "SELECT a.plan, SUM(r.mrr_delta) AS churn_mrr\nFROM revenue_events r\nJOIN accounts a ON a.account_id = r.account_id\nWHERE r.event_type = 'churn'\n  AND r.event_date BETWEEN $1::date - INTERVAL '3 days'\n                      AND $1::date\nGROUP BY a.plan\nORDER BY churn_mrr DESC;\n",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "8db7fbff-dfa2-4218-b55c-8abadf147f11",
      "name": "sum up/ hypothesis",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2624,
        -80
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are a Product Data & Revenue Analyst.\n\nWe detected an incident:\n{{ $json.incident }}\n\nHere is churn MRR by country (top offenders first):\n{{ $json.churn_by_country }}\n\nHere is churn MRR by plan:\n{{ $json.churn_by_plan }}\n\n1. Summarize what happened in simple business language.\n2. Identify the most impacted segments (country, plan).\n3. Propose 3-5 plausible hypotheses (product issues, price changes, bugs, market events).\n4. Propose 3 concrete next steps for the Product team.\n"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a802b7e3-031a-4ed0-8137-ed9c4a8d63cb",
      "name": "root cause summary",
      "type": "n8n-nodes-base.slack",
      "position": [
        2912,
        -80
      ],
      "webhookId": "3133419b-c60f-4490-af90-15a8bfe14060",
      "parameters": {
        "text": "={   \"text\": \"*Product Health – Root Cause Analysis*\\n\\nIncident:\\n- Metric: {{ $json.metric_name }}\\n- Date: {{ $json.date_start }}\\n- Severity: {{ $json.severity }}\\n\\nAI Summary:\\n{{ $json.ai_summary }}\",   \"mrkdwn\": true }",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "yourvalue",
          "cachedResultName": "product-health-monitor"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "credential-id",
          "name": "slackOAuth2Api Credential"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "cf9ba3bc-5f48-4be4-bed1-183731eec3e6",
      "name": "update incident status",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3280,
        -80
      ],
      "parameters": {
        "query": "UPDATE incidents \nSET status = 'analyzed',\n    ai_summary = $1,\n    updated_at = NOW()\nWHERE incident_id = $2",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "d63d2cd2-ebc7-4282-bd17-3926a7813c3a",
      "name": "daily report trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1488,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a352dc8d-f1c6-415b-a778-d0b501f050b1",
      "name": "sum up",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        320
      ],
      "parameters": {
        "jsCode": "const rows = items\n  .map(i => i.json)\n  .filter(r => r && Object.keys(r).length > 0); \n\n\nif (rows.length === 0) {\n  return [];\n}\n\nconst summary = {};\nfor (const r of rows) {\n  const metric = r.metric_name || 'unknown_metric';\n  const severity = r.severity || 'unknown';\n  const key = metric + '|' + severity;\n  summary[key] = (summary[key] || 0) + 1;\n}\n\nconst today = new Date();\nconst yesterday = new Date(today);\nyesterday.setDate(today.getDate() - 1);\nconst dateStr = yesterday.toISOString().slice(0, 10);\n\nlet text = `Daily Product Health Report – ${dateStr}\\n\\n`;\ntext += `Total incidents: ${rows.length}\\n\\n`;\n\nfor (const [key, count] of Object.entries(summary)) {\n  const [metric, severity] = key.split('|');\n  text += `• ${metric} – ${severity}: ${count}\\n`;\n}\n\ntext += `\\n---\\nThis email was sent automatically with n8n.`;\n\nreturn [\n  {\n    json: {\n      text,\n      incidents: rows,\n    },\n  },\n];\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "b475d6ca-7eb2-4629-9b53-a3429a82cbcd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 1184,
        "height": 320,
        "content": "## 4) daily report\nGenerates daily global health report for leadership."
      },
      "typeVersion": 1
    },
    {
      "id": "6fe9a892-1e9f-4bc7-af5d-8f5b68e35d36",
      "name": "email alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1152,
        0
      ],
      "webhookId": "yourwebhookid",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=<h2>🚨 Product Health – Anomaly Detected</h2>  <p><strong>Metric:</strong> {{$json.metric_name}}</p> <p><strong>Date:</strong> {{$json.date_start || $json.date}}</p> <p><strong>Severity:</strong> {{$json.severity}}</p>  <p><strong>Baseline:</strong> {{$json.baseline_value}}</p> <p><strong>Actual:</strong> {{$json.actual_value}}</p> <p><strong>Delta (%):</strong> {{$json.delta_pct}}</p>  <hr> <p>This incident was logged in Supabase and synced to Notion.</p> <p>— Product Health Bot</p>",
        "options": {},
        "subject": "=🚨 Product Health Alert – {{$json.metric_name}}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "gmailOAuth2 Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "44d5aeab-1255-48e7-a35c-8d6cbe56199d",
      "name": "daily report email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2000,
        320
      ],
      "webhookId": "yourwebhookid",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{$json.text}}",
        "options": {},
        "subject": "📊 Product Health – Daily Report"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "gmailOAuth2 Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "745d2bd0-4bc9-4797-8eaf-c9a5d8d59777",
      "name": "root cause summary email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3104,
        -80
      ],
      "webhookId": "yourwebhookid",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=<h2>Root Cause Summary</h2>  <p>Metric: {{$json.metric_name}}</p> <p>Severity: {{$json.severity}}</p> <p>Summary:</p>  <p>{{$json.summary}}</p>",
        "options": {},
        "subject": "Product Health – Root Cause Summary"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "gmailOAuth2 Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7cb0f8c5-972a-4b52-898f-bfab120f105c",
      "name": "log system",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1312,
        0
      ],
      "parameters": {
        "query": "INSERT INTO system_logs (workflow, status, message)\nVALUES ($1, $2, $3)\n",
        "options": {
          "queryReplacement": "Daily revenue health, success, Incident logged and notifications sent"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "5f13ff70-ebd1-4a50-a593-35f03d0e0cf8",
      "name": "Execute the SQL query",
      "type": "n8n-nodes-base.postgres",
      "position": [
        208,
        0
      ],
      "parameters": {
        "query": "SELECT\n  date,\n  new_mrr,\n  churn_mrr,\n  net_mrr_delta\nFROM daily_revenue_metrics\nWHERE date >= current_date - INTERVAL '30 days'\nORDER BY date;\n",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "957a6f31-5e9e-473a-8238-d18888fbdeb0",
      "name": "Trigger RH",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "df08d817-79cd-4799-8e15-1470aaedfe91",
      "name": "Update notions",
      "type": "n8n-nodes-base.notion",
      "position": [
        800,
        0
      ],
      "parameters": {
        "pageId": {
          "__rl": true,
          "mode": "url",
          "value": "https://www.notion.so/"
        },
        "options": {},
        "resource": "databasePage",
        "operation": "update"
      },
      "credentials": {
        "notionApi": {
          "id": "credential-id",
          "name": "notionApi Credential"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2c680c1d-25c7-4fc7-9d2a-60afd028063d",
      "name": "slack notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        992,
        0
      ],
      "webhookId": "yourwebhookid",
      "parameters": {
        "text": "={   \"text\": \":rotating_light: *Product Health – Revenue Anomaly Detected*\\n\\n*Metric:* {{ $json.metric_name }}\\n*Date:* {{ $json.date }}\\n*Severity:* {{ $json.severity }}\\n*Baseline:* {{ $json.baseline_value }}\\n*Current:* {{ $json.actual_value }}\\n*Delta (%):* {{ $json.delta_pct }}\",   \"mrkdwn\": true }",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "yourvalue",
          "cachedResultName": "product-health-monitor"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "credential-id",
          "name": "slackOAuth2Api Credential"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "18dabaa1-657a-4ae4-938f-64f3897f7244",
      "name": "Condition incident",
      "type": "n8n-nodes-base.if",
      "position": [
        1856,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "32982826-f0c7-40a7-a02c-218779f1c142",
              "operator": {
                "type": "number",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.incident_id }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": false
    },
    {
      "id": "20515047-ac2d-423d-9677-a2cce8bc1d6e",
      "name": "Merge data",
      "type": "n8n-nodes-base.merge",
      "position": [
        2464,
        -80
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "ec378c84-8bcd-437f-ac78-c00a7c2b6649",
      "name": "log system final",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3440,
        -80
      ],
      "parameters": {
        "query": "INSERT INTO system_logs (workflow, status, message)\nVALUES ($1, $2, $3);\n",
        "options": {
          "queryReplacement": "Root cause & summary, success, Root cause analysis generated – summary sent to Product team"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "01c6c372-4bf8-4c00-be38-88cfbff0bfad",
      "name": "anomalies check",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        0
      ],
      "parameters": {
        "jsCode": "const rows = items.map(item => item.json);\n\nif (rows.length < 8) {\n  return [];\n}\n\nrows.sort((a, b) => new Date(a.date) - new Date(b.date));\n\nconst values = rows.map(r => parseFloat(r.churn_mrr || 0));\n\nconst lastIndex = rows.length - 1;\nconst lastRow = rows[lastIndex];\nconst lastValue = values[lastIndex];\n\nconst window = 7;\nconst baselineValues = values.slice(lastIndex - window, lastIndex);\n\nconst mean = baselineValues.reduce((s, v) => s + v, 0) / baselineValues.length;\nconst variance = baselineValues\n  .map(v => Math.pow(v - mean, 2))\n  .reduce((s, v) => s + v, 0) / baselineValues.length;\nconst std = Math.sqrt(variance);\n\n\nif (std === 0) {\n  return [];\n}\n\nconst z = (lastValue - mean) / std;\nconst deltaPct = mean === 0 ? null : ((lastValue - mean) / mean) * 100;\n\nif (z > 2) {\n  const anomaly = {\n    date: lastRow.date,\n    metric_name: 'churn_mrr',\n    baseline_value: mean,\n    actual_value: lastValue,\n    z_score: z,\n    delta_pct: deltaPct,\n    severity:\n      deltaPct !== null && deltaPct > 50 ? 'high'\n      : deltaPct !== null && deltaPct > 25 ? 'medium'\n      : 'low',\n  };\n\n  return [{ json: anomaly }];\n}\n\nreturn [];\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "46fa29f5-e74d-49ad-a6e5-1cb9ceb3eb8a",
      "name": "Slack notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        832,
        320
      ],
      "webhookId": "7e6afeb8-8ebb-4bb2-b856-eb6efed30d9d",
      "parameters": {
        "text": "={   \"text\": \":warning: *Product Health – Feature Usage Drop*\\n\\n*Metric:* {{ $json.metric_name }}\\n*Date:* {{ $json.date }}\\n*Severity:* {{ $json.severity }}\\n*Baseline accounts:* {{ $json.baseline_value }}\\n*Current accounts:* {{ $json.actual_value }}\\n*Delta (%):* {{ $json.delta_pct }}\",   \"mrkdwn\": true }",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "yourvalue",
          "cachedResultName": "product-health-monitor"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "credential-id",
          "name": "slackOAuth2Api Credential"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "30289ed9-1f3b-4372-ac06-85b07d34969f",
      "name": "usage health email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1040,
        320
      ],
      "webhookId": "yourwebhookid",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=<h2>🚨 Usage Anomaly Detected</h2>  <p><strong>Metric:</strong> {{$json.metric_name}}</p> <p><strong>Severity:</strong> {{$json.severity}}</p> <p><strong>Baseline:</strong> {{$json.baseline_value}}</p> <p><strong>Actual:</strong> {{$json.actual_value}}</p> <p><strong>Delta:</strong> {{$json.delta_pct}}%</p>  <p>Logged in Supabase + synced to Notion.</p>",
        "options": {},
        "subject": "=🚨 Usage Anomaly – {{$json.metric_name}}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "gmailOAuth2 Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "f52c020d-2b5e-4ca4-b5ea-7fb94fa5070b",
      "name": "Trigger CS",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1488,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7e760a13-f097-490c-862c-3aff18472946",
      "name": "Trigger UH",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8,
              "triggerAtMinute": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "eebfb6b0-e6f3-4d6a-a711-b1c270207116",
      "name": "log system UH",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1312,
        320
      ],
      "parameters": {
        "query": "INSERT INTO system_logs (workflow, status, message)\nVALUES ($1, $2, $3);\n",
        "options": {
          "queryReplacement": "Daily usage health, success, Usage health check executed – anomaly processed + notifications sent"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "9c37e887-7186-4a2a-a2b2-b841b8d6ddef",
      "name": "Execute SQL query incident check",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1664,
        320
      ],
      "parameters": {
        "query": "SELECT *\nFROM incidents\nWHERE detected_at::date = current_date - 1\nORDER BY detected_at;\n",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6,
      "alwaysOutputData": true
    },
    {
      "id": "36eac13a-bab7-4b21-b572-0299d00ff1c1",
      "name": "Notions database creation",
      "type": "n8n-nodes-base.notion",
      "position": [
        2240,
        320
      ],
      "parameters": {
        "title": "=\"title\": \"=Daily Report – {{ new Date().toISOString().slice(0,10) }}\"",
        "blockUi": {
          "blockValues": [
            {
              "textContent": "={{$json.text}}"
            },
            {
              "textContent": "={{ $json.incidents.length }}"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "url",
          "value": "https://www.notion.so/"
        }
      },
      "credentials": {
        "notionApi": {
          "id": "credential-id",
          "name": "notionApi Credential"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "70b6bd0a-6913-41bd-a116-4cc283b95223",
      "name": "log system final1",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2448,
        320
      ],
      "parameters": {
        "query": "INSERT INTO system_logs (workflow, status, message)\nVALUES ($1, $2, $3);\n",
        "options": {
          "queryReplacement": "Daily report, success, Daily report executed – email + Notion summary created"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "credential-id",
          "name": "postgres Credential"
        }
      },
      "typeVersion": 2.6
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d11c921e-6e03-4d93-a4de-1dd05961236c",
  "connections": {
    "sum up": {
      "main": [
        [
          {
            "node": "daily report email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "anomalies": {
      "main": [
        [
          {
            "node": "insert incidents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge data": {
      "main": [
        [
          {
            "node": "sum up/ hypothesis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger CS": {
      "main": [
        [
          {
            "node": "select open incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger RH": {
      "main": [
        [
          {
            "node": "Execute the SQL query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger UH": {
      "main": [
        [
          {
            "node": "daily usage metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "email alert": {
      "main": [
        [
          {
            "node": "log system",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "log incident": {
      "main": [
        [
          {
            "node": "Update notions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update notions": {
      "main": [
        [
          {
            "node": "slack notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "anomalies check": {
      "main": [
        [
          {
            "node": "log incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "revenue by plan": {
      "main": [
        [
          {
            "node": "Merge data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "insert incidents": {
      "main": [
        [
          {
            "node": "Slack notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Condition incident": {
      "main": [
        [
          {
            "node": "revenue by country",
            "type": "main",
            "index": 0
          },
          {
            "node": "revenue by plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack notification": {
      "main": [
        [
          {
            "node": "usage health email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "daily report email": {
      "main": [
        [
          {
            "node": "Notions database creation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "revenue by country": {
      "main": [
        [
          {
            "node": "Merge data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "root cause summary": {
      "main": [
        [
          {
            "node": "root cause summary email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "slack notification": {
      "main": [
        [
          {
            "node": "email alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "sum up/ hypothesis": {
      "main": [
        [
          {
            "node": "root cause summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "usage health email": {
      "main": [
        [
          {
            "node": "log system UH",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "daily usage metrics": {
      "main": [
        [
          {
            "node": "anomalies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "daily report trigger": {
      "main": [
        [
          {
            "node": "Execute SQL query incident check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "select open incident": {
      "main": [
        [
          {
            "node": "Condition incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute the SQL query": {
      "main": [
        [
          {
            "node": "anomalies check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "update incident status": {
      "main": [
        [
          {
            "node": "log system final",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "root cause summary email": {
      "main": [
        [
          {
            "node": "update incident status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notions database creation": {
      "main": [
        [
          {
            "node": "log system final1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute SQL query incident check": {
      "main": [
        [
          {
            "node": "sum up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}