{ "name": "79 Ops brief (reference)", "nodes": [ { "name": "Brief webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "parameters": { "httpMethod": "POST", "path": "my-ops-brief", "responseMode": "responseNode", "options": {} }, "webhookId": "d8018a49-2915-5dd1-adc6-aa234e759aa9", "position": [ 0, 300 ], "id": "f5d30f9a-d747-55da-969f-360018e9323a" }, { "name": "Get facts", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "url": "http://localhost:5678/webhook/my-ops-facts", "options": { "timeout": 30000 }, "method": "POST", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ from: $json.body?.from ?? '', to: $json.body?.to ?? '' }) }}" }, "position": [ 220, 300 ], "id": "f4e1e0cf-6d75-58c5-963f-ede1302344e8" }, { "name": "Make brief", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "jsCode": "const f = $('Get facts').first().json;\nconst label = { runs: '실행', alerts: '알림', dlq: 'DLQ', quality: '데이터 품질', cost: '비용', release: '릴리스' };\nconst mark = { red: '🔴', yellow: '🟡', green: '🟢', unknown: '⚪' };\nconst actionsFor = {\"runs\": \"오류 묶음 1위 확인 · 고치기 (77일차)\", \"alerts\": \"높은 알림부터 확인하고 닫기 (76일차)\", \"dlq\": \"parked 건 원인 확인 뒤 런북대로 재처리 (76일차)\", \"quality\": \"격리함을 콘텐츠 담당자에게 (78일차)\", \"cost\": \"비용 1위 사용자 · 한도 확인 (75일차)\", \"release\": \"카나리 끄기 · 되돌리기 검토 (74일차)\"};\nconst pct = (v) => (Math.round(v * 10000) / 100) + '%';\nconst g = (k, fn) => (f[k] === null ? '출처 응답 없음' : fn());\nconst key = {\n runs: g('runs', () => `오류율 ${pct(f.runs.error_rate)} · p95 ${f.runs.p95_ms}ms · 답 못 함 ${pct(f.runs.unanswered_rate)}`),\n alerts: g('alerts', () => `${f.alerts.count}건 (높음 ${f.alerts.high})`),\n dlq: g('dlq', () => `${f.dlq.size}건 (parked ${f.dlq.parked})`),\n quality: g('quality', () => `평균 ${f.quality.avg_score} · 마지막 ${f.quality.last_score} · 격리 ${f.quality.quarantine}`),\n cost: g('cost', () => `$${f.cost.cost_usd} / $${f.cost.daily_usd} (${pct(f.cost.used_ratio)})`),\n release: g('release', () => `${f.release.active}` + (f.release.canary_release ? ` · 카나리 ${f.release.canary_release} ${f.release.canary_percent}% (다시 묻기 ${f.release.canary_repeat_rate === null ? '-' : pct(f.release.canary_repeat_rate)})` : '')),\n};\nconst order = ['runs', 'alerts', 'dlq', 'quality', 'cost', 'release'];\nconst focus = [...f.red, ...f.yellow].slice(0, 3);\nconst actions = focus.map((k) => ({ area: k, status: f.health[k], action: actionsFor[k] }));\nconst lines = [\n `# 운영 브리핑 (${f.period.from} ~ ${f.period.to} 실행 기록 + 지금 상태)`, '',\n `전체: ${mark[f.overall]} ${f.overall}`, '',\n '| 영역 | 상태 | 숫자 |', '| --- | --- | --- |',\n ...order.map((k) => `| ${label[k]} | ${mark[f.health[k]]} | ${key[k]} |`), '',\n '## 먼저 볼 것', '',\n ...actions.map((a, i) => `${i + 1}. ${mark[a.status]} ${label[a.area]}: ${a.action}`),\n];\nreturn [{ json: { overall: f.overall, red: f.red, yellow: f.yellow, actions, markdown: lines.join('\\n'), facts: f } }];" }, "position": [ 440, 300 ], "id": "7ac33296-1304-5a8b-a95a-1129228a81e4" }, { "name": "Drop facts", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "jsCode": "const { facts, ...rest } = $json; return [{ json: rest }];" }, "position": [ 660, 300 ], "id": "8932dd61-126a-5bb6-bd78-c2e8f62e131e" }, { "name": "Send brief", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": {} }, "position": [ 880, 300 ], "id": "5f04ca50-933a-5d1c-82a6-b22f607ee3f4" } ], "connections": { "Brief webhook": { "main": [ [ { "node": "Get facts", "type": "main", "index": 0 } ] ] }, "Get facts": { "main": [ [ { "node": "Make brief", "type": "main", "index": 0 } ] ] }, "Make brief": { "main": [ [ { "node": "Drop facts", "type": "main", "index": 0 } ] ] }, "Drop facts": { "main": [ [ { "node": "Send brief", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" } }