# task-2937 — InsuRo Phase 0a 증권분석 측정 스파이크

**팀**: dev1-team (헤르메스) · **일자**: 2026-08-12 · **레벨**: Lv.3 (측정 스파이크)
**성격**: **read-only 측정**. 프로덕션 코드 변경 0건 · PR 0건 · 머지 0건.
**판정표**: `/home/jay/workspace/teams/dev1/task-2937-policy-spike/reports/PHASE0_VERDICT.md`

---

## S — Situation
InsuRo 증권분석/신규설계 프로젝트는 Phase 0 게이트("확장 vs 재작성" 판정) 없이 Phase 1 착수가 금지돼 있다.
확보된 데이터는 약관 3건(메리츠 세분형 871p / 메리츠 통합형 1767p / 삼성 통합형 1474p)이며,
실증권(고객 증권)은 회장 제공 예정이라 미확보 상태였다.

## C — Complication
"이름만으로 grouping이 되는가"에 따라 약관 파서 투자 규모가 10배 갈린다(lazy-fetch vs 풀파서).
동시에 로키가 "숨은 예외조항(감액·면책·유사암범위)을 발췌가 놓치면 오판정한다"고 최대 지적했으나,
**아무도 이 수치를 실측한 적이 없었다.** 추정으로 Phase 1을 설계하면 미분류 폭증 또는 오판정 리스크를 떠안는다.

## Q — Question
1. 약관에서 담보 조항을 얼마나 잡아내는가(recall)?
2. 담보명만으로 회장 taxonomy 판정이 되는가?
3. 숨은 예외조항을 발췌가 놓치는가?
4. 약관 표지에서 Drive 논리키 6필드를 뽑을 수 있는가?
5. 현 `policy_analyzer` 스키마로 grouping 플래그를 담을 수 있는가 (extend vs rewrite)?

## A — Answer (실측 결과)

| 항목 | 목표 | **실측** | 판정 |
|---|---|---|---|
| 약관 담보 발췌 recall | ≥95% | **34.8%** (80/230) | ❌ FAIL |
| 표지 파싱 textlayer | — | **94.4%** (17/18) | ✅ PASS |
| 표지 파싱 vision | ≥90% | **72.2%** (13/18) | ❌ FAIL |
| **이름만 grouping 일치율** | 실측 확보 | **50.0%** (42/84) | ⚠️ **FULL_PARSER** |
| 예외 신호 포함률 | — | **85.4%** | 🟡 조건부 |
| 리터럴 키워드 recall | — | **43.4%** | ⚠️ 경고 |
| **SEMANTIC_ONLY (로키 갭)** | — | **42.1%** | ⚠️ 핵심 리스크 |
| 스키마 grouping 플래그 | — | **0/5** | ❌ 부적합 → **wrap** |
| 증권 추출 / JPG vision | ≥95%/≥90% | **측정불가** | ⏸ 실증권 미제공 |

### 핵심 결론 3가지

**① 이름 grouping = FULL_PARSER 확정 (50.0% < 60% 임계)**
단, 오류의 성질이 중요하다. 혼동행렬 실측 결과 **이름만으로 구체 그룹을 확정한 건은 오판 0건**이고,
불일치 42건은 **전부 `UNKNOWN_ADVISOR`(판정 불가)** 에서 나왔다.
→ 이름 grouping은 "틀리는" 게 아니라 **"모르는"** 것. 약관을 안 읽으면 **절반이 미분류로 남는다**.

**② 로키 지적은 실재하나 형태가 다르다 — 위치가 아니라 "표현방식"이 숨긴다**
예상했던 별표·주석 은닉은 소수였다(별표 3/294). 실제 위험은 **SEMANTIC_ONLY 42.1%**:
- `감액` 리터럴 recall **18.3%** — 약관은 "감액"이란 단어 대신 **"1년 경과 전 50%/후 100%" 지급률 표**로 표현
- `면책` 리터럴 recall **1.7%** — "면책" 대신 **"암보장개시일 = 최초계약일+90일" 정의**로 표현
- `정액` — 그 단어가 약관 원문에 **아예 등장하지 않음**
→ **키워드 기반 lazy-fetch는 감액·면책을 구조적으로 놓친다.** ①의 FULL_PARSER 판정을 독립적으로 재확인.

