# 1단계 · 품질 규칙
- 교시: 1교시 강의 (09:00~09:50) · 2교시 [Lab 01](../../Last3Mon/78/LAB-01-Q-QUALITY-RULES.md) 실습 5개 (10:00~10:50)
- 이 단계가 끝나면: 들어오는 데이터의 "나쁨"을 품질 차원으로 나눠 말하고, 그것을 **말로 정한 규칙과 오류 코드**로 바꾸고, 규칙을 Code로 확인하는 검사를 만들고, 경계(딱 기준선 · 공백 · 형식 · 비슷한 주소)에서 재 보고, 이미 있는 데이터와 맞춰 보고, 보낸 사람에게 고칠 것을 돌려줄 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, 78일차 판 데이터 API, FAQ 배치 B-1 13줄 · B-2 10줄 · 경계 배치 10줄, 11/01 실습을 5개로 늘린 뒤 정답 워크플로우를 학생 순서대로 1회). 규칙 판정은 누가 돌려도 같습니다. 출력 전부는 수업 뒤 공개되는 `Last3Mon/78/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: 이 문서에서 "Lab 01 예제 1"은 2교시 [Lab 01 문서](../../Last3Mon/78/LAB-01-Q-QUALITY-RULES.md)의 그 절입니다. 파일 경로(`cases/` · `requests/` · `my-work/` · `reference/`)는 실습 폴더 `~/aio-01d4-stu/Last3Mon/78` 기준입니다.
```mermaid
flowchart LR
A["1. 데이터 품질"] --> B["2. 규칙은 말이 먼저"] --> C["3. 오류 코드"] --> D["4. 규칙은 Code가"] --> E["5. 경계"]
E --> F["6. 이미 있는 데이터"] --> G["7. 보낸 사람에게"] --> H["8. 요청문으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 09:00~09:06 | 개요 1~6장 | 오늘 하루, 같은 배치 다른 판정, 실습 15개 |
| 09:06~09:13 | 1 · 개요 7장 | 데이터 품질의 차원 |
| 09:13~09:20 | 2 · 3 · 개요 8~9장 | 규칙은 말이 먼저, 오류 코드 |
| 09:20~09:26 | 4 · 개요 10장 | 규칙은 Code가 |
| 09:26~09:33 | 5 · 개요 11장 | 경계 |
| 09:33~09:43 | 6 · 7 · 개요 12~14장 | 이미 있는 데이터 · 오류와 경고, 고침 목록 · 데이터 계약 |
| 09:43~09:50 | 8 · 9 · 확인 질문 | 요청문, 자주 하는 실수 |
### 2교시 Lab 01에서 이어서 하는 것
| 2교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| 시작 (10:00) | — | 품질 규칙표 복사 | `cp templates/QUALITY-RULES-TEMPLATE.md my-work/QUALITY.md` (오늘의 판정을 모두 여기에) |
| Lab 01 예제 1 (10:00~10:10, 에이전트 없이) | 1 · 2 | B-1을 눈으로 검사, 같은 배치를 기준 검사에 | `call base-faq-check requests/faq-batch-1.json` → `QUALITY.md` 1절 |
| Lab 01 예제 2 (10:10~10:25) | 2 · 3 · 4 | 규칙대로 판정하는 내 검사 | `my-work/faq-check.json` (새로), 테스트 `cases/lab01.json` (전: `--webhook base-faq-check`) |
| Lab 01 예제 3 (10:25~10:32, 에이전트 없이) | 5 | 경계에서 두 검사를 재 보기 | `test cases/lab01-edges.json -v` · `--webhook base-faq-check` → `QUALITY.md` 3절 |
| Lab 01 과제 1 (10:32~10:40) | 6 | 이미 있는 FAQ와 맞춰 보기 | 테스트 `cases/lab01-challenge.json` |
| Lab 01 과제 2 (10:40~10:50) | 7 | 보낸 사람에게 돌려줄 고침 목록 | 테스트 `cases/lab01-challenge2.json` → `QUALITY.md` 4절 |
## 1. 데이터 품질: 무엇이 "나쁜" 데이터인가 (2교시 Lab 01 예제 1)
### 일반 설명
> **생활 속 예 · 식당 주방의 아침 검수.** 아침마다 납품 트럭이 오면 주방장이 상자를 엽니다. 무른 채소, 라벨 없는 고기, 주문하지 않은 물건, 유통기한이 지난 우유를 골라냅니다. "상자가 비어 있지 않으면 받는다"로는 식당이 망합니다. 워크플로우가 믿고 쓰는 데이터(오늘은 FAQ)가 이 식재료이고, 오늘은 검수를 자동으로 하는 날입니다.
**데이터 품질(data quality)은 "이 데이터를 믿고 써도 되나"입니다.** 워크플로우 · 보고서 · 챗봇은 모두 어딘가의 데이터를 읽고 일합니다. 읽는 데이터가 틀리면 프로그램은 멈추지 않고 틀린 결과를 **자신 있게** 냅니다. 답을 못 하는 것보다 틀린 답이 더 나쁜 까닭입니다. 그래서 데이터를 쓰기 전에 "좋은가"를 먼저 묻습니다.
"좋다 · 나쁘다"는 막연하므로 업계에서는 나쁨을 몇 가지 **차원(dimension)**으로 나눠 말합니다. 차원으로 나누면 무엇을 검사해야 할지 빠뜨리지 않고, 문제가 생겼을 때 어떤 종류의 문제인지 이름을 붙일 수 있습니다.
| 차원 | 묻는 것 | 흔한 예 |
| --- | --- | --- |
| 완전성 (completeness) | 꼭 있어야 할 값이 있나 | 이름이 빈 회원, 주소가 빠진 주문 |
| 유효성 (validity) | 형식 · 길이 · 범위가 맞나 | `@`가 없는 메일, 2월 30일, 음수 나이 |
| 고유성 (uniqueness) | 겹치지 않나 | 같은 회원 번호가 두 명 |
| 일관성 (consistency) | 다른 데이터와 맞나 | 없는 부서 코드, 이미 있는 상품 번호 |
| 정확성 (accuracy) | 내용이 사실과 맞나 | 질문과 상관없는 답, 틀린 가격 |
| 적시성 (timeliness) | 지금도 맞나 | 바뀌기 전 규정 · 지난 가격 |
사람에게 그대로 보여 주는 데이터(고객 안내문 · FAQ · 공지)에는 **안전**(개인정보 · 위험한 링크)도 품질에 들어갑니다. 업계에서는 Great Expectations · Soda · dbt tests · Deequ 같은 데이터 검사 도구를 쓰고, 데이터를 옮기고 쌓는 과정의 한 단계로 검사를 둡니다.
**ETL과 데이터 파이프라인.** 회사의 데이터는 한곳에서 생기지 않습니다. 주문은 쇼핑몰에서, 회원은 회원 시스템에서, 문의는 헬프데스크에서 생깁니다. 이것을 한곳(데이터 웨어하우스 · 검색 색인 · FAQ 저장소)에 모아 쓰려면 세 단계를 거칩니다.
| 단계 | 영어 | 하는 일 | 예 |
| --- | --- | --- | --- |
| 꺼내기 | **E**xtract | 원래 있던 곳에서 데이터를 읽어 옴 | 콘텐츠 담당자가 보낸 FAQ 파일을 읽음 |
| 바꾸기 | **T**ransform | 쓸 모양으로 다듬고, **검사하고**, 필요한 칸을 더함 | 앞뒤 공백 정리, 규칙 검사, 나쁜 줄 걸러 내기 |
| 싣기 | **L**oad | 쓰는 곳에 넣음 | 검사를 통과한 FAQ만 저장소에 넣음 |
세 글자를 따서 **ETL**이라고 부르고, 이렇게 데이터가 정해진 단계를 차례로 흘러가게 만든 것을 **데이터 파이프라인**(data pipeline)이라고 부릅니다. 품질 검사는 대개 바꾸기(T) 단계, 즉 **싣기 전에** 둡니다. 한번 실린 나쁜 데이터는 이미 누군가 읽고 쓰기 시작하기 때문입니다. 오늘 만드는 n8n 워크플로우도 작은 데이터 파이프라인입니다: 배치를 받고(E) → 검사하고(T) → 통과한 것만 넣습니다(L).
### 오늘 실습에서
B-1 13줄을 AGENTS.md 3절 규칙표를 들고 눈으로 읽으면 (Lab 01 예제 1):
| 판정 | 줄 | 차원 |
| --- | --- | --- |
| 분명히 나쁨 (8) | FAQ-103 빈 제목 · FAQ-105 빈 답 · FAQ-108 빈 키워드 | 완전성 |
| | FAQ-10 id 두 자리 · FAQ-106 300자 넘는 답 | 유효성 |
| | 둘째 FAQ-102 | 고유성 |
| | FAQ-109 개인 휴대전화 · FAQ-110 단축 링크 | 안전 |
| 판단 보류 (3) | FAQ-003 · FAQ-112 (이미 있는 FAQ와 겹침), FAQ-113 (답이 다른 이야기) | 일관성 · 정확성 |
| 좋음 (2) | FAQ-101 · FAQ-102 | |
- 같은 배치를 팀의 기준 검사에 보내면 **10줄 통과** — 빈 값 셋만 잡았습니다 (2절).
## 2. 규칙은 말이 먼저: 같은 배치, 다른 판정 (2교시 Lab 01 예제 1 · 2)
### 일반 설명
> **생활 속 예 · 벽에 붙은 검수 기준표.** 주방장마다 보는 눈이 다르면 같은 상자가 어떤 날은 받히고 어떤 날은 반품됩니다. 그래서 식당은 "라벨 없는 고기는 반품, 냉장품은 5도 이하"처럼 기준표를 벽에 붙입니다. 사람이 바뀌어도 판정이 같아지는 것은 기준표 덕분입니다.
**검사 = 규칙 + 판정.** "나쁜 데이터를 걸러 줘"라는 말만으로는 판정이 하나로 정해지지 않습니다. 사람마다 · 프로그램마다 "나쁨"을 다르게 읽기 때문입니다. 77일차에 "지표는 정의가 먼저"였던 것처럼, 검사는 **규칙이 먼저**입니다. 규칙은 적어도 다음을 말로 정해 둬야 합니다.
| 정해야 할 것 | 예 | 정하지 않으면 |
| --- | --- | --- |
| 무엇을 보나 | 어느 칸을 검사하나 | 한두 칸만 보고 나머지는 지나감 |
| 형식 · 범위 | "번호는 영문 3자 + 숫자 3자", "설명은 10~300자" | 사람마다 다른 기준 |
| 경계 | "40자를 **넘으면**", "공백만 있으면 빈 값" | 딱 기준선의 값이 날마다 다르게 판정됨 (5절) |
| 판정의 단위 | 줄마다인가, 묶음 전체인가 | "이 파일은 나쁘다"로 끝나 아무도 못 고침 |
| 막을 것 · 알릴 것 | 오류와 경고 (6절) | 모든 것을 막거나 모든 것을 통과 |
규칙이 말로 정해져 있지 않으면, 검사를 만드는 사람(또는 CLI 에이전트)이 **자기 방식으로** 판정합니다. 그 판정이 계산상 틀린 것은 아니어도, 팀이 원하는 판정과 다를 수 있습니다.
### 오늘 실습에서
| | 기준 검사 (`base-faq-check`) | 내 검사 (`my-faq-check`, Lab 01 예제 2) |
| --- | --- | --- |
| 규칙 | 빈 제목 · 빈 답 · 빈 키워드 | AGENTS.md 3절 열 가지 |
| B-1 | 13줄 중 **10줄** 통과 | **5줄** 통과 — 눈으로 찾은 8줄과 같은 줄이 나쁨 |
| B-2 | 10줄 모두 통과 | 9줄 (FAQ-209 메일 주소), 사내 링크 FAQ-207은 통과 |
| `items`가 목록이 아님 | **500** (Code가 터짐) | **400** `{"error": "items must be a list"}` |
| `cases/lab01.json` | **1/5** | **5/5** |
- 기준이 틀린 계산을 한 것이 아니라 **규칙이 셋뿐**이었습니다. "버그 찾기"가 아니라 "규칙을 정해 그대로 대기"가 고치는 일입니다.
## 3. 오류 코드: 기계와 사람이 함께 읽는다 (2교시 Lab 01 예제 2)
### 일반 설명
> **생활 속 예 · 반품 사유 스티커.** 주방장은 반품 상자에 "온도" · "라벨"처럼 짧은 스티커를 붙입니다. 월말에 스티커를 세면 "이번 달 반품의 절반이 온도"가 보입니다. 납품업체에는 스티커를 풀어 쓴 사유서를 보냅니다. 짧은 표시와 긴 설명을 나눠 두는 것입니다.
**오류 코드(error code)는 "무엇이 왜 잘못됐는지"를 짧은 이름 하나로 정해 둔 것입니다.** 검사 결과가 "나쁘다" 한 마디뿐이면 아무도 고칠 수 없습니다. "몇째 줄의 어느 칸이 어떤 규칙에 걸렸는지"가 있어야 고칩니다. 그 "어떤 규칙"을 날마다 다른 문장으로 쓰지 않고, 규칙마다 **이름 하나**를 정해 붙이는 것이 오류 코드입니다.
우리가 이미 아는 예가 많습니다. 웹의 HTTP 상태 코드(404 = 없음, 500 = 서버 오류), 신용카드 결제 거절 코드, 자동차 계기판의 고장 코드가 모두 같은 생각입니다. 사람이 읽기에는 짧지만, 기계는 이 이름으로 **세고 · 비교하고 · 갈래를 나눕니다.**
| 좋은 오류 코드의 성질 | 까닭 |
| --- | --- |
| 짧고 바뀌지 않는 이름 (보통 영어) | 프로그램이 이름으로 셈 · 비교. 문장은 고쳐 쓸 때마다 다른 값이 되어 셀 수 없음 |
| 규칙 하나에 이름 하나 | "가장 흔한 문제는 무엇인가"를 이름별로 셀 수 있음 |
| 한 줄에 여럿이 붙을 수 있음 | 이름도 비고 메일도 틀리면 둘 다 알려야 한 번에 고침 |
| 사람이 읽는 말과 나눔 | 코드는 기계용, 설명 문장은 사람용 (7절). 문장을 바꿔도 통계는 그대로 |
| 판정과 나눔 | "통과 = 붙은 코드가 하나도 없음"으로 정하면 규칙을 더해도 판정 방법은 그대로 |
이름을 정할 때는 "무엇이(칸) · 어떻게(비어 있음 · 너무 김 · 형식이 잘못됨)"가 보이게 짓는 것이 흔합니다. 예를 들어 "제목이 비어 있음", "메일 형식이 잘못됨", "개인정보가 들어 있음" 같은 규칙마다 영어 이름을 하나씩 붙입니다. 이름이 정해지면 검사 결과를 다른 프로그램(가져오기 · 보고서 · 알림)이 그대로 이어 쓸 수 있습니다.
### 오늘 실습에서
B-1을 내 검사로 보면 `codes`에 여덟 가지가 1건씩입니다: `title_missing` · `id_format` · `answer_missing` · `answer_too_long` · `duplicate_in_batch` · `keywords_missing` · `pii` · `external_link`. 결과의 `rows`는 들어온 줄과 같은 순서 · 같은 수입니다 (AGENTS.md 4절) — 테스트가 `rows.8.errors`처럼 줄 번호로 봅니다.
## 4. 규칙은 Code가 확인한다 (2교시 Lab 01 예제 2)
### 일반 설명
> **생활 속 예 · 온도는 온도계로.** 냉장고 온도를 주방장의 손등으로 재지 않습니다. 온도계는 누가 들어도 같은 숫자를 줍니다. 대신 "이 소스, 맛이 맞나?"는 온도계로 잴 수 없어 사람이 맛봅니다.
**정답이 정해진 판정은 Code(프로그램)로 합니다.** 빈 값인가, 몇 글자인가, 정해진 형식(정규식)에 맞나, 같은 값이 두 번 나오나 — 이런 판정은 같은 데이터에 **언제나 같은** 답이 나와야 합니다. 프로그램은 그렇게 하고, 빠르고, 돈이 들지 않으며, 왜 그렇게 판정했는지 보여 줄 수 있습니다.
같은 일을 LLM에게 "이 데이터 괜찮아?"라고 물으면 그럴듯한 답이 오지만, 같은 데이터에 날마다 다른 판정이 나올 수 있고 줄마다 시간과 비용이 듭니다 (75일차). LLM은 **규칙으로 쓸 수 없는 판정**(이 답이 질문에 맞는 내용인가)에만 씁니다 — 2단계.
| 판정 | 누가 | 까닭 |
| --- | --- | --- |
| 빈 값 · 길이 · 형식 · 중복 · 전화번호 · 링크 | Code | 정답이 정해져 있음 |
| 이미 있는 데이터와 겹치나 | Code + 저장소 조회 (6절) | 저장소에 물어보면 정답이 나옴 |
| 뜻 (내용이 맞나) | LLM (2단계) | 정해진 규칙으로 쓰기 어려움 |
| 고치기 | 사람 | 무엇이 맞는지는 보낸 사람이 앎 |
하나 더: 검사를 시작하기 **전에** 들어온 데이터의 모양부터 봅니다. 목록이 와야 할 자리에 글자가 오면 "보낸 쪽이 잘못 보냈다(400)"고 바로 돌려보내야 합니다. 그대로 검사 코드에 넣으면 코드가 터져 "서버가 고장 났다(500)"로 보입니다.
### 오늘 실습에서
내 검사는 `Check webhook` → `Is a list?`(IF) → `Check rows`(Code 하나) → `Send result`. Code에 들어가기 **전에** 모양을 봅니다: `items`가 목록이 아니면 400으로 돌려보내야 부른 쪽이 "내 본문이 틀렸다"를 압니다. 기준 검사처럼 Code 안에서 터지면 500 — "서버가 고장났다"로 읽힙니다.
## 5. 경계: 규칙은 기준선에서 시험받는다 (2교시 Lab 01 예제 3)
### 일반 설명
> **생활 속 예 · 냉장고 온도 딱 5도.** 기준표에 "냉장품 5도 이하"라고 적혀 있습니다. 4도와 8도는 누구나 판정합니다. 하지만 딱 5도는? "이하"라는 한 글자가 정합니다. 온도계 없이 "차갑네"로 판정하면 5.1도도 받습니다.
**경계(edge, boundary)는 규칙의 기준선 바로 위 · 아래 · 딱 그 위치의 값입니다.** 검사의 실수는 대부분 경계에서 나옵니다. 아주 나쁜 데이터(텅 빈 값, 엄청 긴 글)만 시험하면 검사가 "잘 된다"고 믿게 되지만, 실제로 헷갈리는 것은 기준선 근처입니다. 소프트웨어 테스트에서는 이것을 **경계값 분석(boundary value analysis)**이라고 부르고, 기준선의 바로 앞 · 위 · 뒤 값을 일부러 시험합니다.
경계를 정확히 판정하려면 규칙에 경계의 **말**이 들어가야 합니다.
| 경계 | 정해야 할 말 | 말이 없으면 |
| --- | --- | --- |
| 딱 기준 길이 | "넘으면"인가 "이상이면"인가 | 딱 40자인 값이 어떤 날은 막히고 어떤 날은 통과 |
| 공백만 있는 값 | "앞뒤 공백을 뺀 뒤 판정" | 띄어쓰기만 넣은 칸이 "값이 있음"으로 통과 |
| 형식의 변형 | "하이픈 · 점 · 띄어쓰기가 없어도 같은 것" | `010-1234-5678`은 잡고 `01012345678`은 놓침 |
| 비슷하게 생긴 값 | "**정확히** 이 주소" | `intra.example.com.evil.io`처럼 앞부분만 같은 피싱 주소가 사내 주소로 통과 (73일차) |
경계를 시험 데이터로 만들어 두면, 규칙을 고칠 때마다 다시 돌려 경계가 그대로인지 확인할 수 있습니다.
### 오늘 실습에서
경계 배치(`requests/faq-edges.json`, 10줄)를 두 검사에 (Lab 01 예제 3):
| 경계 | 내 검사 | 기준 |
| --- | --- | --- |
| 40자 · 41자 제목 | ✔ 통과 · `title_too_long` | ✘ 둘 다 통과 |
| 공백만 있는 제목 | ✔ `title_missing` | ✘ 통과 |
| 10자 · 9자 답 | ✔ 통과 · `answer_too_short` | ✘ 둘 다 통과 |
| `01012345678` · `02-123-4567` | ✔ `pii` · 통과 | ✘ 둘 다 통과 |
| 사내 주소 · `evil.io` | ✔ 통과 · `external_link` | ✘ 둘 다 통과 |
| `faq-410` | ✔ `id_format` | ✘ 통과 |
- **6/6 대 0/6.** 강사의 첫 판도 `evil.io`를 통과시켰습니다 — 정규식이 "뒤에 `intra.example.com`이 오나"만 보고 거기서 **끝나는지**는 보지 않았기 때문입니다. 테스트에 경계 줄을 넣어 두면 이런 실수를 미리 잡습니다.
## 6. 이미 있는 데이터: 오류와 경고 (2교시 Lab 01 과제 1)
### 일반 설명
> **생활 속 예 · 냉장고를 먼저 연다.** 납품 상자만 보면 멀쩡한 우유입니다. 그런데 냉장고에 어제 받은 우유가 그대로라면 같은 칸에 넣으면 안 됩니다. 반면 "같은 회사 제품"인 것은 흔하고 괜찮습니다 — 알아만 두면 됩니다.
**검사는 들어오는 데이터만이 아니라, 이미 있는 데이터와의 관계도 봅니다.** 들어온 줄 하나만 보면 멀쩡해도, 저장소에 같은 번호가 이미 있으면 덮어쓰거나 두 벌이 됩니다. 같은 제목이 둘이면 검색이 어느 쪽을 보여 줄지 모릅니다. 데이터베이스에서는 이것을 **유일 제약**(unique constraint, 같은 값은 하나만)과 **참조 무결성**(referential integrity, 가리키는 대상이 실제로 있어야 함)이라고 부릅니다.
그리고 **막을 것과 알릴 것을 나눕니다.** 모든 문제를 막으면 아무것도 들어가지 못하고, 모두 통과시키면 검사가 의미 없습니다.
| | 오류 (error) | 경고 (warning) |
| --- | --- | --- |
| 뜻 | 들어가면 안 됨 | 들어가도 되지만 사람이 알아야 함 |
| 판정 | 그 줄은 통과하지 못함 | 통과 여부에 영향 없음 |
| 흔한 예 | 이미 있는 번호, 같은 제목 | 다른 글과 같은 키워드, 평소보다 긴 글 |
무엇을 오류로, 무엇을 경고로 둘지도 규칙의 일부라 팀이 정해 적어 둡니다.
### 오늘 실습에서
검사 앞에 `Get FAQ`(`GET /faq-all`, 처음 6개 + 오늘 더한 것)를 두면 (Lab 01 과제 1): FAQ-003 `exists_id`, FAQ-112 `same_title` + 경고 `keyword_used:vpn`, B-1 통과 5 → **3**줄. 테스트가 막 더한 FAQ-201도 `exists_id` — "이미 있는 데이터"는 파일이 아니라 **지금** 데이터 API에 있는 것입니다.
## 7. 보낸 사람에게: 고침 목록 · 데이터 계약 (2교시 Lab 01 과제 2)
### 일반 설명
> **생활 속 예 · 반품 사유서.** "안 받아요"만 말하면 납품업체는 무엇이 잘못됐는지 모릅니다. "3번 상자 · 라벨 없음, 7번 상자 · 냉장 온도 9도"라고 적어 주면 다음 납품이 나아집니다.
**검사 결과는 기계와 사람이 함께 읽습니다.** 다음 프로그램(가져오기 · 보고서)은 줄마다의 판정과 오류 코드를 읽고, 데이터를 보낸 사람은 "무엇을 어떻게 고치면 되는지"를 읽습니다. 오류 코드마다 고칠 말이 정해져 있으면 표 하나로 충분합니다 ("제목이 비어 있음 → 제목을 써 주세요"). 이것을 LLM으로 만들면 같은 문제에 날마다 다른 말이 나오고 비용이 듭니다.
업계에서는 보내는 쪽과 받는 쪽이 데이터의 모양과 규칙을 **미리 약속**하는 것을 **데이터 계약**(data contract)이라고 부릅니다. 약속을 어긴 데이터는 받지 않고, **이유와 함께** 돌려보냅니다. 돌려보낸 이유가 분명해야 보낸 사람이 배우고 다음 데이터가 나아집니다.
| 고침 목록에 들어갈 것 | 까닭 |
| --- | --- |
| 몇째 줄인지 (사람이 세는 번호, 1부터) | 보낸 사람이 자기 파일에서 바로 찾음 |
| 무엇이 잘못됐는지 (코드) | 기계가 셈 · 분류 |
| 어떻게 고치는지 (사람의 말) | 보낸 사람이 바로 고침 |
| 한 줄 요약 ("몇 줄 중 몇 줄 통과") | 한눈에 상황 파악 |
그리고 워크플로우는 데이터를 **고쳐 주지 않습니다.** 빈 제목을 짐작해 채우면 틀린 내용이 "검사를 통과한 데이터"로 들어가고 아무도 그 사실을 모릅니다. 공백 정리처럼 뜻이 바뀌지 않는 **다듬기**와 내용을 바꾸는 **고치기**는 다릅니다.
### 오늘 실습에서
결과에 `fix_list` · `message`를 더하면 (Lab 01 과제 2): B-1 → `"B-1: 13줄 중 3줄 통과, 10줄 고칠 것"`, 고칠 줄 10개(3째 FAQ-103 "제목을 써 주세요" … 12째 FAQ-112 "같은 제목의 FAQ가 이미 있습니다 …"), B-2 → FAQ-209 하나. `row`는 1부터, 줄 순서 그대로 — 보낸 사람이 자기 파일에서 바로 찾게.
## 8. 요청문(프롬프트)으로 에이전트에게 맡기기
검사 요청문은 "나쁜 FAQ를 걸러 줘"가 아니라 **규칙**을 적습니다 (AGENTS.md 1절: 에이전트는 먼저 "무엇을 보고 어떤 코드를 붙이는지"를 표로 보여 줌).
| 넣을 것 | 예 |
| --- | --- |
| 파일 · 웹훅 · 이름 | "`my-work/faq-check.json`, 웹훅 `my-faq-check`, 이름 `78 FAQ check (my)`" |
| 모양이 틀린 본문 | "`items`가 목록이 아니면 400" |
| 규칙과 경계 | "AGENTS.md 3절 규칙대로, 공백만 있는 값은 빈 값, 글자 수는 앞뒤 공백을 뺀 뒤" |
| 헷갈리는 규칙 | "pii는 하이픈 없는 휴대전화도, external_link는 호스트가 정확히 intra.example.com이 아니면" |
| 결과 모양 · 하지 않을 것 | "`rows`는 들어온 순서 그대로, LLM은 쓰지 않는다" |
| 기대 결과 | "`cases/lab01.json`, 앞의 테스트도 그대로" |
## 9. 자주 하는 실수
| 실수 | 결과 | 바로잡기 |
| --- | --- | --- |
| "나쁜 FAQ 걸러 줘"만 씀 | 에이전트마다 다른 규칙 | 규칙 표를 요청문에 |
| 공백을 값으로 봄 | `" "` 제목이 통과 | 앞뒤 공백을 뺀 뒤 판정 |
| `>`와 `>=`를 섞음 | 딱 40자 제목이 막힘 | "넘으면"을 말로 |
| 전화번호를 `010-` 형식만 찾음 | `01012345678`이 나감 | 구분자가 없어도 되는 형식 |
| 주소를 "앞이 같나"로 봄 | `evil.io`가 통과 | 호스트가 정확히 · 끝나는지까지 |
| Code 안에서 본문 모양을 봄 | 500 | Code 앞에 IF → 400 |
| 겹치는 키워드도 오류로 | 넣을 수 있는 FAQ가 없음 | 오류와 경고를 나눔 |
| 나쁜 줄을 고쳐서 넣음 | 틀린 데이터가 "통과"로 | 고침 목록을 돌려주고 사람이 고침 |
| 규칙을 LLM에게 판정시킴 | 날마다 다르고 비쌈 | 규칙은 Code, 뜻만 LLM |
## 확인 질문
1. 기준 검사는 B-1을 10줄 통과, 내 검사는 5줄 통과라고 합니다. 기준 검사가 "틀린" 건가요?
계산은 맞습니다. 기준은 빈 값 세 가지만 보는 **규칙**으로 판정했고, 그 규칙대로라면 10줄이 통과입니다. 문제는 그 규칙으로 FAQ에 개인 전화번호와 단축 링크가 들어간다는 것입니다. 무엇을 막을지는 팀이 정하고(AGENTS.md 3절), 모든 검사가 그 규칙을 씁니다.
2. 오류 이유를 "제목이 비었어요" 같은 문장 대신 `title_missing` 같은 코드로 붙이는 까닭은?
코드는 짧고 바뀌지 않아 기계가 셀 수 있습니다 (`codes`, 품질 보고서의 가장 흔한 문제). 문장은 사람마다 · 날마다 달라 셀 수 없습니다. 사람에게 보여 줄 말은 코드마다 표로 정해 따로 붙입니다 (과제 2).
3. 경계 테스트에서 기준 검사가 0/6이었습니다. 보통 배치(B-1)에서는 1/5를 맞혔는데, 경계에서는 왜 하나도 못 맞혔나요?
경계 배치의 줄은 빈 값이 아닌 것들입니다 (41자 · 공백 · 하이픈 없는 번호 · 비슷한 주소 · 소문자 id). 기준이 가진 규칙(빈 값)에 걸릴 줄이 하나도 없으니 모두 통과입니다. 보통 배치에서 맞힌 1/5는 "빈 값" 케이스가 마침 기준의 규칙과 같았기 때문입니다.
4. 이미 쓰는 키워드(`vpn`)를 오류가 아니라 경고로 둔 까닭은?
키워드가 겹치는 것은 흔하고 나쁘지 않습니다 (VPN 관련 FAQ가 여럿일 수 있음). 오류로 막으면 새 FAQ를 거의 넣을 수 없습니다. 반면 같은 id · 같은 제목은 덮어쓰거나 검색이 헷갈리므로 막습니다. 막을 것과 알릴 것을 나누는 것도 규칙입니다.
5. 고침 목록을 만들 수 있다면, 워크플로우가 아예 고쳐서 넣으면 더 편하지 않나요?
빈 제목을 워크플로우가 짐작해 채우면, 틀린 내용이 "검사를 통과한 FAQ"로 들어가고 아무도 모릅니다. 보낸 사람도 무엇이 틀렸는지 배우지 못해 다음 배치도 같습니다. 워크플로우는 막고 알리고, 고치는 것은 사람입니다 (오늘의 원칙). 공백 정리처럼 뜻이 바뀌지 않는 "다듬기"와 내용을 바꾸는 "고치기"는 다릅니다.