# 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`) ```python # 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 컬럼 ```yaml - 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 작성 가이드 1. **id 명명**: 카테고리 prefix + 3-digit (예: `std_006`, `ko_006`, `en_001`, `mix_006`, `exam_001`, `ocr_001`, `fail_004`) 2. **graded_relevance 채점 사유 notes 필수** — 왜 그 등급 부여했는지 1~3줄 사람용 audit trail 3. **failure_expected = true** 인 case 는 `graded_relevance: {}` (빈 dict) 4. **language 결정**: - 순수 한국어 query + 한국어 doc → `ko` - 순수 영어 query + 영어 doc → `en` - 혼합 (한국어+영어 keyword OR ko query → en doc) → `mixed` - 다른 언어 (불어 등) 도 일단 `mixed` (PR-2 에서 enum 확장 검토) 5. **ocr_derived 결정**: 정답 doc 중 `documents.source_type='scanned'` 또는 marker 처리 chunk 가 1개 이상이면 `true` 6. **정답 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 사용 예 ```bash 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): - [x] v0.2 schema swap (23 case) - [x] run_eval graded 함수 + --eval-version flag - [x] 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 영향 없음