**③ 스키마 판정 = wrap** (extend ✗ / rewrite ✗)
`policy_analyzer.py`는 증권 PDF 추출기로 현행 유지, 약관·grouping·JPG vision은 신규 모듈 분리 + 래핑.
- `DEFAULT_COVERAGES` 고정 화이트리스트 + 프롬프트 규칙1 "리스트에 없는 담보는 포함하지 마세요"(`:178`)
  → 표적/면역/중입자/하이클래스 등이 **AI 출력 단계에서 원천 탈락**
- `amount` = `int(만원)` 강제(`:254-258`) → "1억 한도 1천만원 단위 비례"·"연간한도" **표현 불가**
- provenance 저장 자리 없음 · 입력 PDF 1개 + 타임아웃 120초 → 1767p 약관 **커버 불가**
- plan.md 위임계획(「policy_analyzer=추출 유지(래핑)」)과 **일치**. 회귀 위험 0.

---

## 발견 이슈 및 해결

### 이슈 1 — [해결] 발췌 엔진이 「암진단비」를 통째로 누락 (recall 34.8%)
- **발견**: 발췌 결과 141건을 팀장이 육안 검토 중, 메리츠 2문서에 **「암진단비(유사암제외)」·「유사암진단비」가 한 건도 없음**을 확인. 암보험 분석의 가장 기본 담보다.
- **검증**: 발췌 엔진과 **독립적인 정답 담보 목록**을 약관 색인/목차에서 구축(`build_truth_list.py`) → 원문 존재 확정
  - meritz_sebun 물리 153p `"13. 갱신형 암진단비(유사암제외)보장 특별약관"`
  - meritz_tonghap 물리 1460p `"1. 암진단비(유사암제외)(통합간편가입)보장 특별약관"`
- **근본원인 확정**: 담보 헤딩 정규식이 `N-M.`(하이픈) 형식만 매칭. 메리츠는 절마다 번호 체계가 달라 `13.` 형식 절이 전부 탈락.
- **처리**: 스파이크 범위상 정규식 수정은 하지 않고 **누락률을 정량화**하고 정답목록을 **회귀 게이트로 남김**. Phase 2 착수 시 이 목록 대비 recall 재측정이 완료 조건.

### 이슈 2 — [해결] 예외조항 recall 7.8%는 측정 아티팩트 → 지표 교체
- **발견**: 하네스가 산출한 예외조항 recall 7.8%(44/565)가 라벨러 보고("clause_text만으로 판정 충분했다")와 모순.
- **검증**: 팀장이 직접 미스 샘플을 덤프 → 라벨러가 **verbatim이 아닌 요약·재구성 인용**을 기록한 것이 원인.
  예: `"최초계약일부터 1년 경과시점 전일 이전: 보험가입금액의 50% / 1년 경과시점 이후: 100%"` ← 표를 산문으로 재구성. 원문 substring 아님.
  `"...보장 특별약관 (담보명 자체에 '갱신형' 명기)"` ← 라벨러 주석이 인용문에 혼입.
- **처리**: 해당 지표를 **무효 표기**(`exception_recall_quote_based_INVALID: true`, 기존 수치는 삭제하지 않고 보존)하고,
  재라벨링 없이 객관 측정 가능한 **「예외조항 신호 포함률」 지표를 신규 구현**(`measure_exception_signal.py`).
  향후 라벨링 프로토콜에 **verbatim 인용 강제**를 `metrics_definition.md`에 명문화.
- **교훈**: 하네스는 정상 동작했고 **라벨링 프로토콜이 결함**이었다. 지표 이상치를 그대로 보고했으면 회장에게 잘못된 수치가 갔다.

### 이슈 3 — [미해결·회장 판단 필요] 삼성 "하이클래스" 브랜딩이 taxonomy 정의와 역매칭
- 회장 정의: 하이클래스 = 급여본인전액+비급여 · **수술/약물/방사선 각 항목별 한도**
- 실측: `하이클래스암특정치료비`(013/014)는 **항목별이 아니라 3종 중 무엇을 받든 연1회 단일 정액**
- 반면 `종합병원암전액본인부담(비급여포함)특정치료비Ⅱ/Ⅲ`(022/026/035/036/039)가 **회장 정의의 하이클래스와 정확히 일치**
- → **담보명 브랜딩을 신뢰하면 안 된다**는 이름≠실질의 결정적 실증. **taxonomy 동결 전 회장 확인 필수**(동결 후 발견 시 지식DB 전체 오염).

