Files
aio-01d4-stu/Last3Mon/78/answers/25-lab03-challenge2.txt
T

762 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$ uv run tools/flow.py push answers/78-lab03-challenge2-check-log.json answers/78-lab03-challenge2-report.json
! unpublished '78 Schema check (reference)' (w6b17d7c76908a6a): it used the same webhook path
! unpublished '78 Quality report (answer)' (wa6b87a0a6957e93): it used the same webhook path
✔ pushed '78 Schema check + quality log (answer)' (id wf4c2f04db13849f)
POST http://localhost:5678/webhook/my-check
✔ pushed '78 Quality report + by dataset (answer)' (id we40b9a92abbf7fd)
POST http://localhost:5678/webhook/my-quality-report
$ uv run tools/flow.py test cases/lab03-challenge2.json -v
✔ before: POST http://localhost:8800/faq-reset · HTTP 200 · 0.0s · {"cleared": 5}
→ request POST http://localhost:8800/faq-reset
{}
← response HTTP 200
{
"cleared": 5
}
✔ before: POST http://localhost:8800/quarantine-clear · HTTP 200 · 0.0s · {"cleared": 1}
→ request POST http://localhost:8800/quarantine-clear
{}
← response HTTP 200
{
"cleared": 1
}
✔ before: POST http://localhost:8800/quality-log-clear · HTTP 200 · 0.0s · {"cleared": 1}
→ request POST http://localhost:8800/quality-log-clear
{}
← response HTTP 200
{
"cleared": 1
}
✔ before: POST http://localhost:8800/alerts-clear · HTTP 200 · 0.0s · {"cleared": 1}
→ request POST http://localhost:8800/alerts-clear
{}
← response HTTP 200
{
"cleared": 1
}
✔ before: POST /webhook/my-check · HTTP 200 · 0.9s · {"schema": "employee", "batch": "HR-1", "total": 9, "valid": 4, "invalid": 5, "rows": [{"row": 1, "key": "sehoon.oh@exam
→ request POST /webhook/my-check
{
"schema": "employee",
"batch": "HR-1",
"csv": "name,dept,email,phone,joined\n오세훈,영업팀,sehoon.oh@example.com,010-2222-3333,2026-10-01\n윤하늘,재무팀,haneul.yoon@example.com,,2026-10-05\n강민수,개발팀,minsu.kang@example,010-4444-5555,2026-10-06\n서지아,마케팅팀,jia.seo@example.com,010-6666-7777,2026-10-07\n임도윤,인사팀,doyun.lim@example.com,010-8888-9999,2026-13-01\n,IT팀,noname@example.com,,2026-10-09\n배수아,총무팀,sua.bae@example.com,02-123-4567,2026-10-12\n배수아,총무팀,sua.bae@example.com,02-123-4567,2026-10-12\n정다은,IT팀,daeun.jung@example.com,010-1111-2222,2026-10-14"
}
← response HTTP 200
{
"schema": "employee",
"batch": "HR-1",
"total": 9,
"valid": 4,
"invalid": 5,
"rows": [
{
"row": 1,
"key": "sehoon.oh@example.com",
"ok": true,
"errors": []
},
{
"row": 2,
"key": "haneul.yoon@example.com",
"ok": true,
"errors": []
},
{
"row": 3,
"key": "minsu.kang@example",
"ok": false,
"errors": [
"bad_format:email"
]
},
{
"row": 4,
"key": "jia.seo@example.com",
"ok": false,
"errors": [
"not_allowed:dept"
]
},
{
"row": 5,
"key": "doyun.lim@example.com",
"ok": false,
"errors": [
"bad_format:joined"
]
},
{
"row": 6,
"key": "noname@example.com",
"ok": false,
"errors": [
"missing:name"
]
},
{
"row": 7,
"key": "sua.bae@example.com",
"ok": true,
"errors": []
},
{
"row": 8,
"key": "sua.bae@example.com",
"ok": false,
"errors": [
"duplicate_in_batch"
]
},
{
"row": 9,
"key": "daeun.jung@example.com",
"ok": true,
"errors": []
}
],
"codes": {
"bad_format:email": 1,
"not_allowed:dept": 1,
"bad_format:joined": 1,
"missing:name": 1,
"duplicate_in_batch": 1
}
}
✔ before: POST /webhook/my-check · HTTP 200 · 0.3s · {"schema": "ticket", "batch": "TK-1", "total": 9, "valid": 2, "invalid": 7, "rows": [{"row": 1, "key": "T-2001", "ok": t
→ request POST /webhook/my-check
{
"schema": "ticket",
"batch": "TK-1",
"items": [
{
"ticket_id": "T-2001",
"title": "모니터가 켜지지 않음",
"requester": "박서연",
"priority": "normal",
"opened": "2026-10-28",
"contact": "010-2345-6789"
},
{
"ticket_id": "T-12",
"title": "메일 첨부가 안 열림",
"requester": "이준호",
"priority": "low",
"opened": "2026-10-28"
},
{
"ticket_id": "T-2003",
"title": "VPN이 계속 끊김",
"requester": "최유진",
"priority": "urgent",
"opened": "2026-10-29"
},
{
"ticket_id": "T-2004",
"title": "키보드 교체 요청",
"requester": "한지훈",
"priority": "low",
"opened": "2026-02-30"
},
{
"ticket_id": "T-2005",
"title": "프린터",
"requester": "김민지",
"priority": "normal",
"opened": "2026-10-29"
},
{
"ticket_id": "T-2006",
"title": "공유 드라이브 권한",
"requester": "박서연",
"priority": "normal",
"opened": "2026-10-30",
"contact": "010-12-3456"
},
{
"ticket_id": "T-2001",
"title": "모니터가 또 꺼짐",
"requester": "박서연",
"priority": "high",
"opened": "2026-10-30"
},
{
"ticket_id": "T-2008",
"title": "화상회의 카메라 대여",
"requester": "",
"priority": "low",
"opened": "2026-10-30"
},
{
"ticket_id": "T-2009",
"title": "새 직원 계정 만들기",
"requester": "이준호",
"priority": "high",
"opened": "2026-10-30"
}
]
}
← response HTTP 200
{
"schema": "ticket",
"batch": "TK-1",
"total": 9,
"valid": 2,
"invalid": 7,
"rows": [
{
"row": 1,
"key": "T-2001",
"ok": true,
"errors": []
},
{
"row": 2,
"key": "T-12",
"ok": false,
"errors": [
"bad_format:ticket_id"
]
},
{
"row": 3,
"key": "T-2003",
"ok": false,
"errors": [
"not_allowed:priority"
]
},
{
"row": 4,
"key": "T-2004",
"ok": false,
"errors": [
"bad_format:opened"
]
},
{
"row": 5,
"key": "T-2005",
"ok": false,
"errors": [
"too_short:title"
]
},
{
"row": 6,
"key": "T-2006",
"ok": false,
"errors": [
"bad_format:contact"
]
},
{
"row": 7,
"key": "T-2001",
"ok": false,
"errors": [
"duplicate_in_batch"
]
},
{
"row": 8,
"key": "T-2008",
"ok": false,
"errors": [
"missing:requester"
]
},
{
"row": 9,
"key": "T-2009",
"ok": true,
"errors": []
}
],
"codes": {
"bad_format:ticket_id": 1,
"not_allowed:priority": 1,
"bad_format:opened": 1,
"too_short:title": 1,
"bad_format:contact": 1,
"duplicate_in_batch": 1,
"missing:requester": 1
}
}
✔ before: POST /webhook/my-faq-import · HTTP 200 · 5.0s · {"batch": "B-3", "total": 6, "imported": 5, "quarantined": 1, "rejected": false, "score": 0.8333, "max_invalid_ratio": 0
→ request POST /webhook/my-faq-import
{
"batch": "B-3",
"items": [
{
"id": "FAQ-301",
"title": "택배 수령",
"keywords": [
"택배"
],
"answer": "회사로 온 택배는 1층 안내 데스크에서 사원증을 보여 주고 받습니다."
},
{
"id": "FAQ-302",
"title": "주차 등록",
"keywords": [
"주차"
],
"answer": "주차 등록은 총무팀 포털에서 차량 번호를 적어 신청합니다."
},
{
"id": "FAQ-303",
"title": "명함 신청",
"keywords": [
"명함"
],
"answer": "명함은 인사 포털에서 신청하면 일주일 안에 자리로 배달됩니다."
},
{
"id": "FAQ-304",
"title": "사내 식당 메뉴",
"keywords": [
"식당",
"메뉴"
],
"answer": "사내 식당 메뉴는 매주 월요일 아침 사내 게시판에 올라옵니다."
},
{
"id": "FAQ-305",
"title": "교육 신청",
"keywords": [
"교육"
],
"answer": "짧"
},
{
"id": "FAQ-306",
"title": "건강검진",
"keywords": [
"건강검진"
],
"answer": "건강검진은 매년 9월에 인사팀이 안내하며 지정 병원에서 받습니다."
}
]
}
← response HTTP 200
{
"batch": "B-3",
"total": 6,
"imported": 5,
"quarantined": 1,
"rejected": false,
"score": 0.8333,
"max_invalid_ratio": 0.3,
"codes": {
"answer_too_short": 1
},
"imported_ids": [
"FAQ-301",
"FAQ-302",
"FAQ-303",
"FAQ-304",
"FAQ-306"
],
"quarantined_ids": [
"FAQ-305"
],
"llm_calls": 5
}
POST http://localhost:5678/webhook/my-quality-report · 4 case(s) × 1
✔ 검사도 품질 기록 (직원 · 티켓 · FAQ) · 0.0s
→ request POST http://localhost:8800/quality-log/list
{}
← response HTTP 200
{
"count": 3,
"items": [
{
"batch": "HR-1",
"dataset": "employee",
"total": 9,
"valid": 4,
"quarantined": 5,
"rejected": false,
"score": 0.4444,
"codes": {
"bad_format:email": 1,
"not_allowed:dept": 1,
"bad_format:joined": 1,
"missing:name": 1,
"duplicate_in_batch": 1
},
"at": "2026-11-01T18:11:30"
},
{
"batch": "TK-1",
"dataset": "ticket",
"total": 9,
"valid": 2,
"quarantined": 7,
"rejected": false,
"score": 0.2222,
"codes": {
"bad_format:ticket_id": 1,
"not_allowed:priority": 1,
"bad_format:opened": 1,
"too_short:title": 1,
"bad_format:contact": 1,
"duplicate_in_batch": 1,
"missing:requester": 1
},
"at": "2026-11-01T18:11:30"
},
{
"batch": "B-3",
"dataset": "faq",
"total": 6,
"valid": 5,
"quarantined": 1,
"rejected": false,
"score": 0.8333,
"codes": {
"answer_too_short": 1
},
"at": "2026-11-01T18:11:35"
}
]
}
✔ 데이터셋마다 나눠 보기 · 0.1s
→ request POST /webhook/my-quality-report
{}
← response HTTP 200
{
"count": 3,
"batches": [
{
"batch": "HR-1",
"score": 0.4444,
"rejected": false,
"total": 9
},
{
"batch": "TK-1",
"score": 0.2222,
"rejected": false,
"total": 9
},
{
"batch": "B-3",
"score": 0.8333,
"rejected": false,
"total": 6
}
],
"avg_score": 0.5,
"worst_batch": "TK-1",
"rejected_batches": 0,
"top_codes": [
{
"code": "duplicate_in_batch",
"count": 2
},
{
"code": "answer_too_short",
"count": 1
},
{
"code": "bad_format:contact",
"count": 1
},
{
"code": "bad_format:email",
"count": 1
},
{
"code": "bad_format:joined",
"count": 1
},
{
"code": "bad_format:opened",
"count": 1
},
{
"code": "bad_format:ticket_id",
"count": 1
},
{
"code": "missing:name",
"count": 1
},
{
"code": "missing:requester",
"count": 1
},
{
"code": "not_allowed:dept",
"count": 1
},
{
"code": "not_allowed:priority",
"count": 1
},
{
"code": "too_short:title",
"count": 1
}
],
"trend": "좋아짐",
"by_dataset": {
"employee": {
"count": 1,
"avg_score": 0.4444,
"worst_batch": "HR-1",
"top_code": "bad_format:email"
},
"ticket": {
"count": 1,
"avg_score": 0.2222,
"worst_batch": "TK-1",
"top_code": "bad_format:contact"
},
"faq": {
"count": 1,
"avg_score": 0.8333,
"worst_batch": "B-3",
"top_code": "answer_too_short"
}
},
"worst_dataset": "ticket"
}
✔ 가장 나쁜 데이터셋 · 그 이유 · 0.0s
→ request POST /webhook/my-quality-report
{}
← response HTTP 200
{
"count": 3,
"batches": [
{
"batch": "HR-1",
"score": 0.4444,
"rejected": false,
"total": 9
},
{
"batch": "TK-1",
"score": 0.2222,
"rejected": false,
"total": 9
},
{
"batch": "B-3",
"score": 0.8333,
"rejected": false,
"total": 6
}
],
"avg_score": 0.5,
"worst_batch": "TK-1",
"rejected_batches": 0,
"top_codes": [
{
"code": "duplicate_in_batch",
"count": 2
},
{
"code": "answer_too_short",
"count": 1
},
{
"code": "bad_format:contact",
"count": 1
},
{
"code": "bad_format:email",
"count": 1
},
{
"code": "bad_format:joined",
"count": 1
},
{
"code": "bad_format:opened",
"count": 1
},
{
"code": "bad_format:ticket_id",
"count": 1
},
{
"code": "missing:name",
"count": 1
},
{
"code": "missing:requester",
"count": 1
},
{
"code": "not_allowed:dept",
"count": 1
},
{
"code": "not_allowed:priority",
"count": 1
},
{
"code": "too_short:title",
"count": 1
}
],
"trend": "좋아짐",
"by_dataset": {
"employee": {
"count": 1,
"avg_score": 0.4444,
"worst_batch": "HR-1",
"top_code": "bad_format:email"
},
"ticket": {
"count": 1,
"avg_score": 0.2222,
"worst_batch": "TK-1",
"top_code": "bad_format:contact"
},
"faq": {
"count": 1,
"avg_score": 0.8333,
"worst_batch": "B-3",
"top_code": "answer_too_short"
}
},
"worst_dataset": "ticket"
}
✔ 한 데이터셋만 (앞의 칸은 그대로) · 0.1s
→ request POST /webhook/my-quality-report
{
"dataset": "ticket"
}
← response HTTP 200
{
"count": 1,
"batches": [
{
"batch": "TK-1",
"score": 0.2222,
"rejected": false,
"total": 9
}
],
"avg_score": 0.2222,
"worst_batch": "TK-1",
"rejected_batches": 0,
"top_codes": [
{
"code": "bad_format:contact",
"count": 1
},
{
"code": "bad_format:opened",
"count": 1
},
{
"code": "bad_format:ticket_id",
"count": 1
},
{
"code": "duplicate_in_batch",
"count": 1
},
{
"code": "missing:requester",
"count": 1
},
{
"code": "not_allowed:priority",
"count": 1
},
{
"code": "too_short:title",
"count": 1
}
],
"trend": "그대로",
"by_dataset": {
"employee": {
"count": 1,
"avg_score": 0.4444,
"worst_batch": "HR-1",
"top_code": "bad_format:email"
},
"ticket": {
"count": 1,
"avg_score": 0.2222,
"worst_batch": "TK-1",
"top_code": "bad_format:contact"
},
"faq": {
"count": 1,
"avg_score": 0.8333,
"worst_batch": "B-3",
"top_code": "answer_too_short"
}
},
"worst_dataset": "ticket"
}
Passed 4/4
report: runs/lab03-challenge2-20261101-181135.json
$ uv run tools/flow.py test cases/lab03.json
POST http://localhost:5678/webhook/my-check · 4 case(s) × 1
✔ 직원 CSV · 9줄 중 4줄 통과 · 0.1s
✔ 직원 CSV · 줄마다 이유 · 0.1s
✔ 같은 워크플로우로 FAQ 스키마 · 0.1s
✔ 모르는 스키마 → 400 · 0.0s
Passed 4/4
report: runs/lab03-20261101-181136.json
$ uv run tools/flow.py test cases/lab03-challenge.json
✔ before: POST http://localhost:8800/faq-reset · HTTP 200 · 0.0s · {"cleared": 5}
✔ before: POST http://localhost:8800/quarantine-clear · HTTP 200 · 0.0s · {"cleared": 1}
✔ before: POST http://localhost:8800/quality-log-clear · HTTP 200 · 0.0s · {"cleared": 6}
✔ before: POST http://localhost:8800/alerts-clear · HTTP 200 · 0.0s · {"cleared": 1}
! before: POST /webhook/my-faq-import · HTTP 422 · 0.1s (allow_fail: rejected on purpose) · {"error": "batch rejected", "batch": "B-1", "invalid_ratio": 0.7692, "max_invalid_ratio": 0.3, "codes": {"title_missing"
✔ before: POST /webhook/my-faq-import · HTTP 200 · 8.8s · {"batch": "B-2", "total": 10, "imported": 8, "quarantined": 2, "rejected": false, "score": 0.8, "max_invalid_ratio": 0.3
✔ before: POST /webhook/my-faq-import · HTTP 200 · 4.6s · {"batch": "B-3", "total": 6, "imported": 5, "quarantined": 1, "rejected": false, "score": 0.8333, "max_invalid_ratio": 0
✔ before: POST /webhook/my-faq-import · HTTP 200 · 1.8s · {"batch": "B-2-fix", "total": 2, "imported": 2, "quarantined": 0, "rejected": false, "score": 1, "max_invalid_ratio": 0.
POST http://localhost:5678/webhook/my-quality-report · 4 case(s) × 1
✔ 배치 네 개 · 평균 · 가장 나쁜 배치 · 0.0s
✔ 가장 많은 이유 · 흐름 · 0.1s
✔ 배치마다 점수 순서 · 0.0s
✔ 다른 데이터셋으로 거르기 · 0.1s
Passed 4/4
report: runs/lab03-challenge-20261101-181151.json
✔ after: POST http://localhost:8800/faq-reset · HTTP 200 · 0.0s · {"cleared": 15}
$ uv run tools/flow.py quality
FAQ: 6 (nothing added)
quarantine: 1
B-3 FAQ-305 answer_too_short
quality log: 4
B-1 faq total 13 valid 3 score 0 rejected True
B-2 faq total 10 valid 8 score 0.8 rejected False
B-3 faq total 6 valid 5 score 0.8333 rejected False
B-2-fix faq total 2 valid 2 score 1 rejected False
alerts: 3
[high] faq-import · batch_rejected · B-1 rejected
[high] faq-import · low_quality · B-2 quality 0.8
[high] faq-import · low_quality · B-3 quality 0.8333
$ uv run tools/flow.py ops --reset
✔ live run logs, added FAQ, quarantine, quality log, 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)
✔ schemas faq · employee · ticket, quarantine, quality log (day 78 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready