{ "name": "79 Ops facts (reference)", "nodes": [ { "name": "Facts webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "parameters": { "httpMethod": "POST", "path": "my-ops-facts", "responseMode": "responseNode", "options": {} }, "webhookId": "663cb969-bc5a-52e7-929e-7e0161c2806d", "position": [ 0, 300 ], "id": "2b858e10-bcb6-507a-b018-ccad5055474c" }, { "name": "Get logs", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/logs/query", "options": {}, "method": "POST", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ from: $('Facts webhook').first().json.body?.from || '2026-10-26', to: $('Facts webhook').first().json.body?.to || '2026-10-30' }) }}" }, "position": [ 220, 300 ], "id": "bf44c4e1-c21f-5ce3-8b3c-7a403c9de155" }, { "name": "Get alerts", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/alerts", "options": {} }, "position": [ 440, 300 ], "id": "00bb14a7-97da-5f51-83c0-ba80bb44de43" }, { "name": "Get DLQ", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/dlq", "options": {} }, "position": [ 660, 300 ], "id": "91d75ee3-35c2-5d59-8d9f-4245564a9be2" }, { "name": "Get quality", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/quality-log", "options": {} }, "position": [ 880, 300 ], "id": "ec7984e1-42a4-544a-bf05-c45c5c620978" }, { "name": "Get quarantine", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/quarantine", "options": {} }, "position": [ 1100, 300 ], "id": "a617d18d-5214-5f24-bc5c-49efd6e41e8a" }, { "name": "Get usage", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/usage/summary", "options": {} }, "position": [ 1320, 300 ], "id": "37980fb4-f88c-56a4-81de-79e4a1544312" }, { "name": "Get budget", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/budget", "options": {} }, "position": [ 1540, 300 ], "id": "910bbd7e-c6fe-5a06-abd7-fd4b60dc33da" }, { "name": "Get release", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://data-api:8800/releases/helpdesk", "options": {} }, "position": [ 1760, 300 ], "id": "ad4454f9-c31e-5c86-b44d-70c35b2c6f10" }, { "name": "Make facts", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "jsCode": "const body = $('Facts webhook').first().json.body ?? {};\nconst from = body.from || '2026-10-26', to = body.to || '2026-10-30';\nconst r4 = (v) => Math.round(v * 10000) / 10000;\nconst logs = $('Get logs').first().json.logs ?? [];\nconst total = logs.length, errors = logs.filter((l) => l.status === 'error').length;\nconst d = logs.map((l) => l.duration_ms).sort((a, b) => a - b);\nconst p95 = d.length ? d[Math.max(0, Math.ceil(0.95 * d.length) - 1)] : 0;\nconst groups = {};\nfor (const l of logs.filter((x) => x.status === 'error')) { const k = l.error_node + '\\u0000' + l.error_message; groups[k] = (groups[k] ?? 0) + 1; }\nconst top = Object.entries(groups).sort((a, b) => b[1] - a[1])[0];\nconst rel = {};\nfor (const l of logs.filter((x) => x.route === '자동 답변')) { const x = (rel[l.release] ??= { answers: 0, repeats: 0 }); x.answers += 1; if (l.repeat) x.repeats += 1; }\nconst rate = (name) => rel[name] ? r4(rel[name].repeats / rel[name].answers) : null;\nconst alerts = $('Get alerts').first().json.alerts ?? [];\nconst byReason = {};\nfor (const a of alerts) byReason[a.reason ?? 'unknown'] = (byReason[a.reason ?? 'unknown'] ?? 0) + 1;\nconst dlq = $('Get DLQ').first().json.items ?? [];\nconst q = $('Get quality').first().json.items ?? [];\nconst quarantine = $('Get quarantine').first().json.size ?? 0;\nconst usage = $('Get usage').first().json, budget = $('Get budget').first().json;\nconst users = Object.entries(usage.by_user ?? {}).sort((a, b) => b[1].cost_usd - a[1].cost_usd);\nconst release = $('Get release').first().json;\nconst facts = {\n period: { from, to },\n runs: { total, errors, error_rate: total ? r4(errors / total) : 0, p95_ms: p95,\n unanswered_rate: total ? r4(logs.filter((l) => l.route === '담당자 연결').length / total) : 0,\n top_failure: top ? { node: top[0].split('\\u0000')[0], message: top[0].split('\\u0000')[1], count: top[1] } : null },\n alerts: { count: alerts.length, high: alerts.filter((a) => a.severity === 'high').length, by_reason: byReason },\n dlq: { size: dlq.length, parked: dlq.filter((x) => x.parked).length, waiting: dlq.filter((x) => !x.parked).length },\n quality: { batches: q.length, avg_score: q.length ? r4(q.reduce((s, x) => s + x.score, 0) / q.length) : 0,\n last_score: q.length ? q.at(-1).score : null, rejected_batches: q.filter((x) => x.rejected).length, quarantine },\n cost: { requests: usage.requests ?? 0, calls: usage.calls ?? 0, cost_usd: Math.round((usage.cost_usd ?? 0) * 1e6) / 1e6,\n daily_usd: budget.daily_usd, used_ratio: budget.daily_usd ? r4(budget.used_usd / budget.daily_usd) : 0,\n top_user: users.length ? { user: users[0][0], cost_usd: Math.round(users[0][1].cost_usd * 1e6) / 1e6, share: r4(users[0][1].cost_usd / usage.cost_usd) } : null },\n release: { active: release.active, canary_release: release.canary?.release ?? null, canary_percent: release.canary?.percent ?? 0,\n active_repeat_rate: rate(release.active), canary_repeat_rate: release.canary ? rate(release.canary.release) : null },\n};\n\nreturn [{ json: facts }];" }, "position": [ 1980, 300 ], "id": "207a0874-b62e-5dd2-bf56-3d8f273ca7c1" }, { "name": "Send facts", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": {} }, "position": [ 2200, 300 ], "id": "2e25d833-7a9c-52cf-a2ae-7c20cccb5ba6" } ], "connections": { "Facts webhook": { "main": [ [ { "node": "Get logs", "type": "main", "index": 0 } ] ] }, "Get logs": { "main": [ [ { "node": "Get alerts", "type": "main", "index": 0 } ] ] }, "Get alerts": { "main": [ [ { "node": "Get DLQ", "type": "main", "index": 0 } ] ] }, "Get DLQ": { "main": [ [ { "node": "Get quality", "type": "main", "index": 0 } ] ] }, "Get quality": { "main": [ [ { "node": "Get quarantine", "type": "main", "index": 0 } ] ] }, "Get quarantine": { "main": [ [ { "node": "Get usage", "type": "main", "index": 0 } ] ] }, "Get usage": { "main": [ [ { "node": "Get budget", "type": "main", "index": 0 } ] ] }, "Get budget": { "main": [ [ { "node": "Get release", "type": "main", "index": 0 } ] ] }, "Get release": { "main": [ [ { "node": "Make facts", "type": "main", "index": 0 } ] ] }, "Make facts": { "main": [ [ { "node": "Send facts", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" } }