# 3단계 · 품질 관리 - 교시: 5교시 강의 (14:00~14:50) · 6교시 [Lab 03](../../Last3Mon/78/LAB-03-Q-QUALITY-MANAGEMENT.md) 실습 5개 (15:00~15:50) - 이 단계가 끝나면: FAQ에 박힌 규칙을 **스키마**(규칙을 적은 데이터)로 꺼내 다른 데이터에도 쓰고, 스키마로 되는 규칙과 안 되는 규칙을 나누고, 격리된 줄이 고쳐져 돌아오면 격리함을 비우는 되돌림 고리를 만들고, 새 데이터셋을 워크플로우를 고치지 않고 늘리고, 품질 기록으로 보고서 · 데이터셋별 품질을 정의대로 말할 수 있다. - 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, 78일차 판 데이터 API의 스키마 셋 `faq` · `employee` · `ticket`, 직원 CSV 9줄 · 접수 건 9줄 · FAQ 배치, 11/01 정답 워크플로우를 학생 순서대로 1회). - 실습 가리키기: 이 문서에서 "Lab 03 예제 1"은 6교시 [Lab 03 문서](../../Last3Mon/78/LAB-03-Q-QUALITY-MANAGEMENT.md)의 그 절입니다. 파일 경로는 실습 폴더 `~/aio-01d4-stu/Last3Mon/78` 기준입니다. ```mermaid flowchart LR A["1. 스키마"] --> B["2. 되는 것 · 안 되는 것"] --> C["3. 되돌림 고리"] --> D["4. 설정으로 넓히기"] D --> E["5. 품질 보고서"] --> F["6. 데이터셋별"] --> G["7. 하루를 한 그림으로"] ``` ## 0. 이 시간 (50분) | 시간 | 절 · 개요 | 하는 일 | | --- | --- | --- | | 14:00~14:10 | 1 · 개요 22장 | 스키마: 규칙을 데이터로 | | 14:10~14:16 | 2 · 개요 23장 | 스키마로 되는 것 · 안 되는 것 | | 14:16~14:22 | 3 · 개요 24장 | 격리 → 고침 → 다시 → 빼기 | | 14:22~14:28 | 4 · 개요 25장 | 새 스키마, 스키마 변경 = 규칙 변경 | | 14:28~14:40 | 5 · 6 · 개요 26~27장 | 품질 보고서의 정의, 데이터셋별 · 평균이 가리는 것 | | 14:40~14:50 | 7 · 8 · 확인 질문 | 하루 정리 표, 자주 하는 실수 | ### 6교시 Lab 03에서 이어서 하는 것 | 6교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 | | --- | --- | --- | --- | | Lab 03 예제 1 (15:00~15:14) | 1 · 2 | FAQ 검사에 직원 CSV(전), 스키마 검사 | `call my-faq-check requests/employees-new.json`, `my-work/check.json` (새로) → `lab03` 4/4, `QUALITY.md` 8절 | | Lab 03 예제 2 (15:14~15:22) | 3 | 고친 줄을 격리함에서 빼기 | `my-work/faq-import.json` 고치기, `test cases/lab03-fix.json -v` (전 3/4 → 4/4) | | Lab 03 예제 3 (15:22~15:28, 에이전트 없이) | 4 | 새 스키마 `ticket` | `call my-faq-check requests/tickets-new.json`, `test cases/lab03-schema.json -v` | | Lab 03 과제 1 (15:28~15:40) | 5 | 품질 보고서 | `my-work/quality-report.json` (새로), 테스트 `cases/lab03-challenge.json` → `QUALITY.md` 9절 | | Lab 03 과제 2 (15:40~15:50) | 6 | 검사도 기록 · 데이터셋별 | 두 파일 고치기, 테스트 `cases/lab03-challenge2.json` | ## 1. 스키마: 규칙을 데이터로 (6교시 Lab 03 예제 1) ### 일반 설명 > **생활 속 예 · 품목마다 다른 검수 카드.** 채소는 무른 곳, 고기는 온도와 라벨, 생선은 눈 색을 봅니다. 품목마다 주방장을 따로 두지 않고 품목마다 **검수 카드**를 만들어 두면, 한 사람이 카드를 바꿔 들고 무엇이든 검수합니다. 새 품목이 오면 카드를 한 장 더 만듭니다. **스키마(schema)는 데이터의 모양을 적은 데이터입니다.** 어떤 칸이 있고, 칸마다 타입은 무엇이고, 꼭 있어야 하는지, 범위는 어떤지. 검사 코드는 칸 이름을 모르고 **타입마다** 판정합니다: "이 칸은 `email`이니 메일 꼴인지", "이 칸은 `enum`이니 목록 안에 있는지". 그러면 데이터가 늘어도 검사 코드는 그대로이고 스키마만 늘어납니다. | 업계에서 | 같은 생각 | | --- | --- | | JSON Schema · OpenAPI | API 요청 · 응답의 모양 | | 데이터베이스 테이블 정의 | `NOT NULL` · `CHECK` · `UNIQUE` | | Great Expectations · Soda · dbt tests | 데이터셋마다 "기대값" 목록 | | pydantic · zod | 코드 안의 모델 (스키마를 코드로 씀) | **CSV.** 회사 데이터는 CSV로 많이 옵니다. 첫 줄이 열 이름이고, 값은 모두 글자입니다. 그래서 날짜 · 메일도 "글자의 꼴"로 판정하고, 줄 번호는 사람이 세는 번호(열 이름 줄을 빼고 1부터)로 돌려줍니다. ### 오늘 실습에서 Lab 01의 FAQ 검사에 인사팀의 직원 CSV를 보내면 400 (`items`가 없음) — FAQ의 칸 이름과 규칙이 Code에 박혀 있습니다 (Lab 03 예제 1 고치기 전). 스키마 검사 `my-check`는 `GET /schemas/employee`를 읽어: | 줄 | 값 | 코드 | 스키마의 규칙 | | --- | --- | --- | --- | | 3 | `minsu.kang@example` | `bad_format:email` | `email` | | 4 | `마케팅팀` | `not_allowed:dept` | `enum` 여섯 팀 | | 5 | `2026-13-01` | `bad_format:joined` | `date` — 실제 있는 날 | | 6 | 이름 빈칸 | `missing:name` | `required` | | 8 | 7째와 같은 메일 | `duplicate_in_batch` | `key: email` | - 9줄 중 **4줄** 통과. 빈 전화(2째)와 사무실 번호(7째)는 통과 — 선택 칸은 비면 형식을 보지 않습니다. 같은 워크플로우로 `schema: faq` B-3 → 6줄 중 5줄 (FAQ-305 `too_short:answer`). ## 2. 스키마로 되는 것 · 안 되는 것 (6교시 Lab 03 예제 1) ### 일반 설명 > **생활 속 예 · 검수 카드에 없는 칸.** 카드에는 무게 · 온도 · 라벨 칸이 있습니다. 하지만 "이 업체와 이미 계약한 품목인가"는 장부를 봐야 알고, "맛이 맞나"는 맛봐야 압니다. 카드가 할 일과 다른 사람이 할 일이 나뉩니다. **스키마는 모양을 넓게, Code · LLM은 그 데이터만의 규칙을 깊게.** 스키마로 거의 모든 데이터의 기본 모양(필수 · 길이 · 꼴 · 목록 · 날짜 · 중복)을 봅니다. 그러나 데이터마다 다른 규칙 — 답 안의 개인정보, 이미 있는 데이터와의 관계, 뜻 — 은 스키마의 타입으로 적기 어렵습니다. 둘 중 하나를 고르는 것이 아니라 층을 나눕니다. | 층 | 무엇 | 오늘 | | --- | --- | --- | | 스키마 | 모양 | `my-check` (모든 데이터셋) | | 데이터별 Code | 내용 규칙 · 이미 있는 데이터 | `my-faq-check` (FAQ만) | | LLM | 뜻 | `Judge` (FAQ 가져오기) | | 사람 | 고치기 · 기준 정하기 | 콘텐츠 담당자 · 팀 | ### 오늘 실습에서 FAQ를 `my-check`(`schema: faq`)로만 보면 B-3에서 Lab 01의 검사와 판정이 같았습니다 — B-3에는 개인정보 · 링크 · 이미 있는 FAQ가 없었기 때문입니다. B-2를 스키마로만 보면 FAQ-209(메일 주소)가 **통과**합니다. 그래서 FAQ는 Lab 01의 검사를 계속 쓰고, `QUALITY.md` 8절에 이 경계를 적습니다. ## 3. 되돌림 고리: 격리 → 고침 → 다시 → 빼기 (6교시 Lab 03 예제 2) ### 일반 설명 > **생활 속 예 · 보류 칸의 쪽지 떼기.** 업체가 라벨을 붙여 다시 가져온 고기를 냉장고에 넣었습니다. 그런데 보류 칸의 옛 상자에 "라벨 없음" 쪽지가 그대로라면, 다음 날 아침 주방장은 "아직 해결 안 됐네" 하고 또 전화합니다. **격리함은 "사람이 볼 일"의 목록입니다.** 해결된 줄이 남아 있으면 목록을 믿을 수 없습니다. 그래서 고쳐서 다시 넣은 줄은 격리함에서 뺍니다. 다만 **넣은 줄만** 뺍니다 — 고쳤는데 또 틀린 줄은 다시 사람에게 가야 합니다. 이 고리(격리 → 사람이 고침 → 다시 넣음 → 격리함 비우기)가 품질 관리의 한 바퀴이고, 76일차 DLQ의 replay와 같은 모양입니다. ```mermaid flowchart LR Q[("격리함")] -.->|사람이 고침| F(["고친 배치"]) --> I["가져오기
검사 · LLM · 넣기"] --> X["넣은 id만 뺌"] --> Q I --> L["품질 기록"] ``` ### 오늘 실습에서 | | 고치기 전 (Lab 02 과제 2까지) | 고친 뒤 (`Release fixed`) | | --- | --- | --- | | B-2-fix 두 줄 넣기 | 됨 · `score` 1 · FAQ 16 | 같음 | | 격리함 | **2** (해결된 FAQ-209 · 210이 남음) | **0** | | 품질 기록 | B-2 0.8 → B-2-fix 1 | 같음 | - 고친 배치의 이름이 `B-2-fix`라 "같은 배치 · 같은 id면 바꿈"에도 걸리지 않습니다. 격리함을 비우는 일은 저절로 되지 않습니다. ## 4. 설정으로 넓히기: 스키마만 바꾸면 (6교시 Lab 03 예제 3) ### 일반 설명 > **생활 속 예 · 생크림 카드 한 장.** 식당이 디저트를 시작해 생크림이 들어옵니다. 주방장을 새로 뽑지 않고 "생크림 카드"를 한 장 더 만들면 내일부터 같은 사람이 검수합니다. 단, 카드에 "냉장 3도 이하"를 잘못 "30도"로 쓰면 그날부터 상한 크림이 들어옵니다. **규칙을 데이터로 두면, 새 데이터셋은 설정 한 장입니다** (configuration over code). 워크플로우를 고쳐 push하지 않고 스키마 파일만 더합니다. 그만큼 **스키마 변경 = 규칙 변경**입니다. 스키마 파일도 저장소에 두고 리뷰 · 테스트를 거쳐 바꿉니다 (74일차 배포 관문과 같은 생각). 업계에서는 스키마 레지스트리(schema registry)로 버전을 관리합니다. ### 오늘 실습에서 데이터 API에 세 번째 스키마 `ticket`이 있습니다. 예제 1의 `my-check`를 고치지 않고 접수 건 9줄을 보내면 (Lab 03 예제 3): | | FAQ 검사 (`my-faq-check`) | 스키마 검사 (`my-check`, 그대로) | | --- | --- | --- | | 통과 | **0줄** — 칸이 달라 모두 `id_format` · `answer_missing` | **2줄** | | 이유 | 9줄 모두 같은 세 코드 + 빈 id 중복 8 | `T-12` · `urgent` · **2026-02-30** · 3자 제목 · `010-12-3456` · 중복 · 요청자 빈칸 — 줄마다 다름 | - 선택 칸 `contact`가 없는 9째 줄은 통과, 있는데 꼴이 틀린 6째 줄은 `bad_format:contact`. "없어도 된다"와 "아무거나 돼도 된다"는 다릅니다. ## 5. 품질 보고서: 품질도 정의가 먼저 (6교시 Lab 03 과제 1) ### 일반 설명 > **생활 속 예 · 월말 검수 일지 정리.** 사장님이 묻습니다: "이번 달 납품은 어땠어? 제일 나빴던 날은? 반품 이유는 뭐가 제일 많았어? 나아지고 있어?" 일지를 넘기며 말로 답하면 사람마다 답이 다릅니다. **품질 지표도 77일차 지표처럼 정의가 먼저입니다.** 업계에서는 데이터 품질 점수 · 데이터 품질 대시보드라고 부르고, 데이터 관측(data observability) 도구(Monte Carlo · Bigeye 등)가 같은 일을 합니다. 정해야 할 것은 이렇습니다. | 정해야 할 것 | 오늘의 정의 (AGENTS.md 3절) | 정하지 않으면 | | --- | --- | --- | | 거절을 평균에 넣나 | 넣음 (보내온 배치의 품질) | 0.6583과 0.8778 중 무엇인지 모름 | | 같으면 무엇을 | 먼저 나온 배치 · 코드 글자순 | 실행마다 순서가 바뀜 | | 추세 | 마지막 − 첫, ±0.05 | "좋아진 것 같다" | | 0건 | 평균 0, `null` 아님 | 다음 계산이 깨짐 | 숫자는 Code가 세고, 그 숫자를 **무엇과 무엇을 비교했는지와 함께** 말하는 것은 사람입니다. ### 오늘 실습에서 테스트가 B-1(거절) · B-2 · B-3 · B-2-fix를 차례로 가져온 기록 네 줄 (Lab 03 과제 1): | 칸 | 값 | | --- | --- | | `avg_score` | **0.6583** = (0 + 0.8 + 0.8333 + 1) / 4 | | `worst_batch` | B-1 · 거절 1 | | `top_codes` 1위 | `pii` 2 (B-1 FAQ-109 전화 + B-2 FAQ-209 메일) | | `trend` | 좋아짐 (0 → 1) | - `trend`의 함정: 13줄짜리 거절 배치와 고친 2줄짜리 배치를 같은 무게로 비교한 것입니다. 숫자는 정의대로 맞지만, 회의에서는 "첫 배치는 거절, 마지막은 고친 두 줄"을 함께 말합니다 (77일차 표본 크기와 같은 이야기). ## 6. 데이터셋별: 평균이 가리는 것 (6교시 Lab 03 과제 2) ### 일반 설명 > **생활 속 예 · 업체별 성적표.** 한 달 평균만 보면 "괜찮다"인데 업체별로 나누면 채소 가게는 늘 좋고 생선 가게만 절반이 반품입니다. 사장님이 전화할 곳은 생선 가게입니다. **같은 기록 모양을 쓰면 다른 워크플로우의 품질도 한 보고서에서 봅니다.** 검사(스키마 검사)도 가져오기와 같은 모양으로 기록을 남기면, 보고서는 고치지 않고도 직원 · 접수 건 데이터셋을 셉니다. 그리고 전체 평균은 나눠 봐야 할 것을 가립니다 — 77일차에 두 주 오류율이 월요일 09시를 가렸듯이. **추세는 같은 데이터셋 안에서.** 서로 다른 데이터셋(직원 → FAQ)을 이어 "좋아졌다"고 말하면 뜻이 없습니다. ### 오늘 실습에서 | 데이터셋 | 기록 | `avg_score` | `top_code` | | --- | --- | --- | --- | | employee | HR-1 | 0.4444 (4/9) | `bad_format:email` | | ticket | TK-1 | **0.2222** (2/9) → `worst_dataset` | `bad_format:contact` | | faq | B-3 | 0.8333 (5/6) | `answer_too_short` | | 전체 | | 0.5 | | - 전체 0.5는 "절반쯤 괜찮다"지만 나누면 접수 건이 가장 손볼 곳 — 헬프데스크 팀에 "옮기기 전에 일곱 줄을 고쳐 달라"고 말할 근거입니다. `top_code`는 1건씩 같은 수 중 글자순 첫째라는 것도 함께 말합니다. - 전체 `trend`가 `좋아짐`(0.4444 → 0.8333)으로 나오지만 직원과 FAQ를 비교한 것이라 버립니다. ## 7. 식당 주방으로 본 오늘 하루 | 오늘 | 식당 주방 | 단계 | | --- | --- | --- | | 데이터 품질 · 차원 | 아침 식재료 검수 | 1 | | 품질 규칙 · 오류 코드 | 검수 기준표 · 반품 사유 스티커 | 1 | | 경계 | 냉장고 온도 딱 5도 | 1 | | 이미 있는 데이터 · 오류와 경고 | 냉장고 먼저 열기 | 1 | | 고침 목록 · 데이터 계약 | 반품 사유서 | 1 | | 배치 게이트 · 격리함 | 트럭째 반품 · 보류 칸 | 2 | | LLM 검사 | 한 숟가락 맛보기 (상한 것은 안 맛봄) | 2 | | 기준을 바꾸면 | 반품 기준을 풀면 · 조이면 | 2 | | 품질 기록 · 알림 | 검수 일지 · 사장님 호출 | 2 | | LLM 호출 수 · 늘 같은가 | 맛보기 횟수 · 맛보는 사람 | 2 | | 스키마 · 새 스키마 | 품목별 검수 카드 · 생크림 카드 | 3 | | 되돌림 고리 | 보류 칸의 쪽지 떼기 | 3 | | 품질 보고서 · 데이터셋별 | 월말 일지 정리 · 업체별 성적표 | 3 | ## 8. 자주 하는 실수 | 실수 | 결과 | 바로잡기 | | --- | --- | --- | | 필드 이름을 검사 코드에 씀 | 새 데이터셋마다 워크플로우 수정 | 스키마의 `fields`를 돌며 타입마다 | | 날짜를 꼴(`YYYY-MM-DD`)만 봄 | 13월 · 2월 30일이 통과 | 실제 있는 날인지 | | 빈 선택 칸에 형식 검사 | 멀쩡한 줄이 `bad_format` | 비었으면 `required`만 | | 스키마만으로 FAQ를 검사 | 메일 주소 FAQ가 통과 | 내용 규칙은 Code로 따로 | | 고친 줄을 넣고 격리함을 그대로 | 해결된 일이 "할 일"로 남음 | 넣은 id만 뺌 | | 스키마를 리뷰 없이 바꿈 | 규칙이 몰래 바뀜 | 스키마도 저장소 · 리뷰 · 테스트 | | 거절을 평균에서 뺌 (말없이) | 품질이 좋아 보임 | 정의를 정하고 함께 말함 | | 전체 평균 · 섞은 추세만 봄 | 손볼 데이터셋이 가려짐 | 데이터셋별로, 추세는 그 안에서 | ## 확인 질문
1. 스키마 검사를 만들었으니 Lab 01의 FAQ 검사는 지워도 되나요? 아닙니다. 스키마는 모양(필수 · 길이 · 꼴 · 중복)을 보고, FAQ 검사는 FAQ만의 내용 규칙(답 안의 개인정보 · 바깥 링크)과 이미 있는 FAQ와의 관계를 봅니다. B-2를 스키마로만 보면 메일 주소 FAQ-209가 통과합니다. 모양은 스키마로 넓게, 내용은 Code로 깊게.
2. 고친 배치를 다시 넣었는데 격리함에서 빼지 않으면 무엇이 문제인가요? 넣기는 됐잖아요. 격리함은 "사람이 볼 일"의 목록입니다. 해결된 FAQ-209 · 210이 남아 있으면 다음 사람이 또 고치려 하거나, 목록 전체를 믿지 않게 됩니다. 넣은 줄만 빼고, 또 틀린 줄은 남겨 다시 사람에게 보냅니다.
3. 새 스키마를 더할 때 워크플로우를 고치지 않아도 된다면, 스키마는 누구나 바꿔도 되나요? 반대입니다. 워크플로우를 고치지 않고 판정이 바뀐다는 것은, 스키마 한 줄이 곧 규칙이라는 뜻입니다. `min: 4`를 `min: 40`으로 잘못 쓰면 그날부터 모든 접수 건이 막힙니다. 스키마도 코드처럼 저장소에 두고 리뷰 · 테스트를 거쳐 바꿉니다.
4. 품질 보고서의 평균은 0.6583입니다. 거절된 B-1을 빼면 0.8778인데, 어느 쪽을 보고해야 하나요? 무엇을 보고 싶은지에 따라 정하고, 정의에 적어 둡니다. 오늘은 콘텐츠 담당자가 **보내오는** 배치의 품질을 보므로 거절도 넣습니다(0.6583). "받은 배치의 품질"이 궁금하면 뺀 숫자(0.8778)를 따로 말합니다. 정하지 않고 그때그때 고르면 숫자를 비교할 수 없습니다.
5. 데이터셋별로 나누니 접수 건이 0.2222로 가장 나쁩니다. 그런데 기록이 한 배치씩뿐입니다. 이 결론을 믿어도 되나요? "이번 배치는 접수 건이 가장 나쁘다"까지는 말할 수 있습니다. "접수 건 데이터가 늘 나쁘다"는 배치가 쌓여야 말할 수 있습니다 (77일차 표본 크기). 9줄 중 7줄이 줄마다 다른 이유로 나쁘다는 것은 옮기기 전에 원본을 손봐야 한다는 신호로는 충분합니다.