$ uv run tools/flow.py push reference/lab03-example2-import.json ! unpublished '78 FAQ import + LLM calls (answer)' (w4b0730166dd93f5): it used the same webhook path ✔ pushed '78 FAQ import + release fixed rows (reference)' (id we9ca88e4f314222) POST http://localhost:5678/webhook/my-faq-import $ uv run tools/flow.py test cases/lab03-fix.json -v ✔ 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/quarantine-clear · HTTP 200 · 0.0s · {"cleared": 2} → request POST http://localhost:8800/quarantine-clear {} ← response HTTP 200 { "cleared": 2 } ✔ before: POST http://localhost:8800/quality-log-clear · HTTP 200 · 0.0s · {"cleared": 2} → request POST http://localhost:8800/quality-log-clear {} ← response HTTP 200 { "cleared": 2 } ✔ 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-faq-import · HTTP 200 · 10.7s · {"batch": "B-2", "total": 10, "imported": 8, "quarantined": 2, "rejected": false, "score": 0.8, "max_invalid_ratio": 0.3 → request POST /webhook/my-faq-import { "batch": "B-2", "items": [ { "id": "FAQ-201", "title": "회의실 예약", "keywords": [ "회의실", "예약" ], "answer": "사내 포털의 '회의실 예약' 메뉴에서 날짜 · 시간 · 인원을 적어 예약합니다." }, { "id": "FAQ-202", "title": "출입증 재발급", "keywords": [ "출입증", "재발급" ], "answer": "출입증을 잃어버렸으면 총무팀에 신분증을 가지고 가서 재발급을 신청합니다." }, { "id": "FAQ-203", "title": "노트북 반납", "keywords": [ "노트북", "반납" ], "answer": "퇴사 5영업일 전까지 IT팀에 노트북과 충전기를 반납합니다." }, { "id": "FAQ-204", "title": "모니터 추가 신청", "keywords": [ "모니터", "추가" ], "answer": "IT 포털의 '장비 신청'에서 모니터 추가를 신청하면 재고 확인 뒤 지급합니다." }, { "id": "FAQ-205", "title": "윈도우 업데이트", "keywords": [ "업데이트", "윈도우" ], "answer": "윈도우 업데이트는 매달 둘째 주 수요일 밤에 자동으로 설치됩니다. 끝나면 다시 시작합니다." }, { "id": "FAQ-206", "title": "화상회의 장비 대여", "keywords": [ "화상회의", "카메라" ], "answer": "화상회의 카메라와 마이크는 각 층 비품실에서 빌리고 같은 날 돌려줍니다." }, { "id": "FAQ-207", "title": "사내 메신저 설치", "keywords": [ "메신저" ], "answer": "사내 메신저는 IT 포털(https://intra.example.com/msg)에서 받아 설치합니다." }, { "id": "FAQ-208", "title": "공유 드라이브 권한", "keywords": [ "공유 드라이브", "권한" ], "answer": "공유 드라이브 권한은 팀장이 IT 포털에서 신청하면 하루 안에 열립니다." }, { "id": "FAQ-209", "title": "보안 사고 신고", "keywords": [ "보안", "신고" ], "answer": "보안 사고는 security.desk@example.com 으로 바로 메일을 보내 주세요." }, { "id": "FAQ-210", "title": "모니터 밝기 조절", "keywords": [ "밝기" ], "answer": "연차 휴가는 입사 1년 뒤부터 15일이며 인사 포털에서 신청합니다." } ] } ← response HTTP 200 { "batch": "B-2", "total": 10, "imported": 8, "quarantined": 2, "rejected": false, "score": 0.8, "max_invalid_ratio": 0.3, "codes": { "pii": 1, "llm_mismatch": 1 }, "imported_ids": [ "FAQ-201", "FAQ-202", "FAQ-203", "FAQ-204", "FAQ-205", "FAQ-206", "FAQ-207", "FAQ-208" ], "quarantined_ids": [ "FAQ-209", "FAQ-210" ], "llm_calls": 9 } POST http://localhost:5678/webhook/my-faq-import · 4 case(s) × 1 ✔ 고친 두 줄 다시 가져오기 · 2.0s → request POST /webhook/my-faq-import { "batch": "B-2-fix", "items": [ { "id": "FAQ-209", "title": "보안 사고 신고", "keywords": [ "보안", "신고" ], "answer": "보안 사고는 IT 포털의 '보안 신고' 메뉴로 바로 알려 주세요. 급하면 IT 문의 창구로 연락합니다." }, { "id": "FAQ-210", "title": "모니터 밝기 조절", "keywords": [ "밝기" ], "answer": "모니터 밝기는 모니터 아래 버튼이나 윈도우 '디스플레이 설정'에서 조절합니다." } ] } ← response HTTP 200 { "batch": "B-2-fix", "total": 2, "imported": 2, "quarantined": 0, "rejected": false, "score": 1, "max_invalid_ratio": 0.3, "codes": {}, "imported_ids": [ "FAQ-209", "FAQ-210" ], "quarantined_ids": [], "llm_calls": 2 } ✔ 격리함이 비었나 · 0.0s → request POST http://localhost:8800/quarantine/list {} ← response HTTP 200 { "size": 0, "items": [] } ✔ FAQ에 FAQ-209 · FAQ-210 · 0.0s → request POST http://localhost:8800/faq/list {} ← response HTTP 200 { "count": 16, "faqs": [ { "id": "FAQ-001", "title": "비밀번호 재설정", "keywords": [ "비밀번호", "패스워드", "password", "재설정", "초기화" ], "answer": "사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다." }, { "id": "FAQ-002", "title": "계정 잠김 해제", "keywords": [ "계정", "잠김", "잠금", "로그인", "실패" ], "answer": "로그인 5회 실패 시 30분 동안 잠깁니다. 30분 후 다시 시도하거나 헬프데스크에 잠금 해제를 요청합니다." }, { "id": "FAQ-003", "title": "VPN 접속 오류", "keywords": [ "vpn", "원격", "재택", "접속", "연결" ], "answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다." }, { "id": "FAQ-004", "title": "프린터 출력 안 됨", "keywords": [ "프린터", "출력", "인쇄", "용지" ], "answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다." }, { "id": "FAQ-005", "title": "메일함 용량 초과", "keywords": [ "메일", "용량", "메일함", "첨부" ], "answer": "오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다." }, { "id": "FAQ-006", "title": "소프트웨어 설치 요청", "keywords": [ "설치", "소프트웨어", "프로그램", "라이선스" ], "answer": "IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다." }, { "id": "FAQ-201", "title": "회의실 예약", "keywords": [ "회의실", "예약" ], "answer": "사내 포털의 '회의실 예약' 메뉴에서 날짜 · 시간 · 인원을 적어 예약합니다." }, { "id": "FAQ-202", "title": "출입증 재발급", "keywords": [ "출입증", "재발급" ], "answer": "출입증을 잃어버렸으면 총무팀에 신분증을 가지고 가서 재발급을 신청합니다." }, { "id": "FAQ-203", "title": "노트북 반납", "keywords": [ "노트북", "반납" ], "answer": "퇴사 5영업일 전까지 IT팀에 노트북과 충전기를 반납합니다." }, { "id": "FAQ-204", "title": "모니터 추가 신청", "keywords": [ "모니터", "추가" ], "answer": "IT 포털의 '장비 신청'에서 모니터 추가를 신청하면 재고 확인 뒤 지급합니다." }, { "id": "FAQ-205", "title": "윈도우 업데이트", "keywords": [ "업데이트", "윈도우" ], "answer": "윈도우 업데이트는 매달 둘째 주 수요일 밤에 자동으로 설치됩니다. 끝나면 다시 시작합니다." }, { "id": "FAQ-206", "title": "화상회의 장비 대여", "keywords": [ "화상회의", "카메라" ], "answer": "화상회의 카메라와 마이크는 각 층 비품실에서 빌리고 같은 날 돌려줍니다." }, { "id": "FAQ-207", "title": "사내 메신저 설치", "keywords": [ "메신저" ], "answer": "사내 메신저는 IT 포털(https://intra.example.com/msg)에서 받아 설치합니다." }, { "id": "FAQ-208", "title": "공유 드라이브 권한", "keywords": [ "공유 드라이브", "권한" ], "answer": "공유 드라이브 권한은 팀장이 IT 포털에서 신청하면 하루 안에 열립니다." }, { "id": "FAQ-209", "title": "보안 사고 신고", "keywords": [ "보안", "신고" ], "answer": "보안 사고는 IT 포털의 '보안 신고' 메뉴로 바로 알려 주세요. 급하면 IT 문의 창구로 연락합니다." }, { "id": "FAQ-210", "title": "모니터 밝기 조절", "keywords": [ "밝기" ], "answer": "모니터 밝기는 모니터 아래 버튼이나 윈도우 '디스플레이 설정'에서 조절합니다." } ] } ✔ 품질 기록 · 점수 1 · 0.0s → request POST http://localhost:8800/quality-log/list {} ← response HTTP 200 { "count": 2, "items": [ { "batch": "B-2", "dataset": "faq", "total": 10, "valid": 8, "quarantined": 2, "rejected": false, "score": 0.8, "codes": { "pii": 1, "llm_mismatch": 1 }, "at": "2026-11-01T18:10:18" }, { "batch": "B-2-fix", "dataset": "faq", "total": 2, "valid": 2, "quarantined": 0, "rejected": false, "score": 1, "codes": {}, "at": "2026-11-01T18:10:20" } ] } Passed 4/4 report: runs/lab03-fix-20261101-181020.json ✔ after: POST http://localhost:8800/faq-reset · HTTP 200 · 0.0s · {"cleared": 10} → request POST http://localhost:8800/faq-reset {} ← response HTTP 200 { "cleared": 10 } $ uv run tools/flow.py quality FAQ: 6 (nothing added) quarantine: 0 quality log: 2 B-2 faq total 10 valid 8 score 0.8 rejected False B-2-fix faq total 2 valid 2 score 1 rejected False alerts: 1 [high] faq-import · low_quality · B-2 quality 0.8