# task-2949 계약서 — 실손 지식 전수강화 + 근거봉인

- **base**: `origin/main` @ `1b64f99` (task-2947 머지본)
- **branch**: `task-2949-silson-evidence`
- **worktree**: `/home/jay/projects/InsuRo-worktrees/task-2949`
- **merge_policy**: tiered (머지는 ANU)
- **원칙**: 원문에 없는 값은 만들지 않는다(금소법 환각 0). 값이 없으면 `null` + `원문 미기재`.

---

## §0. 자료 실사 (사실 기록)

지시문은 "19이미지"라고 했으나 **실제 존재 파일은 18장**이다.

| 조 | 파일 | 장수 |
|---|---|---|
| A1 | `5세대vs유병자vs노후실손.png` | 1 |
| A2 | `변천사&보장안하는손해.jpg` | 1 |
| B/C | `총정리(KB손보)/KakaoTalk_20250808_122716321{,_01..._15}.png` | 16 |
| **계** | | **18** |

→ 보고서에 "19 대비 18" 차이를 명시한다. 임의로 19번째를 만들지 않는다.

---

## §1. 전수 OCR (누락 0)

- 18장 **전부** 아누 vision(claude CLI, sonnet) 1이미지=1호출로 재판독.
- 산출: `teams/dev2/task-2949/ocr_full/<슬러그>.md` (frontmatter: slug/source_file/sha256/ocr_model/ocr_at)
- 처리 로그: `teams/dev2/task-2949/logs/batch{A,B,C}.log` — 이미지별 status/bytes/tables/소요초
- **누락 0 검증**: sha256 18개 목록 ↔ ocr_full 산출 18개 1:1 대조. 미달 시 FAIL.
- 기존 OCR(`teams/dev2/task-2947/ocr/`, 3파일 통합본)과 **대조**해 신규 발견분을 delta 로 기록.

## §2. 자료 간 전수 비교 매트릭스

축:
- **행(세대)**: GEN1_PRE_2003_10, GEN1_2003_10, GEN2, GEN3, GEN4, GEN5, 노후실손, 유병력자실손
- **열(항목)**: `ATTRIBUTE_KEYS` 11종(insurance_term, entry_age, renewal_cycle, reenrollment_cycle,
  coverage_structure, deductible, payout_rate, waiting_period, upper_grade_room, coverage_limit,
  other_features) + `excluded_coverages` + `period`
- **소스**: A1(협회공시), A2(변천사), B/C(KB총정리)

셀 상태 enum (셀 단위 전수):
- `agree` — 2개 이상 소스가 실질 동일
- `conflict` — 소스 간 값이 어긋남 → **양쪽 모두 보존**, 대표값은 **A1/A2(1차) 우선**
- `only_<src>` — 한 소스에만 존재(충돌 아님)
- `absent` — 어느 소스에도 없음(= `null` + 원문 미기재)

산출물 2종(동일 내용, 형식만 다름):
- 기계: `server/silson/data/sources/comparison_matrix.json`
- 사람: `teams/dev2/task-2949/comparison_matrix.md`

**충돌은 전수** `silson_generations.json` 의 해당 레코드 `conflicts[]` 에 기록한다.
`conflicts[]` 항목 스키마:

    {"field": "...", "primary": {"source_id": "...", "value": "..."},
     "secondary": [{"source_id": "...", "value": "..."}],
     "resolution": "primary_wins", "note": "..."}

## §3. 출처 정직 마킹 (구조화 5필드 + tier)

`silson_generations.json` 최상위에 `sources_registry` 를 신설한다:

    "sources_registry": [
      {"source_id": "A1_KLIA_COMPARE", "publisher": "<OCR 각주 원문 그대로>",
       "doc_title": "...", "as_of": "YYYY-MM-DD|null", "ref": "<파일명>#sha256:<8자리>",
       "source_tier": 1, "tier_basis": "<tier 판정 근거 = OCR 원문 인용>",
       "image_sha256": "...", "drive_file_id": null}
    ]