### 이슈 4 — [미해결·회장 판단 필요] taxonomy에 "검사비/수술비/지원금" 축 부재
`암MRI촬영검사비`·`암특정단일유전자검사비`·`암특정생검조직병리검사비`·`다빈치로봇암수술비`·`종합병원암복합치료회복지원금` 등
**명백한 암 담보인데 진단비/치료비 어느 그룹에도 안 맞아** `UNKNOWN_ADVISOR` 처리됨(이것이 미판정의 3대 원인 중 하나).
→ 회장 판단 필요: (a) 별도 그룹 신설 (b) 보장분석 표의 다른 대구분(수술비 등)으로 배치 (c) 범위 외.

### 이슈 5 — [해결·설계 반영 필요] 별표 번호가 챕터별 로컬 재사용
메리츠 세분형에서 "별표2"가 p.649(악성신생물분류표)와 p.764(다른 내용)로 문서 내 중복.
**별표 번호만으로 참조를 특정하면 오참조 확정.** 참조 1-hop 구현 시 **조항이 속한 챕터 범위 내에서 해석**해야 함 → Phase 2 설계 제약으로 기록.

### 이슈 6 — [해결] pyright 타입 오류 15건
`page.get_text()` 반환형 유니언 문제 등. `isinstance` 좁히기 + `Sequence[str]` 패턴으로 해결.
**회귀 검증**: 수정 후 재실행하여 담보 건수 34/49/58 동일 + 출력 JSON byte-identical 확인.
최종 `pyright scripts/` **오류 0건**. `measure_exception_signal.py:171`의 unreachable 경고는 검토 결과
가드 클로즈(`exceptions`가 dict가 아닐 때 `return False`)로 **실제로는 도달 가능**한 정상 로직이라 미수정.
예외 신호 지표(542/85.4%/43.4%/42.1%/14.6%)는 정리 전후 동일함을 재실행으로 확인.

### 이슈 7 — [해결] 픽스처 재실행이 실제 지표 파일을 덮어씀
- **발생**: L1 증거 수집 중 팀장이 `measure_harness.py --input-dir data/fixtures`를 재실행 → 하네스가 기본 출력 경로를 쓰는 설계라
  `data/metrics_final.json` · `reports/metrics_table.md` · `reports/confusion_matrix.md`가 **픽스처 데이터로 덮어써짐**.
- **감지**: 실행 로그의 `docs_found=['fixture_doc1','fixture_doc2']`를 즉시 확인.
- **복구**: 정규 명령 `--input-dir data --labels-pattern 'labels_merged_{doc_id}.json'` 재실행 →
  `docs_found=['meritz_sebun','meritz_tonghap','samsung']` · `extraction_recall 80/230=34.8%` ·
  `grouping.cancer_only 42/84=50.0% → FULL_PARSER` 복구 확인(보고 수치와 일치).
- **재발 방지 제안**: 픽스처 실행 시 `--final-out`/`--report-out`/`--confusion-out`을 픽스처 경로로 강제하거나,
  `--input-dir`에 `fixtures`가 포함되면 기본 출력 경로를 자동으로 픽스처 하위로 돌리도록 하네스 수정(Phase 2 착수 시).

---

## 검증 / 테스트 결과

