79일차 운영 데이터 기반 Ops Assistant: 실습 15개 · 강의(개요 30장 · 단계 문서 · 4단계 정리 · 05 바이브), 정답본 LAB-A · answers · VIBE-A · tops 포함

This commit is contained in:
mcchae committed 2026-11-03 20:54:35 +09:00
1 parent 9ce9aceba6
commit 6f0ecd59a9
156 files changed
+31640

No files matched your search

@@ -0,0 +1,132 @@
{
"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"
}
}