77일차 실행 기록 분석: 실습 15개 · 강의(개요 30장 · 단계 문서 · 4단계 정리 · 05 바이브), 정답본 LAB-A · answers · VIBE-A · tlog 포함

This commit is contained in:
mcchae committed 2026-11-01 23:43:55 +09:00
1 parent 036eae1c6a
commit dcd4cdd66b
148 files changed
+40344

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
$ uv run tools/flow.py ops --reset
✔ live run logs, added FAQ, created tickets, DLQ and alerts emptied, fault switch all ok
$ uv run tools/flow.py off my-log-report my-log-summary my-weekly my-slo-alert my-helpdesk
✔ unpublished my-log-report (id wfed832ca6c81732)
✔ unpublished my-weekly (id w269ccf31103289f)
✔ unpublished my-slo-alert (id w46a402ba838f33c)
✔ unpublished my-helpdesk (id wc5ae6e8bcdf8e39)
✔ n8n restarted. Turn it back on with: uv run tools/flow.py push <the workflow file>
$ uv run tools/flow.py push reference/baseline-report.json
✔ pushed '77 Log report (team baseline, provided)' (id w3f6d15ae763203b)
POST http://localhost:5678/webhook/base-report
$ uv run tools/flow.py doctor
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ dead letter queue /dlq · alerts · fault switch dlq (day 76 data API)
✔ run logs /logs (666 seeded runs) · FAQ additions /faq-extra (day 77 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
+36
View File
@@ -0,0 +1,36 @@
$ uv run tools/flow.py logs --from 2026-10-19 --to 2026-10-30 --last 5
666 run(s) · errors 21 (3.2%)
route: 자동 답변 591 · 담당자 연결 34 · 오류 21 · 잠시 후 20
2026-10-30T17:05:38 success 자동 답변 1311ms 프린터 인쇄 대기열이 안 비워져요
2026-10-30T17:11:46 success 자동 답변 1175ms 메일 메일함 용량이 꽉 찼대요
2026-10-30T17:20:51 success 담당자 연결 79ms 와이파이 회의실 와이파이가 안 잡혀요
2026-10-30T17:24:39 success 자동 답변 1205ms 메일 첨부 파일이 너무 커서 안 보내져요
2026-10-30T17:59:20 success 자동 답변 1076ms 메일 메일함 용량이 꽉 찼대요
$ uv run tools/flow.py logs --from 2026-10-26 --to 2026-10-26 --last 0
70 run(s) · errors 16 (22.9%)
route: 자동 답변 50 · 오류 16 · 담당자 연결 4
$ uv run tools/flow.py call base-report requests/two-weeks.json
HTTP 200 · 1.2s
{
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
$ uv run tools/flow.py call base-report requests/monday.json
HTTP 200 · 0.1s
{
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
+141
View File
@@ -0,0 +1,141 @@
{
"cases": "lab01.json",
"webhook": "base-report",
"passed": 0,
"total": 5,
"results": [
{
"case": "두 주 전체",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 602 (expected 666)",
"error_rate = 3.488372093023256 (expected 0.0315)",
"p50_ms = null (expected 1205)",
"p95_ms = null (expected 2537)"
],
"seconds": 0.2,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
},
{
"case": "두 주 · 갈래별",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"by_route.자동 답변 = 529 (expected 591)",
"by_route.담당자 연결 = 32 (expected 34)",
"by_route.오류 = null (expected 21)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
},
{
"case": "첫 주",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 245 (expected 309)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 245,
"errors": 0,
"error_rate": 0,
"avg_ms": 1317.1551020408162,
"by_route": {
"자동 답변": 230,
"담당자 연결": 15
}
}
},
{
"case": "둘째 주",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 293 (expected 357)",
"error_rate = 7.167235494880546 (expected 0.0588)",
"p95_ms = null (expected 2598)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"total": 293,
"errors": 21,
"error_rate": 7.167235494880546,
"avg_ms": 1173.1399317406144,
"by_route": {
"자동 답변": 237,
"담당자 연결": 15,
"잠시 후": 20
}
}
},
{
"case": "10월 26일 하루",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 0 (expected 70)",
"errors = 0 (expected 16)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
}
]
}
+89
View File
@@ -0,0 +1,89 @@
$ uv run tools/flow.py test cases/lab01.json --webhook base-report -v
POST http://localhost:5678/webhook/base-report · 5 case(s) × 1
✘ 두 주 전체 · 0.2s · total = 602 (expected 666); error_rate = 3.488372093023256 (expected 0.0315); p50_ms = null (expected 1205); p95_ms = null (expected 2537)
→ request POST /webhook/base-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
✘ 두 주 · 갈래별 · 0.1s · by_route.자동 답변 = 529 (expected 591); by_route.담당자 연결 = 32 (expected 34); by_route.오류 = null (expected 21)
→ request POST /webhook/base-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
✘ 첫 주 · 0.1s · total = 245 (expected 309)
→ request POST /webhook/base-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 245,
"errors": 0,
"error_rate": 0,
"avg_ms": 1317.1551020408162,
"by_route": {
"자동 답변": 230,
"담당자 연결": 15
}
}
✘ 둘째 주 · 0.1s · total = 293 (expected 357); error_rate = 7.167235494880546 (expected 0.0588); p95_ms = null (expected 2598)
→ request POST /webhook/base-report
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 293,
"errors": 21,
"error_rate": 7.167235494880546,
"avg_ms": 1173.1399317406144,
"by_route": {
"자동 답변": 237,
"담당자 연결": 15,
"잠시 후": 20
}
}
✘ 10월 26일 하루 · 0.1s · total = 0 (expected 70); errors = 0 (expected 16)
→ request POST /webhook/base-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
Passed 0/5
report: runs/lab01-20261031-074859.json
+135
View File
@@ -0,0 +1,135 @@
{
"cases": "lab01.json",
"webhook": "my-log-report",
"passed": 5,
"total": 5,
"results": [
{
"case": "두 주 전체",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "두 주 · 갈래별",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "첫 주",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
}
}
},
{
"case": "둘째 주",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
}
}
},
{
"case": "10월 26일 하루",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
}
}
}
]
}
+121
View File
@@ -0,0 +1,121 @@
$ uv run tools/flow.py push reference/lab01-example2-report.json
✔ pushed '77 Log report (reference)' (id w1971efa15af33a8)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab01.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 전체 · 0.9s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✔ 두 주 · 갈래별 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✔ 첫 주 · 0.2s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
}
}
✔ 둘째 주 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
}
}
✔ 10월 26일 하루 · 0.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
}
}
Passed 5/5
report: runs/lab01-20261031-074912.json
$ uv run tools/flow.py call my-log-report requests/week2.json
HTTP 200 · 0.1s
{
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
}
}
+148
View File
@@ -0,0 +1,148 @@
{
"cases": "lab01-edges.json",
"webhook": "base-report",
"passed": 2,
"total": 6,
"results": [
{
"case": "하루 · from = to (포함)",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 0 (expected 70)",
"errors = 0 (expected 16)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
},
{
"case": "빈 기간 · 0건이어도 200",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"error_rate = null (expected 0)",
"p95_ms = null (expected 0)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-11-02",
"to": "2026-11-06"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
},
{
"case": "주말 · 실행이 없는 날",
"webhook": "base-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-24",
"to": "2026-10-25"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": null,
"avg_ms": null,
"by_route": {}
}
},
{
"case": "기간을 비우면 전체",
"webhook": "base-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {},
"response": {
"total": 666,
"errors": 21,
"error_rate": 3.153153153153153,
"avg_ms": 1259.3843843843845,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"잠시 후": 20
}
}
},
{
"case": "source seed · 두 주 기록만",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 602 (expected 666)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30",
"source": "seed"
},
"response": {
"total": 602,
"errors": 21,
"error_rate": 3.488372093023256,
"avg_ms": 1249.2392026578073,
"by_route": {
"자동 답변": 529,
"담당자 연결": 32,
"잠시 후": 20
}
}
},
{
"case": "source live · 오늘 실습 기록 (아직 없음)",
"webhook": "base-report",
"run": 1,
"passed": false,
"problems": [
"total = 666 (expected 0)",
"error_rate = 3.153153153153153 (expected 0)"
],
"seconds": 0.1,
"status": 200,
"request": {
"source": "live"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 3.153153153153153,
"avg_ms": 1259.3843843843845,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"잠시 후": 20
}
}
}
]
}
+124
View File
@@ -0,0 +1,124 @@
$ uv run tools/flow.py test cases/lab01-edges.json -v
✔ before: POST http://localhost:8800/logs-reset · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/logs-reset
{}
← response HTTP 200
{
"cleared": 0
}
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 하루 · from = to (포함) · 0.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
}
}
✔ 빈 기간 · 0건이어도 200 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-11-02",
"to": "2026-11-06"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {}
}
✔ 주말 · 실행이 없는 날 · 0.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-24",
"to": "2026-10-25"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {}
}
✔ 기간을 비우면 전체 · 0.1s
→ request POST /webhook/my-log-report
{}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✔ source seed · 두 주 기록만 · 0.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30",
"source": "seed"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✔ source live · 오늘 실습 기록 (아직 없음) · 0.0s
→ request POST /webhook/my-log-report
{
"source": "live"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {}
}
Passed 6/6
report: runs/lab01-edges-20261031-074913.json
$ uv run tools/flow.py test cases/lab01-edges.json --webhook base-report
✔ before: POST http://localhost:8800/logs-reset · HTTP 200 · 0.0s · {"cleared": 0}
POST http://localhost:5678/webhook/base-report · 6 case(s) × 1
✘ 하루 · from = to (포함) · 0.1s · total = 0 (expected 70); errors = 0 (expected 16)
✘ 빈 기간 · 0건이어도 200 · 0.1s · error_rate = null (expected 0); p95_ms = null (expected 0)
✔ 주말 · 실행이 없는 날 · 0.1s
✔ 기간을 비우면 전체 · 0.0s
✘ source seed · 두 주 기록만 · 0.1s · total = 602 (expected 666)
✘ source live · 오늘 실습 기록 (아직 없음) · 0.1s · total = 666 (expected 0); error_rate = 3.153153153153153 (expected 0)
Passed 2/6
report: runs/lab01-edges-20261031-074913.json
@@ -0,0 +1,179 @@
{
"cases": "lab01-challenge.json",
"webhook": "my-log-report",
"passed": 0,
"total": 6,
"results": [
{
"case": "날짜별 · 월요일",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_day.2026-10-26.total = null (expected 70)",
"by_day.2026-10-26.errors = null (expected 16)",
"by_day.2026-10-26.error_rate = null (expected 0.2286)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "날짜별 · 목요일",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_day.2026-10-29.total = null (expected 97)",
"by_day.2026-10-29.errors = null (expected 5)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "날짜는 10일",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_day has none of ['\"2026-10-19\"']"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "릴리스별 다시 묻기 · 1.2.0",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_release has none of ['\"1.2.0\": {\"answers\": 8, \"repeats\": 5, \"repeat_rate\": 0.625}']"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "릴리스별 다시 묻기 · 1.0.0 · 1.1.0",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_release has none of ['\"1.0.0\": {\"answers\": 402, \"repeats\": 19, \"repeat_rate\": 0.0473}']"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
},
{
"case": "1.1.0",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"by_release has none of ['\"1.1.0\": {\"answers\": 181, \"repeats\": 14, \"repeat_rate\": 0.0773}']"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
}
]
}
@@ -0,0 +1,124 @@
$ uv run tools/flow.py test cases/lab01-challenge.json -v
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✘ 날짜별 · 월요일 · 0.1s · by_day.2026-10-26.total = null (expected 70); by_day.2026-10-26.errors = null (expected 16); by_day.2026-10-26.error_rate = null (expected 0.2286)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✘ 날짜별 · 목요일 · 0.1s · by_day.2026-10-29.total = null (expected 97); by_day.2026-10-29.errors = null (expected 5)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✘ 날짜는 10일 · 0.1s · by_day has none of ['"2026-10-19"']
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✘ 릴리스별 다시 묻기 · 1.2.0 · 0.1s · by_release has none of ['"1.2.0": {"answers": 8, "repeats": 5, "repeat_rate": 0.625}']
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✘ 릴리스별 다시 묻기 · 1.0.0 · 1.1.0 · 0.1s · by_release has none of ['"1.0.0": {"answers": 402, "repeats": 19, "repeat_rate": 0.0473}']
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
✘ 1.1.0 · 0.1s · by_release has none of ['"1.1.0": {"answers": 181, "repeats": 14, "repeat_rate": 0.0773}']
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
}
}
Passed 0/6
report: runs/lab01-challenge-20261031-074914.json
+578
View File
@@ -0,0 +1,578 @@
{
"cases": "lab01-challenge.json",
"webhook": "my-log-report",
"passed": 6,
"total": 6,
"results": [
{
"case": "날짜별 · 월요일",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "날짜별 · 목요일",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "날짜는 10일",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "릴리스별 다시 묻기 · 1.2.0",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "릴리스별 다시 묻기 · 1.0.0 · 1.1.0",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "1.1.0",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
}
]
}
+553
View File
@@ -0,0 +1,553 @@
$ uv run tools/flow.py push answers/77-lab01-challenge-day-release.json
! unpublished '77 Log report (reference)' (w1971efa15af33a8): it used the same webhook path
✔ pushed '77 Log report + days and releases (answer)' (id w5ee2fc1d2930303)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab01-challenge.json -v
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 날짜별 · 월요일 · 1.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✔ 날짜별 · 목요일 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✔ 날짜는 10일 · 0.2s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✔ 릴리스별 다시 묻기 · 1.2.0 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✔ 릴리스별 다시 묻기 · 1.0.0 · 1.1.0 · 0.1s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✔ 1.1.0 · 0.0s
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
Passed 6/6
report: runs/lab01-challenge-20261031-074930.json
$ uv run tools/flow.py test cases/lab01.json
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 전체 · 0.1s
✔ 두 주 · 갈래별 · 0.1s
✔ 첫 주 · 0.1s
✔ 둘째 주 · 0.1s
✔ 10월 26일 하루 · 0.1s
Passed 5/5
report: runs/lab01-20261031-074930.json
@@ -0,0 +1,374 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-log-report",
"passed": 0,
"total": 5,
"results": [
{
"case": "두 주 · 단계별 p95",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"steps.faq_ms.p95 = null (expected 39)",
"steps.answer_ms.p95 = null (expected 2509)",
"steps.answer_ms.p50 = null (expected 1179)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "두 주 · 단계마다 잰 실행 수",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"steps.faq_ms.runs = null (expected 646)",
"steps.answer_ms.runs = null (expected 596)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "월요일 · FAQ 단계가 느림",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"steps.faq_ms.p95 = null (expected 1000)",
"steps.answer_ms.p95 = null (expected 2482)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
}
}
},
{
"case": "두 주 · 비용",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"cost_usd = null (expected 0.07836)",
"cost_per_run = null (expected 0.000118)",
"llm_calls = null (expected 596)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
},
{
"case": "빈 기간 · 비용 0",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"cost_usd = null (expected 0)",
"cost_per_run = null (expected 0)",
"llm_calls = null (expected 0)",
"steps.faq_ms.p95 = null (expected 0)"
],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-11-02",
"to": "2026-11-06"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {}
}
}
]
}
@@ -0,0 +1,321 @@
$ uv run tools/flow.py test cases/lab01-challenge2.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✘ 두 주 · 단계별 p95 · 0.1s · steps.faq_ms.p95 = null (expected 39); steps.answer_ms.p95 = null (expected 2509); steps.answer_ms.p50 = null (expected 1179)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✘ 두 주 · 단계마다 잰 실행 수 · 0.1s · steps.faq_ms.runs = null (expected 646); steps.answer_ms.runs = null (expected 596)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✘ 월요일 · FAQ 단계가 느림 · 0.1s · steps.faq_ms.p95 = null (expected 1000); steps.answer_ms.p95 = null (expected 2482)
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
}
}
✘ 두 주 · 비용 · 0.1s · cost_usd = null (expected 0.07836); cost_per_run = null (expected 0.000118); llm_calls = null (expected 596)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
}
}
✘ 빈 기간 · 비용 0 · 0.0s · cost_usd = null (expected 0); cost_per_run = null (expected 0); llm_calls = null (expected 0); steps.faq_ms.p95 = null (expected 0)
→ request POST /webhook/my-log-report
{
"from": "2026-11-02",
"to": "2026-11-06"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {}
}
Passed 0/5
report: runs/lab01-challenge2-20261031-074930.json
@@ -0,0 +1,430 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-log-report",
"passed": 5,
"total": 5,
"results": [
{
"case": "두 주 · 단계별 p95",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "두 주 · 단계마다 잰 실행 수",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "월요일 · FAQ 단계가 느림",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
},
"steps": {
"faq_ms": {
"runs": 70,
"p50": 31,
"p95": 1000
},
"answer_ms": {
"runs": 50,
"p50": 1169,
"p95": 2482
}
},
"cost_usd": 0.00645,
"cost_per_run": 9.2e-05,
"llm_calls": 50
}
},
{
"case": "두 주 · 비용",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "빈 기간 · 비용 0",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-11-02",
"to": "2026-11-06"
},
"response": {
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 0,
"p50": 0,
"p95": 0
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0
}
}
]
}
+422
View File
@@ -0,0 +1,422 @@
$ uv run tools/flow.py push answers/77-lab01-challenge2-steps-cost.json
! unpublished '77 Log report + days and releases (answer)' (w5ee2fc1d2930303): it used the same webhook path
✔ pushed '77 Log report + steps and cost (answer)' (id w0c1a020313bbffc)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab01-challenge2.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 · 단계별 p95 · 0.9s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✔ 두 주 · 단계마다 잰 실행 수 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✔ 월요일 · FAQ 단계가 느림 · 0.2s · 50 calls · $0.006450
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
},
"steps": {
"faq_ms": {
"runs": 70,
"p50": 31,
"p95": 1000
},
"answer_ms": {
"runs": 50,
"p50": 1169,
"p95": 2482
}
},
"cost_usd": 0.00645,
"cost_per_run": 9.2e-05,
"llm_calls": 50
}
✔ 두 주 · 비용 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✔ 빈 기간 · 비용 0 · 0.0s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"from": "2026-11-02",
"to": "2026-11-06"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 0,
"p50": 0,
"p95": 0
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0
}
Passed 5/5
report: runs/lab01-challenge2-20261031-074945.json
$ uv run tools/flow.py test cases/lab01.json
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 전체 · 0.1s · 596 calls · $0.078360
✔ 두 주 · 갈래별 · 0.1s · 596 calls · $0.078360
✔ 첫 주 · 0.1s · 292 calls · $0.038550
✔ 둘째 주 · 0.1s · 304 calls · $0.039810
✔ 10월 26일 하루 · 0.0s · 50 calls · $0.006450
Passed 5/5
report: runs/lab01-20261031-074945.json
$ uv run tools/flow.py test cases/lab01-challenge.json
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 날짜별 · 월요일 · 0.1s · 596 calls · $0.078360
✔ 날짜별 · 목요일 · 0.1s · 596 calls · $0.078360
✔ 날짜는 10일 · 0.1s · 596 calls · $0.078360
✔ 릴리스별 다시 묻기 · 1.2.0 · 0.1s · 596 calls · $0.078360
✔ 릴리스별 다시 묻기 · 1.0.0 · 1.1.0 · 0.1s · 596 calls · $0.078360
✔ 1.1.0 · 0.1s · 596 calls · $0.078360
Passed 6/6
report: runs/lab01-challenge-20261031-074946.json
+651
View File
@@ -0,0 +1,651 @@
{
"cases": "lab02.json",
"webhook": "my-log-report",
"passed": 0,
"total": 6,
"results": [
{
"case": "예제 1 · 가장 많은 실패",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"failures.0.node = null (expected Search FAQ)",
"failures.0.message = null (expected timeout of 1000ms exceeded)",
"failures.0.count = null (expected 16)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "예제 1 · 두 번째 실패",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"failures.1.node = null (expected Answer)",
"failures.1.count = null (expected 5)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "예제 1 · 답하지 못한 주제",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"unanswered_topics.0.topic = null (expected 와이파이)",
"unanswered_topics.0.count = null (expected 34)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "예제 2 · 이상 시간대 1",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies.0.hour = null (expected 2026-10-26T09)",
"anomalies.0.total = null (expected 16)",
"anomalies.0.errors = null (expected 16)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "예제 2 · 이상 시간대 2",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies.1.hour = null (expected 2026-10-29T15)",
"anomalies.1.total = null (expected 7)",
"anomalies.1.errors = null (expected 5)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
},
{
"case": "예제 2 · 첫 주에는 없음",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies = null (expected [])"
],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292
}
}
]
}
+591
View File
@@ -0,0 +1,591 @@
$ uv run tools/flow.py test cases/lab02.json -v
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✘ 예제 1 · 가장 많은 실패 · 0.1s · 596 calls · $0.078360 · failures.0.node = null (expected Search FAQ); failures.0.message = null (expected timeout of 1000ms exceeded); failures.0.count = null (expected 16)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✘ 예제 1 · 두 번째 실패 · 0.1s · 596 calls · $0.078360 · failures.1.node = null (expected Answer); failures.1.count = null (expected 5)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✘ 예제 1 · 답하지 못한 주제 · 0.1s · 596 calls · $0.078360 · unanswered_topics.0.topic = null (expected 와이파이); unanswered_topics.0.count = null (expected 34)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✘ 예제 2 · 이상 시간대 1 · 0.1s · 596 calls · $0.078360 · anomalies.0.hour = null (expected 2026-10-26T09); anomalies.0.total = null (expected 16); anomalies.0.errors = null (expected 16)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✘ 예제 2 · 이상 시간대 2 · 0.1s · 596 calls · $0.078360 · anomalies.1.hour = null (expected 2026-10-29T15); anomalies.1.total = null (expected 7); anomalies.1.errors = null (expected 5)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596
}
✘ 예제 2 · 첫 주에는 없음 · 0.0s · 292 calls · $0.038550 · anomalies = null (expected [])
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292
}
Passed 0/6
report: runs/lab02-20261031-074946.json
+738
View File
@@ -0,0 +1,738 @@
{
"cases": "lab02.json",
"webhook": "my-log-report",
"passed": 3,
"total": 6,
"results": [
{
"case": "예제 1 · 가장 많은 실패",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
},
{
"case": "예제 1 · 두 번째 실패",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
},
{
"case": "예제 1 · 답하지 못한 주제",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.3,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
},
{
"case": "예제 2 · 이상 시간대 1",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies.0.hour = null (expected 2026-10-26T09)",
"anomalies.0.total = null (expected 16)",
"anomalies.0.errors = null (expected 16)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
},
{
"case": "예제 2 · 이상 시간대 2",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies.1.hour = null (expected 2026-10-29T15)",
"anomalies.1.total = null (expected 7)",
"anomalies.1.errors = null (expected 5)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
},
{
"case": "예제 2 · 첫 주에는 없음",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"anomalies = null (expected [])"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
]
}
}
]
}
+693
View File
@@ -0,0 +1,693 @@
$ uv run tools/flow.py push reference/lab02-example1-problems.json
! unpublished '77 Log report + steps and cost (answer)' (w0c1a020313bbffc): it used the same webhook path
✔ pushed '77 Log report + failures and unanswered topics (reference)' (id w51c62bb8e6f4e90)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab02.json -v
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 예제 1 · 가장 많은 실패 · 0.9s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
✔ 예제 1 · 두 번째 실패 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
✔ 예제 1 · 답하지 못한 주제 · 0.3s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
✘ 예제 2 · 이상 시간대 1 · 0.1s · 596 calls · $0.078360 · anomalies.0.hour = null (expected 2026-10-26T09); anomalies.0.total = null (expected 16); anomalies.0.errors = null (expected 16)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
✘ 예제 2 · 이상 시간대 2 · 0.1s · 596 calls · $0.078360 · anomalies.1.hour = null (expected 2026-10-29T15); anomalies.1.total = null (expected 7); anomalies.1.errors = null (expected 5)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
]
}
✘ 예제 2 · 첫 주에는 없음 · 0.1s · 292 calls · $0.038550 · anomalies = null (expected [])
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
]
}
Passed 3/6
report: runs/lab02-20261031-075001.json
+799
View File
@@ -0,0 +1,799 @@
{
"cases": "lab02.json",
"webhook": "my-log-report",
"passed": 6,
"total": 6,
"results": [
{
"case": "예제 1 · 가장 많은 실패",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "예제 1 · 두 번째 실패",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "예제 1 · 답하지 못한 주제",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "예제 2 · 이상 시간대 1",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "예제 2 · 이상 시간대 2",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "예제 2 · 첫 주에는 없음",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
}
]
}
+906
View File
@@ -0,0 +1,906 @@
$ uv run tools/flow.py push reference/lab02-example2-anomalies.json
! unpublished '77 Log report + failures and unanswered topics (reference)' (w51c62bb8e6f4e90): it used the same webhook path
✔ pushed '77 Log report + anomalies (reference)' (id w4957bb6724af44d)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab02.json -v
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 예제 1 · 가장 많은 실패 · 0.9s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 예제 1 · 두 번째 실패 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 예제 1 · 답하지 못한 주제 · 0.2s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 예제 2 · 이상 시간대 1 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 예제 2 · 이상 시간대 2 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 예제 2 · 첫 주에는 없음 · 0.0s · 292 calls · $0.038550
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
Passed 6/6
report: runs/lab02-20261031-075015.json
$ uv run tools/flow.py test cases/lab01-challenge2.json
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 · 단계별 p95 · 0.1s · 596 calls · $0.078360
✔ 두 주 · 단계마다 잰 실행 수 · 0.1s · 596 calls · $0.078360
✔ 월요일 · FAQ 단계가 느림 · 0.0s · 50 calls · $0.006450
✔ 두 주 · 비용 · 0.1s · 596 calls · $0.078360
✔ 빈 기간 · 비용 0 · 0.0s · 0 calls · $0.000000
Passed 5/5
report: runs/lab01-challenge2-20261031-075015.json
$ uv run tools/flow.py call my-log-report requests/two-weeks.json
HTTP 200 · 0.1s
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
+499
View File
@@ -0,0 +1,499 @@
{
"cases": "lab02-periods.json",
"webhook": "my-log-report",
"passed": 5,
"total": 5,
"results": [
{
"case": "첫 주 · 오류 0 → 이상 없음",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
},
{
"case": "둘째 주 · 기준 0.0588 × 3",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "월요일만 · 기준 0.2286 × 3",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"response": {
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
},
"steps": {
"faq_ms": {
"runs": 70,
"p50": 31,
"p95": 1000
},
"answer_ms": {
"runs": 50,
"p50": 1169,
"p95": 2482
}
},
"cost_usd": 0.00645,
"cost_per_run": 9.2e-05,
"llm_calls": 50,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 4
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
}
]
}
},
{
"case": "목요일만 · 15시만",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-29",
"to": "2026-10-29"
},
"response": {
"total": 97,
"errors": 5,
"error_rate": 0.0515,
"p50_ms": 1150,
"p95_ms": 2533,
"by_route": {
"자동 답변": 66,
"담당자 연결": 6,
"잠시 후": 20,
"오류": 5
},
"by_day": {
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
}
},
"by_release": {
"1.1.0": {
"answers": 66,
"repeats": 5,
"repeat_rate": 0.0758
}
},
"steps": {
"faq_ms": {
"runs": 77,
"p50": 24,
"p95": 38
},
"answer_ms": {
"runs": 71,
"p50": 1100,
"p95": 2539
}
},
"cost_usd": 0.00942,
"cost_per_run": 9.7e-05,
"llm_calls": 71,
"failures": [
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 6
}
],
"anomalies": [
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "두 주 · 기준 0.0315 × 3",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
}
]
}
+465
View File
@@ -0,0 +1,465 @@
$ uv run tools/flow.py test cases/lab02-periods.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 첫 주 · 오류 0 → 이상 없음 · 0.1s · 292 calls · $0.038550
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
✔ 둘째 주 · 기준 0.0588 × 3 · 0.1s · 304 calls · $0.039810
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 월요일만 · 기준 0.2286 × 3 · 0.0s · 50 calls · $0.006450
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-26"
}
← response HTTP 200
{
"total": 70,
"errors": 16,
"error_rate": 0.2286,
"p50_ms": 1082,
"p95_ms": 2468,
"by_route": {
"오류": 16,
"자동 답변": 50,
"담당자 연결": 4
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
}
},
"by_release": {
"1.0.0": {
"answers": 50,
"repeats": 1,
"repeat_rate": 0.02
}
},
"steps": {
"faq_ms": {
"runs": 70,
"p50": 31,
"p95": 1000
},
"answer_ms": {
"runs": 50,
"p50": 1169,
"p95": 2482
}
},
"cost_usd": 0.00645,
"cost_per_run": 9.2e-05,
"llm_calls": 50,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 4
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
}
]
}
✔ 목요일만 · 15시만 · 0.1s · 71 calls · $0.009420
→ request POST /webhook/my-log-report
{
"from": "2026-10-29",
"to": "2026-10-29"
}
← response HTTP 200
{
"total": 97,
"errors": 5,
"error_rate": 0.0515,
"p50_ms": 1150,
"p95_ms": 2533,
"by_route": {
"자동 답변": 66,
"담당자 연결": 6,
"잠시 후": 20,
"오류": 5
},
"by_day": {
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
}
},
"by_release": {
"1.1.0": {
"answers": 66,
"repeats": 5,
"repeat_rate": 0.0758
}
},
"steps": {
"faq_ms": {
"runs": 77,
"p50": 24,
"p95": 38
},
"answer_ms": {
"runs": 71,
"p50": 1100,
"p95": 2539
}
},
"cost_usd": 0.00942,
"cost_per_run": 9.7e-05,
"llm_calls": 71,
"failures": [
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 6
}
],
"anomalies": [
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✔ 두 주 · 기준 0.0315 × 3 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
Passed 5/5
report: runs/lab02-periods-20261031-075016.json
@@ -0,0 +1,671 @@
{
"cases": "lab02-challenge.json",
"webhook": "my-log-report",
"passed": 1,
"total": 5,
"results": [
{
"case": "두 주 · 다시 묻는 비율이 가장 높은 주제",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"repeat_topics.0.topic = null (expected 프린터)",
"repeat_topics.0.answers = null (expected 81)",
"repeat_topics.0.repeats = null (expected 8)",
"repeat_topics.0.repeat_rate = null (expected 0.0988)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "두 주 · 수로는 비밀번호가 가장 많지만 비율은 2위",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"repeat_topics.1.topic = null (expected 비밀번호)",
"repeat_topics.1.repeats = null (expected 9)",
"repeat_topics.1.repeat_rate = null (expected 0.0647)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "두 주 · 다시 물은 주제는 6개",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"repeat_topics.5.topic = null (expected 설치)",
"repeat_topics.5.repeat_rate = null (expected 0.0328)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
},
{
"case": "첫 주 · 설치는 다시 묻지 않음",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"repeat_topics.0.topic = null (expected 프린터)"
],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
},
{
"case": "앞의 지표는 그대로",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
}
]
}
@@ -0,0 +1,623 @@
$ uv run tools/flow.py test cases/lab02-challenge.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✘ 두 주 · 다시 묻는 비율이 가장 높은 주제 · 0.1s · 596 calls · $0.078360 · repeat_topics.0.topic = null (expected 프린터); repeat_topics.0.answers = null (expected 81); repeat_topics.0.repeats = null (expected 8); repeat_topics.0.repeat_rate = null (expected 0.0988)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✘ 두 주 · 수로는 비밀번호가 가장 많지만 비율은 2위 · 0.1s · 596 calls · $0.078360 · repeat_topics.1.topic = null (expected 비밀번호); repeat_topics.1.repeats = null (expected 9); repeat_topics.1.repeat_rate = null (expected 0.0647)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✘ 두 주 · 다시 물은 주제는 6개 · 0.1s · 596 calls · $0.078360 · repeat_topics.5.topic = null (expected 설치); repeat_topics.5.repeat_rate = null (expected 0.0328)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
✘ 첫 주 · 설치는 다시 묻지 않음 · 0.0s · 292 calls · $0.038550 · repeat_topics.0.topic = null (expected 프린터)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": []
}
✔ 앞의 지표는 그대로 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
]
}
Passed 1/5
report: runs/lab02-challenge-20261031-075016.json
+841
View File
@@ -0,0 +1,841 @@
{
"cases": "lab02-challenge.json",
"webhook": "my-log-report",
"passed": 5,
"total": 5,
"results": [
{
"case": "두 주 · 다시 묻는 비율이 가장 높은 주제",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
},
{
"case": "두 주 · 수로는 비밀번호가 가장 많지만 비율은 2위",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.3,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
},
{
"case": "두 주 · 다시 물은 주제는 6개",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
},
{
"case": "첫 주 · 설치는 다시 묻지 않음",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
},
{
"case": "앞의 지표는 그대로",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
}
]
}
+823
View File
@@ -0,0 +1,823 @@
$ uv run tools/flow.py push answers/77-lab02-challenge-repeat-topics.json
! unpublished '77 Log report + anomalies (reference)' (w4957bb6724af44d): it used the same webhook path
✔ pushed '77 Log report + repeat topics (answer)' (id wf3d06e3d30eeee6)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab02-challenge.json -v
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 · 다시 묻는 비율이 가장 높은 주제 · 0.9s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
✔ 두 주 · 수로는 비밀번호가 가장 많지만 비율은 2위 · 0.3s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
✔ 두 주 · 다시 물은 주제는 6개 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
✔ 첫 주 · 설치는 다시 묻지 않음 · 0.0s · 292 calls · $0.038550
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
✔ 앞의 지표는 그대로 · 0.1s · 596 calls · $0.078360
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 666,
"errors": 21,
"error_rate": 0.0315,
"p50_ms": 1205,
"p95_ms": 2537,
"by_route": {
"자동 답변": 591,
"담당자 연결": 34,
"오류": 21,
"잠시 후": 20
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 402,
"repeats": 19,
"repeat_rate": 0.0473
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 646,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 596,
"p50": 1179,
"p95": 2509
}
},
"cost_usd": 0.07836,
"cost_per_run": 0.000118,
"llm_calls": 596,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 34
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 81,
"repeats": 8,
"repeat_rate": 0.0988
},
{
"topic": "비밀번호",
"answers": 139,
"repeats": 9,
"repeat_rate": 0.0647
},
{
"topic": "VPN",
"answers": 124,
"repeats": 8,
"repeat_rate": 0.0645
},
{
"topic": "접수 현황",
"answers": 96,
"repeats": 6,
"repeat_rate": 0.0625
},
{
"topic": "메일",
"answers": 90,
"repeats": 5,
"repeat_rate": 0.0556
},
{
"topic": "설치",
"answers": 61,
"repeats": 2,
"repeat_rate": 0.0328
}
]
}
Passed 5/5
report: runs/lab02-challenge-20261031-075031.json
$ uv run tools/flow.py test cases/lab02.json
POST http://localhost:5678/webhook/my-log-report · 6 case(s) × 1
✔ 예제 1 · 가장 많은 실패 · 0.1s · 596 calls · $0.078360
✔ 예제 1 · 두 번째 실패 · 0.1s · 596 calls · $0.078360
✔ 예제 1 · 답하지 못한 주제 · 0.1s · 596 calls · $0.078360
✔ 예제 2 · 이상 시간대 1 · 0.1s · 596 calls · $0.078360
✔ 예제 2 · 이상 시간대 2 · 0.1s · 596 calls · $0.078360
✔ 예제 2 · 첫 주에는 없음 · 0.0s · 292 calls · $0.038550
Passed 6/6
report: runs/lab02-20261031-075031.json
@@ -0,0 +1,73 @@
{
"cases": "lab02-challenge2.json",
"webhook": "my-log-summary",
"passed": 0,
"total": 3,
"results": [
{
"case": "요약 · 숫자는 통계에 있는 것만",
"webhook": "my-log-summary",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"numbers_ok = null (expected true)",
"top_topic = null (expected 와이파이)",
"worst_hour = null (expected 2026-10-26T09)"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "요약 · 주제와 시간대를 말함",
"webhook": "my-log-summary",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"summary has none of ['와이파이']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "요약 · 날짜",
"webhook": "my-log-summary",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"summary has none of ['10월 26일', '10-26', '26일']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
}
]
}
@@ -0,0 +1,40 @@
$ uv run tools/flow.py test cases/lab02-challenge2.json -v
POST http://localhost:5678/webhook/my-log-summary · 3 case(s) × 1
✘ 요약 · 숫자는 통계에 있는 것만 · 0.0s · status 404 (expected 200); numbers_ok = null (expected true); top_topic = null (expected 와이파이); worst_hour = null (expected 2026-10-26T09)
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 요약 · 주제와 시간대를 말함 · 0.0s · status 404 (expected 200); summary has none of ['와이파이']
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 요약 · 날짜 · 0.0s · status 404 (expected 200); summary has none of ['10월 26일', '10-26', '26일']
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-log-summary\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
Passed 0/3
report: runs/lab02-challenge2-20261031-075032.json
@@ -0,0 +1,68 @@
{
"cases": "lab02-challenge2.json",
"webhook": "my-log-summary",
"passed": 3,
"total": 3,
"results": [
{
"case": "요약 · 숫자는 통계에 있는 것만",
"webhook": "my-log-summary",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.4,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 현상은 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
},
{
"case": "요약 · 주제와 시간대를 말함",
"webhook": "my-log-summary",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.6,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
},
{
"case": "요약 · 날짜",
"webhook": "my-log-summary",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.7,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-30"
},
"response": {
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건이 보고되었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
}
]
}
@@ -0,0 +1,60 @@
$ uv run tools/flow.py push answers/77-lab02-challenge2-summary.json
✔ pushed '77 Log summary (answer)' (id w9c86d93226f0b0f)
POST http://localhost:5678/webhook/my-log-summary
$ uv run tools/flow.py test cases/lab02-challenge2.json -v
POST http://localhost:5678/webhook/my-log-summary · 3 case(s) × 1
✔ 요약 · 숫자는 통계에 있는 것만 · 3.4s
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 현상은 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
✔ 요약 · 주제와 시간대를 말함 · 2.6s
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
✔ 요약 · 날짜 · 1.7s
→ request POST /webhook/my-log-summary
{
"from": "2026-10-19",
"to": "2026-10-30"
}
← response HTTP 200
{
"summary": "가장 많은 미응답 주제는 와이파이로 34건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건이 보고되었습니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
Passed 3/3
report: runs/lab02-challenge2-20261031-075051.json
$ uv run tools/flow.py call my-log-summary requests/week2.json
HTTP 200 · 1.8s
{
"summary": "가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.",
"numbers_ok": true,
"unknown_numbers": [],
"top_topic": "와이파이",
"worst_hour": "2026-10-26T09"
}
+218
View File
@@ -0,0 +1,218 @@
{
"cases": "replay-before.json",
"webhook": "my-helpdesk",
"passed": 10,
"total": 10,
"results": [
{
"case": "before · 회의실 와이파이가 안 ",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.1,
"status": 200,
"request": {
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 사내 Wi-Fi 비밀번",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 와이파이가 자주 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 노트북이 와이파이에 연",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 손님용 wifi도 있나",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · VPN이 자꾸 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "VPN",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 비밀번호를 잊어버렸어요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "비밀번호",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 프린터 출력이 안 돼요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "프린터",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 메일함 용량이 꽉 찼대",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "메일",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
},
{
"case": "before · 새 프로그램 설치는 어",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "before"
},
"response": {
"route": "담당자 연결",
"status": "error",
"topic": "설치",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
}
]
}
+386
View File
@@ -0,0 +1,386 @@
$ uv run tools/flow.py push reference/helpdesk-live.json
✔ pushed '77 Live helpdesk (provided)' (id wb754efbe6b67af7)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/replay-before.json -v
✔ before: POST http://localhost:8800/logs-reset · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/logs-reset
{}
← response HTTP 200
{
"cleared": 0
}
✔ before: POST http://localhost:8800/faq-reset · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/faq-reset
{}
← response HTTP 200
{
"cleared": 0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "slow", "tickets": "ok", "prompts": "ok", "usage": "ok", "dlq": "ok", "slow_seconds": 1.2}
→ request POST http://localhost:8800/admin/fault
{
"faq": "slow",
"slow_seconds": 1.2
}
← response HTTP 200
{
"faq": "slow",
"tickets": "ok",
"prompts": "ok",
"usage": "ok",
"dlq": "ok",
"slow_seconds": 1.2
}
POST http://localhost:5678/webhook/my-helpdesk · 10 case(s) × 1
✔ before · 회의실 와이파이가 안 · 2.1s
→ request POST /webhook/my-helpdesk
{
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 사내 Wi-Fi 비밀번 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 와이파이가 자주 끊겨요 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 노트북이 와이파이에 연 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 손님용 wifi도 있나 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "와이파이",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · VPN이 자꾸 끊겨요 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "VPN",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 비밀번호를 잊어버렸어요 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "비밀번호",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 프린터 출력이 안 돼요 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "프린터",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 메일함 용량이 꽉 찼대 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "메일",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
✔ before · 새 프로그램 설치는 어 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "before"
}
← response HTTP 200
{
"route": "담당자 연결",
"status": "error",
"topic": "설치",
"answer": "지금은 답할 수 없어 담당자에게 연결합니다.",
"error": "timeout of 1000ms exceeded"
}
Passed 10/10
report: runs/replay-before-20261031-075117.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "usage": "ok", "dlq": "ok", "slow_seconds": 1.2}
→ request POST http://localhost:8800/admin/fault
{
"faq": "ok",
"tickets": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"usage": "ok",
"dlq": "ok",
"slow_seconds": 1.2
}
$ uv run tools/flow.py logs --tag before --source live --last 3
10 run(s) · errors 10 (100.0%)
route: 오류 10
2026-10-31T07:51:15 error None 1034ms 프린터 Search FAQ 프린터 출력이 안 돼요
2026-10-31T07:51:16 error None 1035ms 메일 Search FAQ 메일함 용량이 꽉 찼대요
2026-10-31T07:51:17 error None 1032ms 설치 Search FAQ 새 프로그램 설치는 어떻게 신청하나요?
$ uv run tools/flow.py test cases/lab03.json -v
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✔ 고치기 전 · 10건 모두 실패 · 0.1s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "before",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 10,
"error_rate": 1,
"p50_ms": 1043,
"p95_ms": 1098,
"by_route": {
"오류": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 10,
"error_rate": 1
}
},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1043,
"p95": 1098
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 10
}
],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✔ 고치기 전 · FAQ timeout · 0.1s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "before",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 10,
"error_rate": 1,
"p50_ms": 1043,
"p95_ms": 1098,
"by_route": {
"오류": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 10,
"error_rate": 1
}
},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1043,
"p95": 1098
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 10
}
],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✘ 고친 뒤 · 오류 없음 · 0.1s · 0 calls · $0.000000 · total = 0 (expected 10); by_route.자동 답변 = null (expected 10)
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 0,
"p50": 0,
"p95": 0
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✔ 고친 뒤 · 답하지 못한 주제 없음 · 0.0s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 0,
"errors": 0,
"error_rate": 0,
"p50_ms": 0,
"p95_ms": 0,
"by_route": {},
"by_day": {},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 0,
"p50": 0,
"p95": 0
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
Passed 3/4
report: runs/lab03-20261031-075118.json
+218
View File
@@ -0,0 +1,218 @@
{
"cases": "replay-after.json",
"webhook": "my-helpdesk",
"passed": 10,
"total": 10,
"results": [
{
"case": "after · 회의실 와이파이가 안 ",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 4.5,
"status": 200,
"request": {
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다.",
"error": null
}
},
{
"case": "after · 사내 Wi-Fi 비밀번",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.4,
"status": 200,
"request": {
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 비밀번호는 별도로 없으며, 사번으로 인증합니다. 회의실에서 연결이 안 되면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "after · 와이파이가 자주 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.4,
"status": 200,
"request": {
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실 등 특정 장소에서 와이파이가 자주 끊기면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "after · 노트북이 와이파이에 연",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.3,
"status": 200,
"request": {
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "after · 손님용 wifi도 있나",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.9,
"status": 200,
"request": {
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "손님용 와이파이에 대한 안내는 제공되지 않습니다. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인하여 사용합니다. 추가 문의 사항은 IT 포털에 문의해 주세요.",
"error": null
}
},
{
"case": "after · VPN이 자꾸 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.4,
"status": 200,
"request": {
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "VPN",
"answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다.",
"error": null
}
},
{
"case": "after · 비밀번호를 잊어버렸어요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.3,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "비밀번호",
"answer": "사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다.",
"error": null
}
},
{
"case": "after · 프린터 출력이 안 돼요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "프린터",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다.",
"error": null
}
},
{
"case": "after · 메일함 용량이 꽉 찼대",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.6,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "메일",
"answer": "오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다.",
"error": null
}
},
{
"case": "after · 새 프로그램 설치는 어",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.4,
"status": 200,
"request": {
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "after"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "설치",
"answer": "새 프로그램 설치는 IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다.",
"error": null
}
}
]
}
+405
View File
@@ -0,0 +1,405 @@
$ uv run tools/flow.py push reference/lab03-example1-helpdesk-fixed.json
! unpublished '77 Live helpdesk (provided)' (wb754efbe6b67af7): it used the same webhook path
✔ pushed '77 Live helpdesk fixed (reference)' (id wc5ae6e8bcdf8e39)
POST http://localhost:5678/webhook/my-helpdesk
$ curl -s -XPOST localhost:8800/faq/add -H 'Content-Type: application/json' -d @requests/faq-wifi.json
{"added": "FAQ-007", "size": 7}
$ uv run tools/flow.py test cases/replay-after.json -v
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "slow", "tickets": "ok", "prompts": "ok", "usage": "ok", "dlq": "ok", "slow_seconds": 1.2}
→ request POST http://localhost:8800/admin/fault
{
"faq": "slow",
"slow_seconds": 1.2
}
← response HTTP 200
{
"faq": "slow",
"tickets": "ok",
"prompts": "ok",
"usage": "ok",
"dlq": "ok",
"slow_seconds": 1.2
}
POST http://localhost:5678/webhook/my-helpdesk · 10 case(s) × 1
✔ after · 회의실 와이파이가 안 · 4.5s
→ request POST /webhook/my-helpdesk
{
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다.",
"error": null
}
✔ after · 사내 Wi-Fi 비밀번 · 2.4s
→ request POST /webhook/my-helpdesk
{
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 비밀번호는 별도로 없으며, 사번으로 인증합니다. 회의실에서 연결이 안 되면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ after · 와이파이가 자주 끊겨요 · 2.4s
→ request POST /webhook/my-helpdesk
{
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실 등 특정 장소에서 와이파이가 자주 끊기면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ after · 노트북이 와이파이에 연 · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ after · 손님용 wifi도 있나 · 2.9s
→ request POST /webhook/my-helpdesk
{
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "손님용 와이파이에 대한 안내는 제공되지 않습니다. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인하여 사용합니다. 추가 문의 사항은 IT 포털에 문의해 주세요.",
"error": null
}
✔ after · VPN이 자꾸 끊겨요 · 2.4s
→ request POST /webhook/my-helpdesk
{
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "VPN",
"answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다.",
"error": null
}
✔ after · 비밀번호를 잊어버렸어요 · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "비밀번호",
"answer": "사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다.",
"error": null
}
✔ after · 프린터 출력이 안 돼요 · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "프린터",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다.",
"error": null
}
✔ after · 메일함 용량이 꽉 찼대 · 2.6s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "메일",
"answer": "오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다.",
"error": null
}
✔ after · 새 프로그램 설치는 어 · 2.4s
→ request POST /webhook/my-helpdesk
{
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "after"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "설치",
"answer": "새 프로그램 설치는 IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다.",
"error": null
}
Passed 10/10
report: runs/replay-after-20261031-075158.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "usage": "ok", "dlq": "ok", "slow_seconds": 1.2}
→ request POST http://localhost:8800/admin/fault
{
"faq": "ok",
"tickets": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"usage": "ok",
"dlq": "ok",
"slow_seconds": 1.2
}
$ uv run tools/flow.py test cases/lab03.json -v
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✔ 고치기 전 · 10건 모두 실패 · 0.1s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "before",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 10,
"error_rate": 1,
"p50_ms": 1043,
"p95_ms": 1098,
"by_route": {
"오류": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 10,
"error_rate": 1
}
},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1043,
"p95": 1098
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 10
}
],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✔ 고치기 전 · FAQ timeout · 0.1s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "before",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 10,
"error_rate": 1,
"p50_ms": 1043,
"p95_ms": 1098,
"by_route": {
"오류": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 10,
"error_rate": 1
}
},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1043,
"p95": 1098
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 10
}
],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✔ 고친 뒤 · 오류 없음 · 0.1s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
✔ 고친 뒤 · 답하지 못한 주제 없음 · 0.0s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
Passed 4/4
report: runs/lab03-20261031-075159.json
$ uv run tools/flow.py ops
fault switch: all ok
live run logs: 20 (tag before 10 · tag after 10)
added FAQ: 1 (FAQ-007)
created tickets: 0
DLQ: 0 (parked 0)
alerts: 0
@@ -0,0 +1,459 @@
{
"cases": "lab03-slo.json",
"webhook": "my-log-report",
"passed": 0,
"total": 4,
"results": [
{
"case": "둘째 주 · SLO 못 지킴",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"slo.ok = null (expected false)",
"unanswered_rate = null (expected 0.0476)",
"slo has none of ['\"error_rate\"', '\"unanswered_rate\"']"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 36,
"repeats": 4,
"repeat_rate": 0.1111
},
{
"topic": "설치",
"answers": 24,
"repeats": 2,
"repeat_rate": 0.0833
},
{
"topic": "비밀번호",
"answers": 70,
"repeats": 5,
"repeat_rate": 0.0714
},
{
"topic": "접수 현황",
"answers": 56,
"repeats": 4,
"repeat_rate": 0.0714
},
{
"topic": "메일",
"answers": 48,
"repeats": 3,
"repeat_rate": 0.0625
},
{
"topic": "VPN",
"answers": 65,
"repeats": 4,
"repeat_rate": 0.0615
}
]
}
},
{
"case": "첫 주 · 답 못 함만",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"slo.ok = null (expected false)",
"slo.breaches = null (expected [\"unanswered_rate\"])"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
},
{
"case": "기준을 바꾸면 · 첫 주",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"slo.ok = null (expected true)"
],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
},
{
"case": "고친 뒤 · 오류 · 답 못 함 기준은 지킴",
"webhook": "my-log-report",
"run": 1,
"passed": false,
"problems": [
"unanswered_rate = null (expected 0)"
],
"seconds": 0.1,
"status": 200,
"request": {
"tag": "after",
"source": "live"
},
"response": {
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
}
]
}
+420
View File
@@ -0,0 +1,420 @@
$ uv run tools/flow.py test cases/lab03-slo.json -v
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✘ 둘째 주 · SLO 못 지킴 · 0.1s · 304 calls · $0.039810 · slo.ok = null (expected false); unanswered_rate = null (expected 0.0476); slo has none of ['"error_rate"', '"unanswered_rate"']
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 36,
"repeats": 4,
"repeat_rate": 0.1111
},
{
"topic": "설치",
"answers": 24,
"repeats": 2,
"repeat_rate": 0.0833
},
{
"topic": "비밀번호",
"answers": 70,
"repeats": 5,
"repeat_rate": 0.0714
},
{
"topic": "접수 현황",
"answers": 56,
"repeats": 4,
"repeat_rate": 0.0714
},
{
"topic": "메일",
"answers": 48,
"repeats": 3,
"repeat_rate": 0.0625
},
{
"topic": "VPN",
"answers": 65,
"repeats": 4,
"repeat_rate": 0.0615
}
]
}
✘ 첫 주 · 답 못 함만 · 0.1s · 292 calls · $0.038550 · slo.ok = null (expected false); slo.breaches = null (expected ["unanswered_rate"])
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
✘ 기준을 바꾸면 · 첫 주 · 0.1s · 292 calls · $0.038550 · slo.ok = null (expected true)
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
]
}
✘ 고친 뒤 · 오류 · 답 못 함 기준은 지킴 · 0.1s · 10 calls · $0.000000 · unanswered_rate = null (expected 0)
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": []
}
Passed 0/4
report: runs/lab03-slo-20261031-075159.json
+495
View File
@@ -0,0 +1,495 @@
{
"cases": "lab03-slo.json",
"webhook": "my-log-report",
"passed": 4,
"total": 4,
"results": [
{
"case": "둘째 주 · SLO 못 지킴",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 36,
"repeats": 4,
"repeat_rate": 0.1111
},
{
"topic": "설치",
"answers": 24,
"repeats": 2,
"repeat_rate": 0.0833
},
{
"topic": "비밀번호",
"answers": 70,
"repeats": 5,
"repeat_rate": 0.0714
},
{
"topic": "접수 현황",
"answers": 56,
"repeats": 4,
"repeat_rate": 0.0714
},
{
"topic": "메일",
"answers": 48,
"repeats": 3,
"repeat_rate": 0.0625
},
{
"topic": "VPN",
"answers": 65,
"repeats": 4,
"repeat_rate": 0.0615
}
],
"unanswered_rate": 0.0476,
"slo": {
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
},
{
"case": "첫 주 · 답 못 함만",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23"
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
],
"unanswered_rate": 0.055,
"slo": {
"ok": false,
"breaches": [
"unanswered_rate"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
},
{
"case": "기준을 바꾸면 · 첫 주",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
},
"response": {
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
],
"unanswered_rate": 0.055,
"slo": {
"ok": true,
"breaches": [],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.06
}
}
}
},
{
"case": "고친 뒤 · 오류 · 답 못 함 기준은 지킴",
"webhook": "my-log-report",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 200,
"request": {
"tag": "after",
"source": "live"
},
"response": {
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": false,
"breaches": [
"p95_ms"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
}
]
}
+540
View File
@@ -0,0 +1,540 @@
$ uv run tools/flow.py push reference/lab03-example2-slo.json
! unpublished '77 Log report + repeat topics (answer)' (wf3d06e3d30eeee6): it used the same webhook path
✔ pushed '77 Log report + SLO (reference)' (id wfed832ca6c81732)
POST http://localhost:5678/webhook/my-log-report
$ uv run tools/flow.py test cases/lab03-slo.json -v
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✔ 둘째 주 · SLO 못 지킴 · 0.9s · 304 calls · $0.039810
→ request POST /webhook/my-log-report
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"total": 357,
"errors": 21,
"error_rate": 0.0588,
"p50_ms": 1171,
"p95_ms": 2598,
"by_route": {
"오류": 21,
"자동 답변": 299,
"담당자 연결": 17,
"잠시 후": 20
},
"by_day": {
"2026-10-26": {
"total": 70,
"errors": 16,
"error_rate": 0.2286
},
"2026-10-27": {
"total": 62,
"errors": 0,
"error_rate": 0
},
"2026-10-28": {
"total": 64,
"errors": 0,
"error_rate": 0
},
"2026-10-29": {
"total": 97,
"errors": 5,
"error_rate": 0.0515
},
"2026-10-30": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 110,
"repeats": 3,
"repeat_rate": 0.0273
},
"1.1.0": {
"answers": 181,
"repeats": 14,
"repeat_rate": 0.0773
},
"1.2.0": {
"answers": 8,
"repeats": 5,
"repeat_rate": 0.625
}
},
"steps": {
"faq_ms": {
"runs": 337,
"p50": 28,
"p95": 40
},
"answer_ms": {
"runs": 304,
"p50": 1162,
"p95": 2532
}
},
"cost_usd": 0.03981,
"cost_per_run": 0.000112,
"llm_calls": 304,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 16
},
{
"node": "Answer",
"message": "429 Rate limit reached for requests",
"count": 5
}
],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [
{
"hour": "2026-10-26T09",
"total": 16,
"errors": 16,
"error_rate": 1
},
{
"hour": "2026-10-29T15",
"total": 7,
"errors": 5,
"error_rate": 0.7143
}
],
"repeat_topics": [
{
"topic": "프린터",
"answers": 36,
"repeats": 4,
"repeat_rate": 0.1111
},
{
"topic": "설치",
"answers": 24,
"repeats": 2,
"repeat_rate": 0.0833
},
{
"topic": "비밀번호",
"answers": 70,
"repeats": 5,
"repeat_rate": 0.0714
},
{
"topic": "접수 현황",
"answers": 56,
"repeats": 4,
"repeat_rate": 0.0714
},
{
"topic": "메일",
"answers": 48,
"repeats": 3,
"repeat_rate": 0.0625
},
{
"topic": "VPN",
"answers": 65,
"repeats": 4,
"repeat_rate": 0.0615
}
],
"unanswered_rate": 0.0476,
"slo": {
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
✔ 첫 주 · 답 못 함만 · 0.1s · 292 calls · $0.038550
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23"
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
],
"unanswered_rate": 0.055,
"slo": {
"ok": false,
"breaches": [
"unanswered_rate"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
✔ 기준을 바꾸면 · 첫 주 · 0.1s · 292 calls · $0.038550
→ request POST /webhook/my-log-report
{
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
}
← response HTTP 200
{
"total": 309,
"errors": 0,
"error_rate": 0,
"p50_ms": 1252,
"p95_ms": 2525,
"by_route": {
"자동 답변": 292,
"담당자 연결": 17
},
"by_day": {
"2026-10-19": {
"total": 73,
"errors": 0,
"error_rate": 0
},
"2026-10-20": {
"total": 57,
"errors": 0,
"error_rate": 0
},
"2026-10-21": {
"total": 60,
"errors": 0,
"error_rate": 0
},
"2026-10-22": {
"total": 55,
"errors": 0,
"error_rate": 0
},
"2026-10-23": {
"total": 64,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.0.0": {
"answers": 292,
"repeats": 16,
"repeat_rate": 0.0548
}
},
"steps": {
"faq_ms": {
"runs": 309,
"p50": 27,
"p95": 39
},
"answer_ms": {
"runs": 292,
"p50": 1200,
"p95": 2470
}
},
"cost_usd": 0.03855,
"cost_per_run": 0.000125,
"llm_calls": 292,
"failures": [],
"unanswered_topics": [
{
"topic": "와이파이",
"count": 17
}
],
"anomalies": [],
"repeat_topics": [
{
"topic": "프린터",
"answers": 45,
"repeats": 4,
"repeat_rate": 0.0889
},
{
"topic": "VPN",
"answers": 59,
"repeats": 4,
"repeat_rate": 0.0678
},
{
"topic": "비밀번호",
"answers": 69,
"repeats": 4,
"repeat_rate": 0.058
},
{
"topic": "접수 현황",
"answers": 40,
"repeats": 2,
"repeat_rate": 0.05
},
{
"topic": "메일",
"answers": 42,
"repeats": 2,
"repeat_rate": 0.0476
}
],
"unanswered_rate": 0.055,
"slo": {
"ok": true,
"breaches": [],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.06
}
}
}
✔ 고친 뒤 · 오류 · 답 못 함 기준은 지킴 · 0.2s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": false,
"breaches": [
"p95_ms"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
Passed 4/4
report: runs/lab03-slo-20261031-075213.json
$ uv run tools/flow.py call my-log-report '{"tag": "after", "source": "live"}'
HTTP 200 · 0.0s
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": false,
"breaches": [
"p95_ms"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
$ uv run tools/flow.py test cases/lab02-challenge.json
POST http://localhost:5678/webhook/my-log-report · 5 case(s) × 1
✔ 두 주 · 다시 묻는 비율이 가장 높은 주제 · 0.1s · 596 calls · $0.078360
✔ 두 주 · 수로는 비밀번호가 가장 많지만 비율은 2위 · 0.1s · 596 calls · $0.078360
✔ 두 주 · 다시 물은 주제는 6개 · 0.1s · 596 calls · $0.078360
✔ 첫 주 · 설치는 다시 묻지 않음 · 0.1s · 292 calls · $0.038550
✔ 앞의 지표는 그대로 · 0.1s · 596 calls · $0.078360
Passed 5/5
report: runs/lab02-challenge-20261031-075213.json
+218
View File
@@ -0,0 +1,218 @@
{
"cases": "replay-normal.json",
"webhook": "my-helpdesk",
"passed": 10,
"total": 10,
"results": [
{
"case": "normal · 회의실 와이파이가 안 ",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.3,
"status": 200,
"request": {
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다.",
"error": null
}
},
{
"case": "normal · 사내 Wi-Fi 비밀번",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 비밀번호는 별도로 없으며, 사번으로 인증합니다. 회의실에서 연결이 안 되면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "normal · 와이파이가 자주 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실 등 특정 장소에서 와이파이가 자주 끊기면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "normal · 노트북이 와이파이에 연",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
},
{
"case": "normal · 손님용 wifi도 있나",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "손님용 와이파이에 대한 안내는 제공되지 않습니다. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인하여 사용합니다. 추가 문의 사항은 IT 포털에 문의해 주세요.",
"error": null
}
},
{
"case": "normal · VPN이 자꾸 끊겨요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "VPN",
"answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다.",
"error": null
}
},
{
"case": "normal · 비밀번호를 잊어버렸어요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.8,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "비밀번호",
"answer": "사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다.",
"error": null
}
},
{
"case": "normal · 프린터 출력이 안 돼요",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "프린터",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다.",
"error": null
}
},
{
"case": "normal · 메일함 용량이 꽉 찼대",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "메일",
"answer": "오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다.",
"error": null
}
},
{
"case": "normal · 새 프로그램 설치는 어",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "normal"
},
"response": {
"route": "자동 답변",
"status": "success",
"topic": "설치",
"answer": "새 프로그램 설치는 IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다.",
"error": null
}
}
]
}
+425
View File
@@ -0,0 +1,425 @@
$ uv run tools/flow.py test cases/replay-normal.json -v
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "usage": "ok", "dlq": "ok", "slow_seconds": 1.2}
→ request POST http://localhost:8800/admin/fault
{
"faq": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"usage": "ok",
"dlq": "ok",
"slow_seconds": 1.2
}
POST http://localhost:5678/webhook/my-helpdesk · 10 case(s) × 1
✔ normal · 회의실 와이파이가 안 · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "회의실 와이파이가 안 잡혀요",
"user": "이준호",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다.",
"error": null
}
✔ normal · 사내 Wi-Fi 비밀번 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "사내 Wi-Fi 비밀번호가 뭐예요?",
"user": "박서연",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 비밀번호는 별도로 없으며, 사번으로 인증합니다. 회의실에서 연결이 안 되면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ normal · 와이파이가 자주 끊겨요 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "와이파이가 자주 끊겨요",
"user": "최유진",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실 등 특정 장소에서 와이파이가 자주 끊기면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ normal · 노트북이 와이파이에 연 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "노트북이 와이파이에 연결이 안 돼요",
"user": "한지훈",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "사내 와이파이는 'CORP-WIFI'에 사번으로 로그인합니다. 회의실에서 와이파이가 잡히지 않으면 가까운 공유기 번호를 IT 포털에 신고해 주세요.",
"error": null
}
✔ normal · 손님용 wifi도 있나 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "손님용 wifi도 있나요?",
"user": "김민지",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "와이파이",
"answer": "손님용 와이파이에 대한 안내는 제공되지 않습니다. 사내 와이파이는 'CORP-WIFI'에 사번으로 로그인하여 사용합니다. 추가 문의 사항은 IT 포털에 문의해 주세요.",
"error": null
}
✔ normal · VPN이 자꾸 끊겨요 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "VPN이 자꾸 끊겨요",
"user": "정우성",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "VPN",
"answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다.",
"error": null
}
✔ normal · 비밀번호를 잊어버렸어요 · 0.8s
→ request POST /webhook/my-helpdesk
{
"question": "비밀번호를 잊어버렸어요",
"user": "오세훈",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "비밀번호",
"answer": "사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다.",
"error": null
}
✔ normal · 프린터 출력이 안 돼요 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "윤하늘",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "프린터",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다.",
"error": null
}
✔ normal · 메일함 용량이 꽉 찼대 · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "강민수",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "메일",
"answer": "오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다.",
"error": null
}
✔ normal · 새 프로그램 설치는 어 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "새 프로그램 설치는 어떻게 신청하나요?",
"user": "서지아",
"tag": "normal"
}
← response HTTP 200
{
"route": "자동 답변",
"status": "success",
"topic": "설치",
"answer": "새 프로그램 설치는 IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다.",
"error": null
}
Passed 10/10
report: runs/replay-normal-20261031-075225.json
$ uv run tools/flow.py test cases/lab03-cost.json -v
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✔ 고치기 전 · 느린 FAQ · 1초에 끊김 · 0.1s · 0 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "before",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 10,
"error_rate": 1,
"p50_ms": 1043,
"p95_ms": 1098,
"by_route": {
"오류": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 10,
"error_rate": 1
}
},
"by_release": {},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1043,
"p95": 1098
},
"answer_ms": {
"runs": 0,
"p50": 0,
"p95": 0
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 0,
"failures": [
{
"node": "Search FAQ",
"message": "timeout of 1000ms exceeded",
"count": 10
}
],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": false,
"breaches": [
"error_rate"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
✔ 고친 뒤 · 느린 FAQ · 기다려서 답함 · 0.1s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "after",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 2342,
"p95_ms": 3560,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 1238,
"p95": 1278
},
"answer_ms": {
"runs": 10,
"p50": 1103,
"p95": 2282
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": false,
"breaches": [
"p95_ms"
],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
✔ 고친 뒤 · 평소 FAQ · 기다릴 일이 없음 · 0.0s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "normal",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 1015,
"p95_ms": 2147,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 17,
"p95": 39
},
"answer_ms": {
"runs": 10,
"p50": 997,
"p95": 2108
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": true,
"breaches": [],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
✔ 고친 뒤 · 답 단계는 FAQ 속도와 상관없음 · 0.1s · 10 calls · $0.000000
→ request POST /webhook/my-log-report
{
"tag": "normal",
"source": "live"
}
← response HTTP 200
{
"total": 10,
"errors": 0,
"error_rate": 0,
"p50_ms": 1015,
"p95_ms": 2147,
"by_route": {
"자동 답변": 10
},
"by_day": {
"2026-10-31": {
"total": 10,
"errors": 0,
"error_rate": 0
}
},
"by_release": {
"1.1.0": {
"answers": 10,
"repeats": 0,
"repeat_rate": 0
}
},
"steps": {
"faq_ms": {
"runs": 10,
"p50": 17,
"p95": 39
},
"answer_ms": {
"runs": 10,
"p50": 997,
"p95": 2108
}
},
"cost_usd": 0,
"cost_per_run": 0,
"llm_calls": 10,
"failures": [],
"unanswered_topics": [],
"anomalies": [],
"repeat_topics": [],
"unanswered_rate": 0,
"slo": {
"ok": true,
"breaches": [],
"limits": {
"error_rate": 0.01,
"p95_ms": 3000,
"unanswered_rate": 0.02
}
}
}
Passed 4/4
report: runs/lab03-cost-20261031-075226.json
$ uv run tools/flow.py ops
fault switch: all ok
live run logs: 30 (tag before 10 · tag after 10 · tag normal 10)
added FAQ: 1 (FAQ-007)
created tickets: 0
DLQ: 0 (parked 0)
alerts: 0
@@ -0,0 +1,172 @@
{
"cases": "lab03-challenge.json",
"webhook": "my-weekly",
"passed": 0,
"total": 7,
"results": [
{
"case": "주간 보고 · 숫자",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"total = null (expected 357)",
"error_rate = null (expected 0.0588)",
"slo_ok = null (expected false)",
"numbers_ok = null (expected true)"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 절",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of ['## 숫자']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 문제 절",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of ['## 문제']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 다음 할 일",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of ['## 다음 할 일']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 비교 숫자",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of ['| 요청 | 309 | 357 |']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 이상 시간대",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of ['2026-10-26 09시']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "주간 보고 · 와이파이",
"webhook": "my-weekly",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"markdown has none of [\"FAQ에 '와이파이' 항목 추가\"]"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
}
]
}
@@ -0,0 +1,102 @@
$ uv run tools/flow.py test cases/lab03-challenge.json -v
POST http://localhost:5678/webhook/my-weekly · 7 case(s) × 1
✘ 주간 보고 · 숫자 · 0.0s · status 404 (expected 200); total = null (expected 357); error_rate = null (expected 0.0588); slo_ok = null (expected false); numbers_ok = null (expected true)
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 절 · 0.0s · status 404 (expected 200); markdown has none of ['## 숫자']
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 문제 절 · 0.0s · status 404 (expected 200); markdown has none of ['## 문제']
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 다음 할 일 · 0.0s · status 404 (expected 200); markdown has none of ['## 다음 할 일']
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 비교 숫자 · 0.0s · status 404 (expected 200); markdown has none of ['| 요청 | 309 | 357 |']
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 이상 시간대 · 0.0s · status 404 (expected 200); markdown has none of ['2026-10-26 09시']
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 주간 보고 · 와이파이 · 0.0s · status 404 (expected 200); markdown has none of ["FAQ에 '와이파이' 항목 추가"]
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-weekly\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
Passed 0/7
report: runs/lab03-challenge-20261031-075226.json
+162
View File
@@ -0,0 +1,162 @@
{
"cases": "lab03-challenge.json",
"webhook": "my-weekly",
"passed": 7,
"total": 7,
"results": [
{
"case": "주간 보고 · 숫자",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.6,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로 오류 16건, 전체 16건입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 절",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.9,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 문제 절",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.4,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 다음 할 일",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.7,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 비교 숫자",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.0,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로 오류 16건, 전체 16건입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 이상 시간대",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.8,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
},
{
"case": "주간 보고 · 와이파이",
"webhook": "my-weekly",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.7,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
},
"response": {
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
}
]
}
+130
View File
@@ -0,0 +1,130 @@
$ uv run tools/flow.py push answers/77-lab03-challenge-weekly.json
✔ pushed '77 Weekly report (answer)' (id w269ccf31103289f)
POST http://localhost:5678/webhook/my-weekly
$ uv run tools/flow.py test cases/lab03-challenge.json -v
POST http://localhost:5678/webhook/my-weekly · 7 case(s) × 1
✔ 주간 보고 · 숫자 · 3.6s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로 오류 16건, 전체 16건입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 절 · 1.9s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 문제 절 · 2.4s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 다음 할 일 · 1.7s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 징후는 10월 26일 09시에 발생했으며, 오류 16건과 총 16건의 요청이 있었습니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 비교 숫자 · 2.0s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로 오류 16건, 전체 16건입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 이상 시간대 · 1.8s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
✔ 주간 보고 · 와이파이 · 1.7s
→ request POST /webhook/my-weekly
{
"from": "2026-10-26",
"to": "2026-10-30",
"prev_from": "2026-10-19",
"prev_to": "2026-10-23"
}
← response HTTP 200
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 이상 징후가 가장 심한 시간대는 10월 26일 09시로, 총 16건 중 16건이 오류입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
Passed 7/7
report: runs/lab03-challenge-20261031-075252.json
$ uv run tools/flow.py call my-weekly requests/weekly.json
HTTP 200 · 1.9s
{
"markdown": "# IT 헬프데스크 주간 운영 보고 (2026-10-26 ~ 2026-10-30)\n\n## 숫자\n\n| 지표 | 지난주 | 이번 주 |\n| --- | --- | --- |\n| 요청 | 309 | 357 |\n| 오류율 | 0% | 5.88% |\n| p95 | 2525ms | 2598ms |\n| 답 못 함 | 5.5% | 4.76% |\n| SLO | 못 지킴 unanswered_rate | 못 지킴 error_rate, unanswered_rate |\n\n## 문제\n\n- 실패 16건 · Search FAQ · timeout of 1000ms exceeded\n- 실패 5건 · Answer · 429 Rate limit reached for requests\n- 답하지 못한 질문 17건 · 와이파이\n- 이상 시간대 2026-10-26 09시 · 16/16 실패\n- 이상 시간대 2026-10-29 15시 · 5/7 실패\n\n## 요약 (LLM, 숫자 확인 통과)\n\n가장 많은 미응답 주제는 와이파이로 17건입니다. 가장 흔한 장애는 Search FAQ 노드에서 발생한 \"timeout of 1000ms exceeded\" 메시지로 16건입니다. 가장 심각한 이상 시간대는 10월 26일 09시로 오류 16건, 전체 16건입니다.\n\n## 다음 할 일\n\n- FAQ에 '와이파이' 항목 추가\n- FAQ 검색 timeout · 재시도 설정 점검 (72일차)\n- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)",
"total": 357,
"error_rate": 0.0588,
"slo_ok": false,
"numbers_ok": true
}
@@ -0,0 +1,132 @@
{
"cases": "lab03-challenge2.json",
"webhook": "my-slo-alert",
"passed": 0,
"total": 6,
"results": [
{
"case": "둘째 주 · SLO 못 지킴 → 알림",
"webhook": "my-slo-alert",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"ok = null (expected false)",
"breaches = null (expected [\"error_rate\", \"unanswered_rate\"])",
"alert = null (expected sent)",
"message has none of ['error_rate 0.0588 > 0.01']"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "알림 확인 (high · slo)",
"webhook": "http://localhost:8800/alerts/latest",
"run": 1,
"passed": false,
"problems": [
"workflow = null (expected slo)",
"severity = null (expected high)",
"reason = null (expected slo)",
"message has none of ['unanswered_rate']"
],
"seconds": 0.0,
"status": 200,
"request": {},
"response": {}
},
{
"case": "10분 안에 다시 확인 → 보내지 않음",
"webhook": "my-slo-alert",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"ok = null (expected false)",
"alert = null (expected suppressed)"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "slo 알림은 1개",
"webhook": "http://localhost:8800/alerts/list",
"run": 1,
"passed": false,
"problems": [
"count = 0 (expected 1)"
],
"seconds": 0.0,
"status": 200,
"request": {
"workflow": "slo"
},
"response": {
"count": 0,
"alerts": []
}
},
{
"case": "기준 안이면 알림 없음",
"webhook": "my-slo-alert",
"run": 1,
"passed": false,
"problems": [
"status 404 (expected 200)",
"ok = null (expected true)",
"breaches = null (expected [])",
"alert = null (expected none)"
],
"seconds": 0.0,
"status": 404,
"request": {
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
},
"response": {
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
},
{
"case": "그래도 알림은 1개",
"webhook": "http://localhost:8800/alerts/list",
"run": 1,
"passed": false,
"problems": [
"count = 0 (expected 1)"
],
"seconds": 0.0,
"status": 200,
"request": {
"workflow": "slo"
},
"response": {
"count": 0,
"alerts": []
}
}
]
}
@@ -0,0 +1,75 @@
$ uv run tools/flow.py test cases/lab03-challenge2.json -v
✔ before: POST http://localhost:8800/alerts-clear · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/alerts-clear
{}
← response HTTP 200
{
"cleared": 0
}
POST http://localhost:5678/webhook/my-slo-alert · 6 case(s) × 1
✘ 둘째 주 · SLO 못 지킴 → 알림 · 0.0s · status 404 (expected 200); ok = null (expected false); breaches = null (expected ["error_rate", "unanswered_rate"]); alert = null (expected sent); message has none of ['error_rate 0.0588 > 0.01']
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 알림 확인 (high · slo) · 0.0s · workflow = null (expected slo); severity = null (expected high); reason = null (expected slo); message has none of ['unanswered_rate']
→ request POST http://localhost:8800/alerts/latest
{}
← response HTTP 200
{}
✘ 10분 안에 다시 확인 → 보내지 않음 · 0.0s · status 404 (expected 200); ok = null (expected false); alert = null (expected suppressed)
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ slo 알림은 1개 · 0.0s · count = 0 (expected 1)
→ request POST http://localhost:8800/alerts/list
{
"workflow": "slo"
}
← response HTTP 200
{
"count": 0,
"alerts": []
}
✘ 기준 안이면 알림 없음 · 0.0s · status 404 (expected 200); ok = null (expected true); breaches = null (expected []); alert = null (expected none)
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
}
← response HTTP 404
{
"code": 404,
"message": "The requested webhook \"POST my-slo-alert\" is not registered.",
"hint": "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"
}
✘ 그래도 알림은 1개 · 0.0s · count = 0 (expected 1)
→ request POST http://localhost:8800/alerts/list
{
"workflow": "slo"
}
← response HTTP 200
{
"count": 0,
"alerts": []
}
Passed 0/6
report: runs/lab03-challenge2-20261031-075255.json
@@ -0,0 +1,154 @@
{
"cases": "lab03-challenge2.json",
"webhook": "my-slo-alert",
"passed": 6,
"total": 6,
"results": [
{
"case": "둘째 주 · SLO 못 지킴 → 알림",
"webhook": "my-slo-alert",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"alert": "sent",
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"total": 357
}
},
{
"case": "알림 확인 (high · slo)",
"webhook": "http://localhost:8800/alerts/latest",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {},
"response": {
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
},
{
"case": "10분 안에 다시 확인 → 보내지 않음",
"webhook": "my-slo-alert",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.3,
"status": 200,
"request": {
"from": "2026-10-26",
"to": "2026-10-30"
},
"response": {
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"alert": "suppressed",
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"total": 357
}
},
{
"case": "slo 알림은 1개",
"webhook": "http://localhost:8800/alerts/list",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"workflow": "slo"
},
"response": {
"count": 1,
"alerts": [
{
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
]
}
},
{
"case": "기준 안이면 알림 없음",
"webhook": "my-slo-alert",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 200,
"request": {
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
},
"response": {
"ok": true,
"breaches": [],
"alert": "none",
"message": "",
"total": 309
}
},
{
"case": "그래도 알림은 1개",
"webhook": "http://localhost:8800/alerts/list",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 200,
"request": {
"workflow": "slo"
},
"response": {
"count": 1,
"alerts": [
{
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
]
}
}
]
}
+160
View File
@@ -0,0 +1,160 @@
$ uv run tools/flow.py push answers/77-lab03-challenge2-slo-alert.json
✔ pushed '77 SLO alert (answer)' (id w46a402ba838f33c)
POST http://localhost:5678/webhook/my-slo-alert
$ uv run tools/flow.py test cases/lab03-challenge2.json -v
✔ before: POST http://localhost:8800/alerts-clear · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/alerts-clear
{}
← response HTTP 200
{
"cleared": 0
}
POST http://localhost:5678/webhook/my-slo-alert · 6 case(s) × 1
✔ 둘째 주 · SLO 못 지킴 → 알림 · 1.0s
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"alert": "sent",
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"total": 357
}
✔ 알림 확인 (high · slo) · 0.0s
→ request POST http://localhost:8800/alerts/latest
{}
← response HTTP 200
{
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
✔ 10분 안에 다시 확인 → 보내지 않음 · 0.3s
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-26",
"to": "2026-10-30"
}
← response HTTP 200
{
"ok": false,
"breaches": [
"error_rate",
"unanswered_rate"
],
"alert": "suppressed",
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"total": 357
}
✔ slo 알림은 1개 · 0.0s
→ request POST http://localhost:8800/alerts/list
{
"workflow": "slo"
}
← response HTTP 200
{
"count": 1,
"alerts": [
{
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
]
}
✔ 기준 안이면 알림 없음 · 0.1s
→ request POST /webhook/my-slo-alert
{
"from": "2026-10-19",
"to": "2026-10-23",
"slo": {
"unanswered_rate": 0.06
}
}
← response HTTP 200
{
"ok": true,
"breaches": [],
"alert": "none",
"message": "",
"total": 309
}
✔ 그래도 알림은 1개 · 0.0s
→ request POST http://localhost:8800/alerts/list
{
"workflow": "slo"
}
← response HTTP 200
{
"count": 1,
"alerts": [
{
"workflow": "slo",
"severity": "high",
"reason": "slo",
"breaches": [
"error_rate",
"unanswered_rate"
],
"message": "SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476 > 0.02",
"at": 1793400787.7696092
}
]
}
Passed 6/6
report: runs/lab03-challenge2-20261031-075308.json
$ uv run tools/flow.py test cases/lab03-slo.json
POST http://localhost:5678/webhook/my-log-report · 4 case(s) × 1
✔ 둘째 주 · SLO 못 지킴 · 0.1s · 304 calls · $0.039810
✔ 첫 주 · 답 못 함만 · 0.1s · 292 calls · $0.038550
✔ 기준을 바꾸면 · 첫 주 · 0.1s · 292 calls · $0.038550
✔ 고친 뒤 · 오류 · 답 못 함 기준은 지킴 · 0.1s · 10 calls · $0.000000
Passed 4/4
report: runs/lab03-slo-20261031-075308.json
$ uv run tools/flow.py ops
fault switch: all ok
live run logs: 30 (tag before 10 · tag after 10 · tag normal 10)
added FAQ: 1 (FAQ-007)
created tickets: 0
DLQ: 0 (parked 0)
alerts: 1
[high] slo · slo · - · SLO breach: error_rate 0.0588 > 0.01, unanswered_rate 0.0476
$ uv run tools/flow.py ops --reset
✔ live run logs, added FAQ, created tickets, DLQ and alerts emptied, fault switch all ok
$ uv run tools/flow.py doctor
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ dead letter queue /dlq · alerts · fault switch dlq (day 76 data API)
✔ run logs /logs (666 seeded runs) · FAQ additions /faq-extra (day 77 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
@@ -0,0 +1,101 @@
{
"name": "77 Log report + days and releases (answer)",
"nodes": [
{
"name": "Report webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-log-report",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "Get logs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://data-api:8800/logs/query",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from ?? '', to: $json.body.to ?? '', tag: $json.body.tag ?? '', source: $json.body.source ?? 'all' }) }}",
"options": {}
},
"position": [
220,
300
]
},
{
"name": "Compute metrics",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const pct = (vals, p) => { const s = [...vals].sort((a, b) => a - b); return s.length ? s[Math.max(0, Math.ceil(p * s.length) - 1)] : 0; };\nconst rate = (a, b) => (b ? Math.round((a / b) * 10000) / 10000 : 0);\nconst rows = $json.logs ?? [];\nconst errors = rows.filter((r) => r.status === 'error').length;\nconst by_route = {};\nfor (const r of rows) { const k = r.route ?? '오류'; by_route[k] = (by_route[k] ?? 0) + 1; }\nconst d = rows.map((r) => r.duration_ms);\nconst out = { total: rows.length, errors, error_rate: rate(errors, rows.length), p50_ms: pct(d, 0.5), p95_ms: pct(d, 0.95), by_route };\nconst group = (key) => { const g = {}; for (const r of rows) (g[key(r)] ??= []).push(r); return g; };\nout.by_day = Object.fromEntries(Object.entries(group((r) => r.started_at.slice(0, 10))).sort().map(([k, v]) => {\n const e = v.filter((r) => r.status === 'error').length; return [k, { total: v.length, errors: e, error_rate: rate(e, v.length) }]; }));\nout.by_release = {};\nfor (const r of rows.filter((x) => x.route === '자동 답변')) { const b = (out.by_release[r.release] ??= { answers: 0, repeats: 0 }); b.answers += 1; if (r.repeat) b.repeats += 1; }\nfor (const b of Object.values(out.by_release)) b.repeat_rate = rate(b.repeats, b.answers);\nreturn [{ json: out }];"
},
"position": [
440,
300
]
},
{
"name": "Send report",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
660,
300
]
}
],
"connections": {
"Report webhook": {
"main": [
[
{
"node": "Get logs",
"type": "main",
"index": 0
}
]
]
},
"Get logs": {
"main": [
[
{
"node": "Compute metrics",
"type": "main",
"index": 0
}
]
]
},
"Compute metrics": {
"main": [
[
{
"node": "Send report",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,101 @@
{
"name": "77 Log report + steps and cost (answer)",
"nodes": [
{
"name": "Report webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-log-report",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "Get logs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://data-api:8800/logs/query",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from ?? '', to: $json.body.to ?? '', tag: $json.body.tag ?? '', source: $json.body.source ?? 'all' }) }}",
"options": {}
},
"position": [
220,
300
]
},
{
"name": "Compute metrics",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const pct = (vals, p) => { const s = [...vals].sort((a, b) => a - b); return s.length ? s[Math.max(0, Math.ceil(p * s.length) - 1)] : 0; };\nconst rate = (a, b) => (b ? Math.round((a / b) * 10000) / 10000 : 0);\nconst rows = $json.logs ?? [];\nconst errors = rows.filter((r) => r.status === 'error').length;\nconst by_route = {};\nfor (const r of rows) { const k = r.route ?? '오류'; by_route[k] = (by_route[k] ?? 0) + 1; }\nconst d = rows.map((r) => r.duration_ms);\nconst out = { total: rows.length, errors, error_rate: rate(errors, rows.length), p50_ms: pct(d, 0.5), p95_ms: pct(d, 0.95), by_route };\nconst group = (key) => { const g = {}; for (const r of rows) (g[key(r)] ??= []).push(r); return g; };\nout.by_day = Object.fromEntries(Object.entries(group((r) => r.started_at.slice(0, 10))).sort().map(([k, v]) => {\n const e = v.filter((r) => r.status === 'error').length; return [k, { total: v.length, errors: e, error_rate: rate(e, v.length) }]; }));\nout.by_release = {};\nfor (const r of rows.filter((x) => x.route === '자동 답변')) { const b = (out.by_release[r.release] ??= { answers: 0, repeats: 0 }); b.answers += 1; if (r.repeat) b.repeats += 1; }\nfor (const b of Object.values(out.by_release)) b.repeat_rate = rate(b.repeats, b.answers);\nout.steps = {};\nfor (const k of ['faq_ms', 'answer_ms']) { const v = rows.map((r) => r.steps?.[k] ?? 0).filter((x) => x > 0); out.steps[k] = { runs: v.length, p50: pct(v, 0.5), p95: pct(v, 0.95) }; }\nout.cost_usd = Math.round(rows.reduce((a, r) => a + (r.cost_usd ?? 0), 0) * 100000) / 100000;\nout.cost_per_run = rows.length ? Math.round((out.cost_usd / rows.length) * 1000000) / 1000000 : 0;\nout.llm_calls = rows.reduce((a, r) => a + (r.llm_calls ?? 0), 0);\nreturn [{ json: out }];"
},
"position": [
440,
300
]
},
{
"name": "Send report",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
660,
300
]
}
],
"connections": {
"Report webhook": {
"main": [
[
{
"node": "Get logs",
"type": "main",
"index": 0
}
]
]
},
"Get logs": {
"main": [
[
{
"node": "Compute metrics",
"type": "main",
"index": 0
}
]
]
},
"Compute metrics": {
"main": [
[
{
"node": "Send report",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,101 @@
{
"name": "77 Log report + repeat topics (answer)",
"nodes": [
{
"name": "Report webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-log-report",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "Get logs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://data-api:8800/logs/query",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from ?? '', to: $json.body.to ?? '', tag: $json.body.tag ?? '', source: $json.body.source ?? 'all' }) }}",
"options": {}
},
"position": [
220,
300
]
},
{
"name": "Compute metrics",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const pct = (vals, p) => { const s = [...vals].sort((a, b) => a - b); return s.length ? s[Math.max(0, Math.ceil(p * s.length) - 1)] : 0; };\nconst rate = (a, b) => (b ? Math.round((a / b) * 10000) / 10000 : 0);\nconst rows = $json.logs ?? [];\nconst errors = rows.filter((r) => r.status === 'error').length;\nconst by_route = {};\nfor (const r of rows) { const k = r.route ?? '오류'; by_route[k] = (by_route[k] ?? 0) + 1; }\nconst d = rows.map((r) => r.duration_ms);\nconst out = { total: rows.length, errors, error_rate: rate(errors, rows.length), p50_ms: pct(d, 0.5), p95_ms: pct(d, 0.95), by_route };\nconst group = (key) => { const g = {}; for (const r of rows) (g[key(r)] ??= []).push(r); return g; };\nout.by_day = Object.fromEntries(Object.entries(group((r) => r.started_at.slice(0, 10))).sort().map(([k, v]) => {\n const e = v.filter((r) => r.status === 'error').length; return [k, { total: v.length, errors: e, error_rate: rate(e, v.length) }]; }));\nout.by_release = {};\nfor (const r of rows.filter((x) => x.route === '자동 답변')) { const b = (out.by_release[r.release] ??= { answers: 0, repeats: 0 }); b.answers += 1; if (r.repeat) b.repeats += 1; }\nfor (const b of Object.values(out.by_release)) b.repeat_rate = rate(b.repeats, b.answers);\nout.steps = {};\nfor (const k of ['faq_ms', 'answer_ms']) { const v = rows.map((r) => r.steps?.[k] ?? 0).filter((x) => x > 0); out.steps[k] = { runs: v.length, p50: pct(v, 0.5), p95: pct(v, 0.95) }; }\nout.cost_usd = Math.round(rows.reduce((a, r) => a + (r.cost_usd ?? 0), 0) * 100000) / 100000;\nout.cost_per_run = rows.length ? Math.round((out.cost_usd / rows.length) * 1000000) / 1000000 : 0;\nout.llm_calls = rows.reduce((a, r) => a + (r.llm_calls ?? 0), 0);\nconst fails = {};\nfor (const r of rows.filter((x) => x.status === 'error')) { const k = `${r.error_node}|${r.error_message}`; fails[k] = (fails[k] ?? 0) + 1; }\nout.failures = Object.entries(fails).map(([k, count]) => ({ node: k.split('|')[0], message: k.split('|')[1], count })).sort((a, b) => b.count - a.count);\nconst un = {};\nfor (const r of rows.filter((x) => x.route === '담당자 연결')) un[r.topic] = (un[r.topic] ?? 0) + 1;\nout.unanswered_topics = Object.entries(un).map(([topic, count]) => ({ topic, count })).sort((a, b) => b.count - a.count);\nconst hours = {};\nfor (const r of rows) (hours[r.started_at.slice(0, 13)] ??= []).push(r);\nout.anomalies = Object.entries(hours).sort().map(([hour, v]) => { const e = v.filter((r) => r.status === 'error').length; return { hour, total: v.length, errors: e, error_rate: rate(e, v.length) }; })\n .filter((h) => h.total >= 5 && h.error_rate >= 3 * out.error_rate && h.errors > 0);\nconst rep = {};\nfor (const r of rows.filter((x) => x.route === '자동 답변')) { const t = (rep[r.topic] ??= { topic: r.topic, answers: 0, repeats: 0 }); t.answers += 1; if (r.repeat) t.repeats += 1; }\nout.repeat_topics = Object.values(rep).filter((t) => t.repeats > 0).map((t) => ({ ...t, repeat_rate: rate(t.repeats, t.answers) }))\n .sort((a, b) => b.repeat_rate - a.repeat_rate || b.repeats - a.repeats);\nreturn [{ json: out }];"
},
"position": [
440,
300
]
},
{
"name": "Send report",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
660,
300
]
}
],
"connections": {
"Report webhook": {
"main": [
[
{
"node": "Get logs",
"type": "main",
"index": 0
}
]
]
},
"Get logs": {
"main": [
[
{
"node": "Compute metrics",
"type": "main",
"index": 0
}
]
]
},
"Compute metrics": {
"main": [
[
{
"node": "Send report",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,167 @@
{
"name": "77 Log summary (answer)",
"nodes": [
{
"name": "Summary webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-log-summary",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "Get report",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://localhost:5678/webhook/my-log-report",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from ?? '', to: $json.body.to ?? '', tag: $json.body.tag ?? '', source: $json.body.source ?? 'all' }) }}",
"options": {
"timeout": 30000
}
},
"position": [
220,
300
]
},
{
"name": "Write summary",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"parameters": {
"promptType": "define",
"text": "={{ JSON.stringify({ total: $json.total, errors: $json.errors, error_rate: $json.error_rate, failures: $json.failures, unanswered_topics: $json.unanswered_topics, anomalies: $json.anomalies }) }}",
"options": {
"systemMessage": "You write a short Korean operations summary for an IT help desk team from the statistics JSON given. Write exactly 3 sentences in 합니다체. Use only numbers that appear in the statistics (you may write a rate like 0.0588 as 5.88%). Sentence 1: the biggest unanswered topic and its count. Sentence 2: the most common failure (node, message, count). Sentence 3: the worst hour from anomalies, written like 10월 26일 09시, with its errors and total. Do not guess causes that are not in the data. Do not add any other numbers.",
"maxIterations": 1
}
},
"position": [
440,
300
]
},
{
"name": "Summary model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "gpt-4.1-mini"
},
"options": {
"temperature": 0
}
},
"position": [
440,
520
],
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
}
},
{
"name": "Check numbers",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const stats = $('Get report').first().json;\nconst summary = String($json.output ?? '').trim();\nconst allowed = new Set();\nconst addNum = (v) => { allowed.add(String(v)); for (const k of [100, 10, 1]) allowed.add(String(Math.round(v * 100 * k) / k)); };\nconst walk = (v) => {\n if (Array.isArray(v)) v.forEach(walk);\n else if (v && typeof v === 'object') Object.entries(v).forEach(([k, x]) => { (String(k).match(/\\d+(\\.\\d+)?/g) ?? []).forEach((n) => allowed.add(String(Number(n)))); walk(x); });\n else if (typeof v === 'string') (v.match(/\\d+(\\.\\d+)?/g) ?? []).forEach((n) => allowed.add(String(Number(n))));\n else if (typeof v === 'number') addNum(v);\n};\nwalk(stats);\nconst found = (summary.match(/\\d+(\\.\\d+)?/g) ?? []).map((n) => String(Number(n)));\nconst unknown = [...new Set(found.filter((n) => !allowed.has(n)))];\nconst worst = [...(stats.anomalies ?? [])].sort((a, b) => b.error_rate - a.error_rate)[0];\nreturn [{ json: { summary, numbers_ok: unknown.length === 0, unknown_numbers: unknown,\n top_topic: stats.unanswered_topics?.[0]?.topic ?? null, worst_hour: worst?.hour ?? null } }];"
},
"position": [
760,
300
]
},
{
"name": "Send summary",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
980,
300
]
}
],
"connections": {
"Summary webhook": {
"main": [
[
{
"node": "Get report",
"type": "main",
"index": 0
}
]
]
},
"Get report": {
"main": [
[
{
"node": "Write summary",
"type": "main",
"index": 0
}
]
]
},
"Write summary": {
"main": [
[
{
"node": "Check numbers",
"type": "main",
"index": 0
}
]
]
},
"Check numbers": {
"main": [
[
{
"node": "Send summary",
"type": "main",
"index": 0
}
]
]
},
"Summary model": {
"ai_languageModel": [
[
{
"node": "Write summary",
"type": "ai_languageModel",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,163 @@
{
"name": "77 Weekly report (answer)",
"nodes": [
{
"name": "Weekly webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-weekly",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "This week",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://localhost:5678/webhook/my-log-report",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from, to: $json.body.to }) }}",
"options": {
"timeout": 30000
}
},
"position": [
220,
300
]
},
{
"name": "Last week",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://localhost:5678/webhook/my-log-report",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $('Weekly webhook').first().json.body.prev_from, to: $('Weekly webhook').first().json.body.prev_to }) }}",
"options": {
"timeout": 30000
}
},
"position": [
440,
300
]
},
{
"name": "Summary",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://localhost:5678/webhook/my-log-summary",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $('Weekly webhook').first().json.body.from, to: $('Weekly webhook').first().json.body.to }) }}",
"options": {
"timeout": 60000
}
},
"position": [
660,
300
]
},
{
"name": "Write report",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const b = $('Weekly webhook').first().json.body;\nconst now = $('This week').first().json, prev = $('Last week').first().json, s = $('Summary').first().json;\nconst pctS = (v) => `${Math.round(v * 10000) / 100}%`;\nconst hour = (h) => `${h.slice(0, 10)} ${h.slice(11, 13)}시`;\nconst slo = (r) => (r.slo?.ok ? '지킴' : '못 지킴 ' + (r.slo?.breaches ?? []).join(', '));\nconst lines = [`# IT 헬프데스크 주간 운영 보고 (${b.from} ~ ${b.to})`, ''];\nlines.push('## 숫자', '', '| 지표 | 지난주 | 이번 주 |', '| --- | --- | --- |');\nlines.push(`| 요청 | ${prev.total} | ${now.total} |`, `| 오류율 | ${pctS(prev.error_rate)} | ${pctS(now.error_rate)} |`,\n `| p95 | ${prev.p95_ms}ms | ${now.p95_ms}ms |`, `| 답 못 함 | ${pctS(prev.unanswered_rate)} | ${pctS(now.unanswered_rate)} |`, `| SLO | ${slo(prev)} | ${slo(now)} |`, '');\nlines.push('## 문제', '');\nfor (const f of (now.failures ?? []).slice(0, 3)) lines.push(`- 실패 ${f.count}건 · ${f.node} · ${f.message}`);\nfor (const u of (now.unanswered_topics ?? []).slice(0, 3)) lines.push(`- 답하지 못한 질문 ${u.count}건 · ${u.topic}`);\nfor (const a of now.anomalies ?? []) lines.push(`- 이상 시간대 ${hour(a.hour)} · ${a.errors}/${a.total} 실패`);\nlines.push('', '## 요약 (LLM, 숫자 확인 ' + (s.numbers_ok ? '통과' : '실패: ' + s.unknown_numbers.join(', ')) + ')', '', s.summary, '');\nlines.push('## 다음 할 일', '');\nif ((now.unanswered_topics ?? [])[0]) lines.push(`- FAQ에 '${now.unanswered_topics[0].topic}' 항목 추가`);\nif ((now.failures ?? []).some((f) => f.node === 'Search FAQ')) lines.push('- FAQ 검색 timeout · 재시도 설정 점검 (72일차)');\nif ((now.failures ?? []).some((f) => /429/.test(f.message))) lines.push('- LLM 사용량 한도 · 대체 경로 점검 (75 · 72일차)');\nreturn [{ json: { markdown: lines.join('\\n'), total: now.total, error_rate: now.error_rate, slo_ok: now.slo?.ok ?? null, numbers_ok: s.numbers_ok } }];"
},
"position": [
880,
300
]
},
{
"name": "Send weekly",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
1100,
300
]
}
],
"connections": {
"Weekly webhook": {
"main": [
[
{
"node": "This week",
"type": "main",
"index": 0
}
]
]
},
"This week": {
"main": [
[
{
"node": "Last week",
"type": "main",
"index": 0
}
]
]
},
"Last week": {
"main": [
[
{
"node": "Summary",
"type": "main",
"index": 0
}
]
]
},
"Summary": {
"main": [
[
{
"node": "Write report",
"type": "main",
"index": 0
}
]
]
},
"Write report": {
"main": [
[
{
"node": "Send weekly",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,239 @@
{
"name": "77 SLO alert (answer)",
"nodes": [
{
"name": "SLO webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-slo-alert",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
]
},
{
"name": "Get report",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://localhost:5678/webhook/my-log-report",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ from: $json.body.from ?? '', to: $json.body.to ?? '', tag: $json.body.tag ?? '', source: $json.body.source ?? 'all', slo: $json.body.slo ?? {} }) }}",
"options": {
"timeout": 30000
}
},
"position": [
220,
300
]
},
{
"name": "Recent alerts",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://data-api:8800/alerts/list",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ workflow: 'slo', reason: 'slo', within: 600 }) }}",
"options": {}
},
"position": [
440,
300
]
},
{
"name": "Decide",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const r = $('Get report').first().json;\nconst recent = $json.count ?? 0;\nconst breaches = r.slo?.breaches ?? [];\nconst message = breaches.length ? 'SLO breach: ' + breaches.map((k) => `${k} ${r[k]} > ${r.slo.limits[k]}`).join(', ') : '';\nconst alert = breaches.length === 0 ? 'none' : recent > 0 ? 'suppressed' : 'sent';\nreturn [{ json: { ok: breaches.length === 0, breaches, alert, message, total: r.total } }];"
},
"position": [
660,
300
]
},
{
"name": "Send it?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.alert === 'sent' }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
880,
300
]
},
{
"name": "Send alert",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "http://data-api:8800/alerts",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ workflow: 'slo', severity: 'high', reason: 'slo', breaches: $json.breaches, message: $json.message }) }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"position": [
1100,
200
]
},
{
"name": "Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "return [{ json: $('Decide').first().json }];"
},
"position": [
1320,
300
]
},
{
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
1540,
300
]
}
],
"connections": {
"SLO webhook": {
"main": [
[
{
"node": "Get report",
"type": "main",
"index": 0
}
]
]
},
"Get report": {
"main": [
[
{
"node": "Recent alerts",
"type": "main",
"index": 0
}
]
]
},
"Recent alerts": {
"main": [
[
{
"node": "Decide",
"type": "main",
"index": 0
}
]
]
},
"Decide": {
"main": [
[
{
"node": "Send it?",
"type": "main",
"index": 0
}
]
]
},
"Send it?": {
"main": [
[
{
"node": "Send alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Result",
"type": "main",
"index": 0
}
]
]
},
"Send alert": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
]
]
},
"Result": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
File diff suppressed because it is too large. Load diff