**정직 규칙 (★ 중요)**
- `source_tier=1` 은 **이미지 안에 1차 출처 표기(예: 손해보험협회 공시실)가 실제로 판독된 경우에만** 부여한다.
- 표기가 없으면 `source_tier: null`, `tier_basis: "출처 표기 없음"` 으로 둔다. **추정 승격 금지.**
- KB 총정리는 보험사 제작 2차 자료 → `source_tier: 2`.
- `as_of` 는 이미지에 명시된 기준일/작성일이 판독된 경우만. 없으면 `null` + `"원문 미기재"`.
  파일시스템 타임스탬프는 자료의 as_of 가 **아니다** — 대체 금지.

레코드별 플래그:
- `primary_source_verified: true` ⟺ 그 레코드의 `sources[]` 중 **하나 이상이 tier1 registry 항목**으로 해석됨.
- `false` 면 **고객 자동판정 경로 hard-block**.

## §4. 활성화 fail-closed 게이트

신규 모듈 `server/silson/gating.py`:
- `SILSON_KNOWLEDGE_ACTIVE` (기본 **미설정 = 비활성**). `"1"/"true"` 일 때만 활성 후보.
- `SILSON_ACTIVATION_APPROVED_BY` 가 비어 있으면 **활성 불가**(회장 단독 승인 표식).
- `activation_status() -> ActivationStatus(active: bool, reasons: list[str])`
- `assert_customer_auto_path(profile)`:
  - 비활성 → `SilsonInactiveError`
  - `primary_source_verified=False` → `PrimarySourceUnverifiedError`
- **기본 동작은 차단**(fail-closed). 예외 경로 없음. 내부 조회/설계사 참고 경로는 차단 대상 아님(고객 자동판정만).
- ★ 기존 공개 API 시그니처/동작은 **비활성 상태에서 그대로**(회귀 0). 게이트는 신규 진입점에서만 강제.

## §5. 근거봉인

- OCR sanitize본을 **repo 편입**: `server/silson/data/sources/ocr/<슬러그>.md`
  - sanitize = 개인정보/연락처/계좌 등 PII 마스킹(자료 성격상 없을 것으로 예상되나 스캔은 필수)
- 원본 이미지 → Google Drive **P0-1 비공개** 업로드(공개권한 생성 없음 — main 에 이미 반영됨)
  - 실패/자격증명 부재 시 `drive_file_id: null` + `upload_status: "PENDING"` 로 **정직 기록**(허위 ID 금지)
- `server/silson/data/sources/manifest.json`:

      {"schema_version": 1, "generated_at": "...", "items": [
        {"slug": "...", "filename": "...", "image_sha256": "...", "bytes": N,
         "ocr_file": "ocr/<슬러그>.md", "ocr_sha256": "...", "source_id": "...",
         "source_tier": 1, "drive_file_id": "...|null", "upload_status": "OK|PENDING"}]}

- manifest 무결성 테스트: 모든 `ocr_file` 실재 + `ocr_sha256` 재계산 일치 + items 수 == 18.

## §6. 검증 게이트 (전부 PASS 필요)

1. OCR 18/18 산출, 누락 0 (sha256 1:1)
2. 비교 매트릭스 전 셀 채움(빈 셀 0) — 셀 수 = 8행 × 13열 = 104 이상
3. 충돌 전수 기록 — matrix 의 `conflict` 셀 수 == JSON `conflicts[]` 총 개수
4. `validate_bundle()` 문제 0
5. 신규 테스트 + 기존 silson 테스트 전부 PASS, **회귀 0**
6. 환각 0: JSON 의 모든 비-null 지식 값이 OCR 원문에 문자열로 존재(대조 스크립트)
7. 게이트 fail-closed 테스트: env 미설정 시 고객 자동판정 경로 차단

## §7. 허용 범위 (allowed_resources)

    paths:
      - server/silson/**
      - server/tests/test_silson_*.py
      - teams/dev2/task-2949/**
    forbidden_paths:
      - server/policy_grouping/**
      - server/policy_extract/**
      - src/**
      - server/main.py
    merge_policy: tiered
