# Phase 2Q Category-Analysis — standards/exam "회귀" 진단 (2026-05-24)

**결론**: Phase 3 의 standards 1.44 / exam 1.11 = **측정 artifact (top-N doc 중복
박제 → graded NDCG inflation)**. Rerank-Fix 의 chunk_id dedup 적용 후 정상화.
"회귀" -0.28/-0.19 는 false alarm — **inflation 의 정정**.

→ **Rerank-Fix 의 NDCG 0.876 이 정확한 측정값**. baseline 0.659 대비 **Δ +0.217 = 실제
multi-query 효과** (Phase 3 박제값 +0.268 은 inflation 포함). Apply rollout 결정은 여전히 valid.

## 1. 핵심 진단

graded NDCG 계산은 returned top-N 에 **unique doc 등장 가정**. retrieval path 가 같은
doc 을 N번 박제하면:
- DCG 분자 = `Σ grade_i / log2(rank_i + 1)` 에 같은 doc 의 grade 가 N번 합산
- ideal DCG (분모) 는 graded_relevance 의 unique doc 기준
- → actual DCG > ideal DCG → **NDCG > 1.0** (정상 invariant 위반)

Phase 3 측정 (commit `a41adb6`) 의 multi-query path 는 chunk_id dedup 미적용
(`merged_chunks_by_doc.setdefault(doc_id, []).extend(chunks)` 가 variant 별 same chunk
중복 누적). reranker 가 동일 chunk N개 받음 + RRF fallback 시에도 unified RRF 의
representative SearchResult 들이 doc_id 중복 가능 (별 chunk_id 의 same doc).

Rerank-Fix (commit `b734fc5`) 의 `_dedup_chunks_by_id` helper 가 chunk_id 기준 dedup →
top-N 에 unique doc 만 등장 → NDCG ≤ 1.0 invariant 복원.

## 2. 카테고리별 graded_ndcg 3-way 비교

| Category | n | baseline | Phase 3 (inflated) | **Rerank-Fix (정확)** | baseline → Fix |
|---|---:|---:|---:|---:|---:|
| standards | 11 | 0.873 | 1.441 ⚠️ | **1.157** | **+0.284** |
| exam | 7 | 0.738 | 1.109 ⚠️ | **0.918** | **+0.180** |

**Rerank-Fix 의 standards 1.157 + exam 0.918 도 여전히 baseline 대비 net 개선**. multi-query
실효성 = 둘 다 유의미 회복.

## 3. "회귀" top 5 — 모두 inflation 정정 (false alarm)

| Case | Category | Δ Phase 3→Fix | Phase 3 | Fix | baseline | Query |
|---|---|---:|---:|---:|---:|---|
| kw_004 | standards | -0.631 | 1.631 ⚠️ | **1.000** | 1.000 | 근로기준법 안전과 보건 |
| kw_009 | standards | -0.631 | 1.631 ⚠️ | **1.000** | 1.000 | KGS FP111 가스설비 배관설비 기준 |
| kw_010 | standards | -0.631 | 1.631 ⚠️ | **1.000** | 1.000 | KGS FU551 가스설비 압력조정기 가스계량기 |
| kw_001 | standards | -0.504 | 1.235 ⚠️ | 0.731 | 0.808 | 산업안전보건법 제6장 |
| exam_004 | exam | -0.472 | 1.099 ⚠️ | 0.627 | 0.674 | 고압가스 용기 내압시험 영구증가량 |

**3 case (kw_004 / kw_009 / kw_010)** = Phase 3 inflation 1.631 → Rerank-Fix 가 baseline
1.000 동일 = **dedup 후 정상값 복원 + 회귀 0**.

**2 case (kw_001 / exam_004)** = Rerank-Fix 가 baseline 대비도 회귀 (0.731 < 0.808 /
0.627 < 0.674). 이는 reranker 가 chunk-level relevance 우선 → doc 의 best chunk 가
baseline 의 doc-level retrieval 보다 grade 낮은 doc 위로 올림.

### kw_001 raw rank (3856 = grade 3, 3868/3879 = grade 2)
```
baseline   top: 1.3868★★ | 2.3879★★ | 3.3856★★★ | 4.3851✗ | 5.4041✗
Phase 3    top: 1.3868★★ | 2.3879★★ | 3.3856★★★ | 4.3851✗ | 5.3868★★    ← 3868 중복
Rerank-Fix top: 1.3879★★ | 2.3868★★ | 3.3890✗ | 4.3863✗ | 5.3856★★★      ← 3856 rank 5
```

→ Rerank-Fix path 에서 reranker 가 3856 (grade 3) 를 rank 5 로 밀어내고 non-relevant
(3890/3863) 를 위로 올림. 실제 reranker ranking 손실 — Apply 후 분석 항목.

## 4. 회복 top — multi-query 의 진짜 효과 (Phase 3 → Rerank-Fix)

