queries.yaml v0.1 23 case → v0.2 schema swap: - 7 카테고리 (standards / korean_only / english_only / mixed / exam / ocr_derived / failure_expected) - language / ocr_derived / failure_expected / graded_relevance 컬럼 추가 - v0.1 호환 보존 (legacy_category + relevant_ids + top3_ids) - 신규 28 case (50+ 목표) 는 후속 PR-Eval-V0_2-Baseline-Analysis run_eval.py 확장: - graded_ndcg_at_k / graded_recall_at_k 함수 추가 - Query / QueryResult dataclass 확장 (v0.2 컬럼) - load_queries v0.1 fallback (top3 → grade 3, 나머지 → grade 2) - --eval-version v0.1/v0.2/both flag (default both) - print_summary 의 by_language / by_ocr_derived 집계 추가 - write_csv 의 graded 컬럼 추가 README.md 신규: - graded 등급 정의 (0~3) + 카테고리 정의 (7개) - v0.2 schema 컬럼 + 신규 case 작성 가이드 - v0.1 호환성 + CLI 사용 예 + baseline 박제 정책 Phase 1 plan: ~/.claude/plans/phase-1-graded-eval-v0-2.md Parent: ~/.claude/plans/peppy-hugging-nest.md § Phase 1 본 PR closure: schema + harness + README. 신규 28 case + baseline 박제 + 약점 분석 (embedding-sensitive failure pattern 4 카테고리 식별) 은 후속 PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document Server 검색 평가셋 (search_eval)
Phase 1 산출물 — graded relevance baseline. peppy-hugging-nest.md Phase 1. Phase 2 모델 점검 (embedding / reranker / OCR-marker pipeline / STT) 의 객관 측정 도구.
개요
tests/search_eval/queries.yaml 은 Document Server 의 검색 품질을 정량 측정하기 위한 평가셋이다. 두 버전 존재:
- v0.1 (2026-04-07) — binary relevance (relevant_ids set + top3_ids 보조). 23 case, 9 카테고리. corpus 753 documents 기준.
- v0.2 (2026-05-23) — graded relevance (0~3 등급) + 사용자 권장 7 카테고리 + language / ocr_derived metadata. v0.1 호환 유지 (legacy_category + relevant_ids + top3_ids 컬럼 보존).
본 PR (PR-Eval-V0_2-Schema-Harness) = v0.1 23 case 의 v0.2 schema swap + run_eval graded harness 만. 신규 28 case (50+ 목표) + baseline 박제 + 약점 분석은 후속 PR (PR-Eval-V0_2-Baseline-Analysis).
Graded relevance 등급 (4단계)
| Grade | 의미 | 기준 |
|---|---|---|
| 3 | highly relevant (top-3 강제) | direct match — 본법/본 chapter/본 article. 검색 결과 top-3 에 반드시 포함돼야 함. |
| 2 | relevant (정답 후보) | 보조 정답 — 시행령/규칙/관련 chapter. top-10 안 = OK. |
| 1 | marginal | 약한 관련 — 같은 법령군이지만 다른 chapter, 같은 주제지만 다른 문서 유형. top-10 안 = 가점, 밖 = 무감점. |
| 0 | irrelevant | 무관 — 검색 결과 잡히면 감점 후보 (현재는 표시만, 감점 미구현). |
failure_expected 케이스는 graded_relevance 비움 ({}). 검색 결과 0건 = correct.
Graded score 계산식 (run_eval.py)
# graded NDCG@k — gain = 2^grade - 1
dcg = sum((2 ** grade - 1) / log2(rank + 1) for rank, doc_id ...)
idcg = sum((2 ** g - 1) / log2(rank + 1) for rank, g in sorted(grades, desc)[:k] ...)
ndcg = dcg / idcg
# graded recall@k — grade >= threshold 만 정답
recall_t2 = |{returned[:k]} ∩ {grade>=2}| / |{grade>=2}|
recall_t3 = |{returned[:k]} ∩ {grade>=3}| / |{grade>=3}|
카테고리 (7개)
| Category | 의미 | 측정 목적 |
|---|---|---|
| standards | 법령/규칙/기준 (산업안전보건법, ASME, KGS, NIST 등) | exact_keyword + structured doc lookup |
| korean_only | 한국어 자연어 query → 한국어 doc | base semantic_search 품질 |
| english_only | 영어 자연어 query → 영어 doc | base 영어 자료 검색 |
| mixed | 한국어+영어 혼합 OR ko query → en doc OR vice versa | crosslingual 능력 (현재 0.53 미달 root cause 검증) |
| exam | 가스기사 study 도메인 (study_questions 기반) |
시험 문제 → 학습 자료 매칭 |
| ocr_derived | marker/OCR pipeline 통한 scanned PDF chunk 검색 | Phase 2C 입력 — OCR 추출 품질이 검색에 미치는 영향 |
| failure_expected | 결과 0건 expected | precision 측정 (no-result correctness) |
legacy_category 컬럼으로 v0.1 9 카테고리 호환 보존 (분석 시 옛 분포 재현 가능).
v0.2 schema 컬럼
- id: kw_001
query: "산업안전보건법 제6장"
category: standards # v0.2 7 카테고리 중 하나
legacy_category: exact_keyword # v0.1 카테고리 보존
intent: fact_lookup # semantic_search / fact_lookup / filter_browse
domain_hint: document # document / news / mixed
language: ko # ko / en / mixed
ocr_derived: false # marker/OCR chunk 검색 케이스 여부
failure_expected: false # 결과 0건 expected
graded_relevance: # {doc_id: grade} (0~3)
3856: 3
3868: 2
3879: 2
relevant_ids: [3856, 3868, 3879] # v0.1 호환 (--eval-version v0.1 용)
top3_ids: [3856] # v0.1 호환
notes: |
Act(3856) = grade 3 (본법, top-3 강제).
Decree(3868)/Rule(3879) = grade 2 (보조 정답, 본법 우선).
신규 case 작성 가이드
- id 명명: 카테고리 prefix + 3-digit (예:
std_006,ko_006,en_001,mix_006,exam_001,ocr_001,fail_004) - graded_relevance 채점 사유 notes 필수 — 왜 그 등급 부여했는지 1~3줄 사람용 audit trail
- failure_expected = true 인 case 는
graded_relevance: {}(빈 dict) - language 결정:
- 순수 한국어 query + 한국어 doc →
ko - 순수 영어 query + 영어 doc →
en - 혼합 (한국어+영어 keyword OR ko query → en doc) →
mixed - 다른 언어 (불어 등) 도 일단
mixed(PR-2 에서 enum 확장 검토)
- 순수 한국어 query + 한국어 doc →
- ocr_derived 결정: 정답 doc 중
documents.source_type='scanned'또는 marker 처리 chunk 가 1개 이상이면true - 정답 doc_id 식별: GPU DB 의
documents테이블에서title ILIKE '%...%'또는 metadata grep 으로 식별. graded 등급은 사람 판단.
Self-check (PR review)
- notes 에 채점 사유 명시?
- graded grade 분포 합리적 (모든 정답이 grade 3 = 의심)?
- failure_expected case 의 graded_relevance 가
{}인가? - legacy_category 보존 (v0.1 호환)?
- language / ocr_derived 일관성?
v0.1 호환성
--eval-version v0.1 으로 옛 점수 (Recall@10 / MRR@10 / NDCG@10 / Top-3 hit-rate) 재현 가능. swap 후 옛 baseline 대비 ±0.001 이내여야 함.
load_queries() 의 v0.1 fallback:
- yaml 에
graded_relevance없고relevant_ids만 있으면,top3_ids는 grade 3 / 나머지 relevant_ids 는 grade 2 로 자동 매핑. - 이 fallback 으로 v0.1 yaml 도 v0.2 mode 에서 graded score 산출 가능.
CLI 사용 예
export DOCSRV_TOKEN="eyJ..."
# 단일 평가, default = 두 mode 다 출력
.venv/bin/python tests/search_eval/run_eval.py \
--base-url https://docs.hyungi.net \
--output reports/baseline_v0_2_2026-05-23.csv
# v0.2 graded only
.venv/bin/python tests/search_eval/run_eval.py \
--base-url https://docs.hyungi.net \
--eval-version v0.2 \
--output reports/v0_2_only.csv
# v0.1 mode (옛 점수 회귀 확인)
.venv/bin/python tests/search_eval/run_eval.py \
--base-url https://docs.hyungi.net \
--eval-version v0.1
# A/B 비교 (baseline vs candidate)
.venv/bin/python tests/search_eval/run_eval.py \
--baseline-url https://docs.hyungi.net \
--candidate-url http://localhost:8000 \
--output reports/phase2a_vs_baseline.csv
Output (v0.2 mode 예)
=== single (n=23, scored=20, graded=20) ===
-- v0.2 graded --
NDCG@10 (graded) : 0.812
Recall@10 (grade>=2) : 0.756
Recall@10 (grade>=3) : 0.900
Latency p50: 240 ms
Latency p95: 412 ms
Failure-case precision: 3/3 (1.00) — empty result expected
by category:
failure_expected n= 3 recall=0.00 ndcg=0.00 gndcg=0.00
korean_only n= 9 recall=0.73 ndcg=0.78 gndcg=0.81
mixed n= 5 recall=0.53 ndcg=0.61 gndcg=0.65
standards n= 5 recall=0.90 ndcg=0.95 gndcg=0.95
english_only n= 1 recall=0.67 ndcg=0.75 gndcg=0.78
by language:
en n= 1 recall=0.67 gndcg=0.78
ko n=15 recall=0.81 gndcg=0.85
mixed n= 4 recall=0.55 gndcg=0.63
(위는 예시 — 실제 baseline 박제는 후속 PR)
Baseline 박제 정책
위치: tests/search_eval/baselines/v{version}_{date}.{json,md}
각 baseline = 2 파일:
.json— 점수 raw (overall + by_category + by_language + by_ocr_derived).md— 약점 카테고리 분석 보고서 (사람용 audit)
박제 시점:
- 평가셋 schema 변경 시 (v0.1 → v0.2 swap)
- 검색 시스템 주요 변경 commit 직후 (모델 swap / chunks 재생성 / reranker 교체)
- Phase 2A~D Diagnose PR 의 비교 baseline
박제 명명 컨벤션: v0_2_baseline_YYYY-MM-DD[_label].{json,md}. label 은 선택 (예: _after_embedding_swap, _chunks_md_content).
Phase 1 closure 조건 (feedback_quant_expectation_not_hard_gate)
본 PR (PR-Eval-V0_2-Schema-Harness):
- v0.2 schema swap (23 case)
- run_eval graded 함수 + --eval-version flag
- README.md (본 파일)
후속 PR (PR-Eval-V0_2-Baseline-Analysis):
- 50+ graded case 확정 (신규 28 case 작성)
- baseline json + analysis md 박제
- 약점 카테고리 보고서 — embedding-sensitive failure pattern 4 카테고리 식별:
- candidate recall@k (embedding 후보 selection)
- crosslingual miss (ko↔en mismatch)
- Korean-English mismatch (한자/영문 fallback)
- OCR-derived chunk miss
closure gate: 0.70 미달 OK — 원인 분해 보고서 (analysis md) 가 있으면 Phase 1 closure.
관련
- Phase 1 plan:
~/.claude/plans/phase-1-graded-eval-v0-2.md - Parent plan:
~/.claude/plans/peppy-hugging-nest.md§ Phase 1 - 건강도 평가 (graded relevance 1년 미작성 gap):
~/.claude/projects/-Users-hyungi/memory/project_document_server_health_assessment.md:24 - 발주건 단위 baseline (별 트랙, Phase 0 / merry-yawning-owl):
queries_order_baseline.yaml+order_groups.yaml— 본 PR 영향 없음