### L1 스모크테스트 (실제 실행)
| # | 항목 | 명령 | 결과 |
|---|---|---|---|
| 1 | 발췌 엔진 | `python3 scripts/extract_clauses.py --all` | ✅ PASS — 3 JSON 생성, 담보 34/49/58건, `clause_text` 빈 항목 0 |
| 2 | 표지 파싱 | `python3 scripts/parse_cover.py --all` | ✅ PASS — textlayer 3/3 문서, vision(claude CLI) 3/3 호출 성공 |
| 3 | 정답목록 | `python3 scripts/build_truth_list.py --all` | ✅ PASS — 암관련 36/94/100건, 페이지 검증률 100%/100%/97% |
| 4 | 하네스 픽스처 | `python3 scripts/measure_harness.py --input-dir data/fixtures` | ✅ PASS — **손계산 기대값 11개 항목 전부 일치** |
| 5 | 하네스 실측 | `measure_harness.py --labels-pattern labels_merged_*` | ✅ PASS — metrics_final.json 생성 |
| 6 | 라벨 병합 | `python3 scripts/merge_labels.py` | ✅ PASS — **141/141 cov_id 완전 매칭, 불일치 0** |
| 7 | 표지 정확도 | `python3 scripts/measure_cover.py` | ✅ PASS — textlayer 17/18, vision 13/18 |
| 8 | 예외 신호 | `python3 scripts/measure_exception_signal.py` | ✅ PASS — 분모 542건, 신호포함률 85.4% |
| 9 | 타입 검사 | `pyright scripts/` | ✅ **0 errors** (수정 전 15건) |
| 10 | 회귀 | 수정 전후 출력 대조 | ✅ PASS — 담보 건수·JSON 내용 동일 |

**하네스 자체의 정확성 증명**: 합성 픽스처(recall 2/3·grouping 1/2·숨은예외 1/2가 나오도록 의도 설계)로
손계산 기대값 vs 스크립트 출력 **11개 항목 전부 일치** 확인. 측정 도구를 먼저 검증한 뒤 실측에 사용했다.

**L1 증거 (실제 산출물 · 재현 실행 출력)**

**이미지 증거(스크린샷)** — 약관 1페이지 렌더 `.png` (vision 판독 입력으로 실제 사용):
```
245216 bytes  data/cover_meritz_sebun.png
502197 bytes  data/cover_meritz_tonghap.png
1346898 bytes data/cover_samsung.png
```

**재현 실행 1** — 예외 신호 측정 (`python3 scripts/measure_exception_signal.py`):
```
[measure_exception_signal] signal_coverage=463/542 = 85.4%
[measure_exception_signal] literal_recall=235/542 = 43.4%
[measure_exception_signal] semantic_only_ratio=228/542 = 42.1%
[measure_exception_signal] miss_rate=79/542 = 14.6%
```

**재현 실행 2** — 최종 지표 (`measure_harness.py --input-dir data --labels-pattern 'labels_merged_{doc_id}.json'`):
```
docs_found=['meritz_sebun', 'meritz_tonghap', 'samsung']
extraction_recall.pooled = 80/230 = 34.78%  (missed_total=150)
grouping.all_141   = 94/141 = 66.7%  -> HYBRID (참고)
grouping.cancer_only = 42/84 = 50.0% -> FULL_PARSER (판정 채택)
```
누락 담보 샘플(실제 출력): `갱신형암진단비(유사암제외)보장특별약관`(p153) · `갱신형유사암진단비보장특별약관`(p155) ·
`갱신형재진단암진단비(1년대기형)보장특별약관`(p162) — **이슈 1의 직접 증거**.

**타입 검사**: `pyright scripts/` → **0 errors** (수정 전 15건)

### 측정 방법론 통제
- **blind 분리**: 이름 판정자(아폴론)는 `clauses_*.json`·PDF·`labels_*.json` 열람 금지, `names_only.json`만 입력.
  실질 라벨러 3인은 `name_only_judgments.json` 열람 금지. → 이름 vs 실질 비교의 독립성 확보.
- **독립 정답목록**: 발췌 엔진과 무관하게 약관 색인/목차에서 정답 담보 목록을 별도 구축 → recall의 분모를 발췌 결과에 의존시키지 않음.
- **판정 임계 기준**: 암담보만(84건) 기준 채택. 전체 141건 기준(66.7%)은 `OUT_OF_SCOPE`가 이름으로 쉽게 걸러져 수치를 인위적으로 부풀리므로 참고용으로만 병기.

---

## 생성 파일 목록 (전부 `/home/jay/workspace/teams/dev1/task-2937-policy-spike/`)

**스크립트(8)**: `extract_clauses.py` · `parse_cover.py` · `build_truth_list.py` · `measure_harness.py` ·
`make_fixture.py` · `merge_labels.py` · `measure_cover.py` · `measure_exception_signal.py` (+`generate_exception_signal_samples.py`)

**리포트(8)**: `PHASE0_VERDICT.md`(★판정표) · `schema_fitness.md` · `metrics_definition.md` · `metrics_table.md` ·
`confusion_matrix.md` · `cover_parsing.md` · `exception_signal.md` · `exception_signal_samples.md`