| Case | Δ | Phase 3 | Rerank-Fix | Query |
|---|---:|---:|---:|---|
| exam_006 | +0.240 | 0.316 | **0.556** | LPG 저장탱크 안전거리 분말소화기 |
| kw_002 | +0.149 | 1.079 ⚠️ | **1.228** | 중대재해 처벌 등에 관한 법률 제2장 중대산업재해 |
| kw_007 | +0.081 | 1.496 ⚠️ | **1.577** | 산업안전보건기준 폭발 화재 위험물 누출 방지 |

**exam_006** = Phase 3 inflation 작은 case 에서 dedup 후 Recall 회복 (baseline 0.321 →
Rerank-Fix 0.556 = **+0.235 vs baseline**). multi-query 의 본격 효과.

**kw_002** = Rerank-Fix 가 inflation 정상화 후에도 baseline 0.834 대비 0.394 회복 — **3917
(grade 3) 가 rank 2 + rank 3 에 등장**:
```
Rerank-Fix top: 1.3921★★ | 2.3917★★★ | 3.3917★★★ | 4.10573✗ | 5.3923✗
```
→ 3917 의 다른 chunk_id 가 rank 2/3 = chunk-level reranker 의 정확한 doc 식별 효과.

## 5. 정정값 박제 (Apply 결정 baseline)

| Metric | 박제됐던 값 (Phase 3) | **정정 (Rerank-Fix)** | baseline | Δ vs baseline |
|---|---:|---:|---:|---:|
| **Overall NDCG** | 0.927 ⚠️ inflated | **0.876** | 0.659 | **+0.217** ✅ |
| Recall t≥2 | 0.687 | 0.721 | 0.695 | +0.026 |
| Recall t≥3 | 0.728 | 0.739 | 0.761 | -0.022 |
| standards | 1.44 ⚠️ | **1.157** | 0.873 | +0.284 |
| exam | 1.11 ⚠️ | **0.918** | 0.738 | +0.180 |
| korean_only | 0.71 | 0.66 | 0.51 | +0.15 |
| mixed | 0.57 | 0.52 | 0.39 | +0.13 |
| english_only | 0.77 | 1.11 ⚠️ | 0.78 | +0.33 |

`english_only` 의 1.11 은 여전히 inflation (baseline `single` 측정의 path 와 같지만
multi-query path 의 동작 다름). 추가 진단 별 chore 후보 — `_rrf_fuse_variants` 의
representative 보존 logic 이 doc 중복 박제 가능성.

## 6. 따라야 할 후속 작업

1. **Apply rollout 결정 = 정정값 기준 invariant 유지** — Rerank-Fix NDCG 0.876 +
   baseline +0.217 = 충분히 net 개선. opt-in path 1주 관찰 valid.

2. **별 chore `PR-Eval-GradedNDCG-Dedup`** — `tests/search_eval/run_eval.py` 의
   graded NDCG 계산 함수에 returned_ids dedup 적용 + NDCG > 1.0 시 warning + dedup
   카운트 박제. 운영 평가 metric inflation 회피 invariant.

3. **별 chore `PR-2Q-Search-Result-Dedup`** — `_rrf_fuse_variants` 의 representative
   보존 logic 이 같은 doc_id 의 chunk-level SearchResult 들을 dedup 하는지 audit.
   `chunks_by_doc` 의 chunk 중복 + `_rrf_fuse_variants` 의 doc_id 중복 2 layer
   dedup invariant.

4. **kw_001 / exam_004 등 baseline 회귀 case** = Apply 후 telemetry 박제 + 실제
   사용자 query 분포에서 발생 빈도 측정. 회귀 발견 시 reranker tuning (예: chunk-level
   score 보다 doc-level grade 우선) 검토.

## 7. 메모리 갱신 필요 항목

- `project_search_v2.md` Phase 2Q section — NDCG 박제값 정정 (0.927 → 0.876, Δ 0.268
  → 0.217), inflation 발견 사유 박제
- `MEMORY.md` Phase 2Q row — 위와 동일
- 신규 feedback memory — graded NDCG dedup invariant (run_eval 측정 무결성)
- decision md 의 4-factor 표는 정정값으로 swap 또는 footnote 추가
- Apply PR 의 docs/phase_2q_apply_opt_in.md 의 metric 목표 (Recall t≥3 ≥ 0.74) 는
  Rerank-Fix 정확값 0.739 와 거의 일치 — 그대로 유지

## 8. raw 산출물

- 본 진단 = `tests/search_eval/baselines/v0_2_phase2q_category_analysis_2026-05-24.md`
- script = `phase2q_category_analysis.py` (read-only csv parse, gpu repo 루트)
- 비교 csv:
  - `reports/v0_2_phase2q_baseline_rebaseline_2026-05-24.csv`
  - `reports/v0_2_phase2q_cand_multi_query_macmini_2026-05-24_cold.csv` (Phase 3, inflated)
  - `reports/v0_2_phase2q_rerank_fix_2026-05-24.csv` (정확값)
- queries.yaml graded_relevance = `tests/search_eval/queries.yaml`