**데이터**: `CONTRACT.md`(데이터 계약) · `clauses_*.json`(3) · `truth_coverage_list_*.json`(3) ·
`labels_*.json`(3) · `labels_merged_*.json`(3) · `name_only_judgments.json` · `metrics_final.json` ·
`exception_signal_metrics.json` · `cover_truth.json` · `cover_metrics.json` · `cover_*.png`(3)

**InsuRo 프로덕션 코드 변경: 0건** (`policy_analyzer.py`·`main.py`는 읽기만)

---

## ANU normal callback 등록
- **경로**: `utils/anu_callback_registrar.py` (정본 registrar) — `build_callback_envelope` → `register_normal_callback`
- **결과**: `status=REGISTERED` · `schedule_id=3264B0D2` · `at=5m` · envelope **642 bytes**(한도 3900 이내)
- **owner**: 독립 ANU key `c119085addb0f8b7` — registrar의 `_assert_independent_anu_key` self-key 가드 통과
- **owner 교차검증**: 실행자 자기 키(`--cron-list`, chat 6937032012)로 조회 시 **schedules=[] (빈 목록)**.
  → 이 callback이 **executor self-key로 등록되지 않았음**을 확인(자가발사 금지 원칙 준수). ANU 소유 스케줄은 실행자 키로 보이지 않는 것이 정상.
- **delivery=DELIVERED / receipt=UNCONFIRMED** — 수령 확인은 ANU 픽업 시점에 확정.
- 참고: registrar가 `--key`(process listing 노출 경고) 경로를 사용 — cokacdir가 `--key-file`/`--key-stdin` 권장. **registrar 구현 개선 후보**(이번 스파이크 범위 밖, 기능 영향 없음).

## 머지 판단
- **머지 필요: No** — 스파이크 산출물이며 프로덕션 코드 변경이 없다. worktree 미사용, PR 미생성, git 커밋 0건.
- 산출물은 `/home/jay/workspace/teams/dev1/task-2937-policy-spike/` 에 보존. Phase 2 착수 시 **정답목록이 회귀 게이트**로 재사용된다.

## 모델 사용 기록
| 팀원 | 역할 | 모델 | 비고 |
|---|---|---|---|
| 불칸 / 불칸2 | 백엔드·발췌 엔진·표지·정답목록 | sonnet | 코딩 |
| 미네르바 / 미네르바2 | 측정 하네스·지표 통합 | sonnet | 코딩+분석 |
| 오딘 | 스키마 적합성 판정 | sonnet | 분석(haiku 금지 대상) |
| 아폴론 | 이름 blind 판정 | sonnet | 도메인 분석 |
| 헤라 / 데메테르 / 헤스티아 | 약관 실질 라벨링 ×3문서 | sonnet | 도메인 분석 |
| 테미스 | 표지 정확도 검증 | sonnet | 분석 |
| 네메시스 | 예외조항 재측정 | sonnet | 측정 방법론 |
| (무명) | pyright 타입 주석 정리 | **haiku** | 정당성: 로직 변경 없는 단순 타입 주석/미사용 import 제거. 회귀 검증 명령 지정 |
- 팀장(Opus)은 직접 코딩하지 않음. 설계(데이터 계약 확정)·분배·검증(이슈 1·2 발견)·통합만 수행.

## 회장 결정 필요 사항
1. **taxonomy 동결 보류 권고** — 이슈 3(하이클래스 역매칭) · 이슈 4(검사비 축 부재) 판단 후 동결
2. **실증권 3~5건 제공** — 증권 추출 정확도·JPG vision 정확도는 실증권 없이 측정 불가(Phase 0 게이트 미완)
3. **Phase 1 착수 가부** — dev1 의견: 조건부 착수 가능. 단 grouping v0의 **미분류 버킷이 절반 가까이 나온다는 전제**로 UX 설계 필요(실측 근거)

## 비고
- 표본 한계: 약관 3건(손보 2사), **생보 0건**. 정답셋은 1인 라벨링(교차 라벨링·IAA 미측정).
- 무효 지표 1건(quote 기반 예외 recall)은 삭제하지 않고 무효 사유와 함께 보존.
