# OHMY_PRODUCT_PREMIUM_DATA_CONTRACT_V1 (동결)

**동결일**: 2026-07-18 KST · **작성**: 아누 · **합의**: GPT + Codex(AGREE) + ANU
**근거**: 회장 self-smoke #1~#3 실측 + ANU 오프라인 실응답 분석 + Codex 3라운드 코드검증
**상태**: **V1 FROZEN** — 이 문서가 A·B·C 트랙의 단일 해석 기준. 변경 시 V2 신규 발행(수정 금지).

---

## 1. 데이터 역할 (확정 — GPT 정교화 반영)
| 항목 | 원천 | 비고 |
|---|---|---|
| 현재 선택 담보 | **DOM** `#bojang_lists input[type=checkbox][data-cd]` 중 `checked===true` | 저장플랜(user_coverages) **사용 금지** |
| 현재 가입금액 | **DOM** 해당 행의 `input#input_<cd>` 의 `.value` | 콤마 제거 파싱. `guide_coverage_amount` attr 은 **참고값**(현재값 아님) |
| 원보험료·기준금액·담보제공여부·납입조건 | **API** `/api/ProductPremiums` `coverage_premiums[].detailList[]` | 구조화 원천 + 독립 계산 검증용 |
| **고객 표시 최종 보험료** | **DOM 셀** `em[company_code][coverage_cd][premium]` | 이미 스케일 적용된 표시값(실증). **단독 계산원 승격 금지** — 아래 3축 교차검증 통과 시에만 사용 |

## 2. 계산식 (실증 확정)
```
정규화보험료 = api_premium × (dom_amount_parsed ÷ api_coverage_amount)
```
- 분모 = `coverage_amount`. (관측 692건에서 `guide_coverage_amount` 와 100% 동일했으나 **영구 불변식 아님** → 다르면 fail-closed)
- 회사별 합계 = Σ(정규화보험료), 선택 담보만.

## 3. 검증 (3축 교차 — 고객용 사용 조건)
회사·담보별로 **3개를 모두 보관**하고 교차검증:
1. `scaled_value` (API 기반 독립 계산)
2. `screen_cell_premium` (DOM 셀 표시값)
3. `screen_selected_total` vs 화면 상단 회사 총액

**고객용 계산에 사용 가능 = 3축이 허용오차 이내일 때만.** 크게 다르면 **조용히 한쪽 채택 금지 → 해당 보험사·담보 차단**.

## 4. 허용오차
- 실측: 10개사 전부 **0.67~6원**(상대오차 0.0018%). 원인 = 담보별 정수 표시 반올림 누적.
- 잠정 기준 `tolerance_basis: PROVISIONAL_COUNT_TIMES_1WON` 유지. **WITHIN_TOLERANCE ≠ VALIDATED.**
- **정확한 반올림 규칙(반올림/버림/올림/절사/회사별)은 미확정** → 고객 표시는 **DOM 셀 값 사용**으로 회피 가능.

## 5. 동일 조회 판정 (★ 필수 불변식)
서로 다른 조회의 숫자를 **직접 비교 금지**. 모든 진단·저장 자료는 아래를 **함께 묶어** 기록하고, **셋이 모두 같을 때만** 동일 조회로 비교:
- `request_fingerprint` (age·gender·insurance_type·plan_id)
- `dom_selection_hash`
- `api_captured_at` / `dom_snapshotted_at`

> 위반 사례 박제: ANU 가 전종혁·43세 캡처(343,414)와 홍길동·42세 캡처(333,648)를 섞어 "상단 합계에 9,766원 별도 구성" 오판 → 동일 조회 확인 후 철회.

## 6. fail-closed 방어 (유지 필수 — 관측 결과를 불변식으로 승격 금지)
`api_missing` / `api_premium_missing_or_invalid` / `zero_premium_unverified`(근거없는 0) / `negative_invalid` / `parse_fail` / `zero_baseline` / `differs_across_companies` / `dom_duplicate` / `api_duplicate`
- 어느 것도 **조용히 통과 금지**. 사유별 카운트 출력.
- `row_editable_input_count !== 1` 또는 `input_id !== "input_<cd>"` → 가입금액 **신뢰 금지**.

## 7. 검증 범위 (★ 일반화 금지)
**확정된 것은 아래 조건의 관측 범위 한정**:
- 상품유형 손보종합(무배당) · 20년납/100세 · 특정 plan_id · 남성 42세 · 선택담보 32개 · 10개 보험사 · 서울대보험쌤 플랜

**미검증(일반화 전 필수 시나리오)**: 다른 나이·성별 / 다른 상품유형 / 갱신형·비갱신형 / 다른 만기·납입기간 / 다른 플랜 / API 담보 부재·중복 / 보험사별 기준금액 상이 응답 / DOM 구조 변경

→ 정확한 상태 = **`DATA_CONTRACT_V1_VERIFIED_IN_OBSERVED_SCOPE`** (전체 환경 일반화 아님)

## 8. 보안 (계약의 일부)
- **토큰**: 페이지 URL `?token=<JWT>`. **저장·로깅·서버전송 절대 금지.** 사용자 명시 클릭 시 1회 사용 후 폐기.
- **PII**: 고객 이름·생년월일·consultant_id·ga_id **출력·저장 금지**.
- **진단 캡처 규칙**: 주소창 캡처 금지 · JWT 패턴 콘솔 출력 차단 · 화면 이름·생년월일 마스킹 · 테스트 데이터 사용.
- **검증 실패 데이터는 정상 상담결과로 저장 금지** (`REQUIRES_REVIEW` 상태 또는 전송 차단).

## 9. 아키텍처 결정 (GPT 제안 · Codex AGREE)
**MAIN world fetch/XHR 가로채기 전면 제거** →
- ISOLATED content script: DOM 선택상태 읽기 + **사용자 클릭 시에만** URL 토큰 1회 전달
- background: `fetch(API, {headers:{Authorization:'Bearer '+token}})` **직접 조회**
- → MAIN world · 인터셉터 · MessageChannel · **first-PORT race 문제 전부 소멸**

**근거**: 구 수집기가 `requests.get(headers={"Authorization": f"Bearer {token}"})` 로 성공 — 쿠키·브라우저 세션 불필요.
**전제(A-0 스파이크로 실증 필요)**: host_permissions/배경 컨텍스트 cross-origin 허용 · 서버측 Origin/Referer 검사 유무 · 토큰 수명.

## ★ AMENDMENT-1 (2026-07-18, 회장 지적 — 다중 사용자/토큰 가변성)
> **회장 지적**: "사용자들은 나와 비슷하게 ohmymanager 에 들어갔을 때 **토큰이 다를 수 있다**. 이 점 잊지 말고 설계해야 한다."
> 이는 단순 "값이 다르다"를 넘어 **아키텍처·데이터격리 3대 제약**을 만든다. 구현 전 필수 반영.

### A1-1. 토큰은 사용자별로 다르다 → 저장·캐시·공유 절대 금지
- 매 실행마다 **현재 페이지에서 새로 읽는다**. 전역변수·storage·서버 어디에도 보관 금지.
- (현 코드 확인: `readTokenFromSearch` 가 함수 스코프 1회 사용 — **준수 중**. 향후 "편의를 위한 토큰 저장" 제안은 **금지**.)

### A1-2. ★ 토큰이 URL 에 없을 수 있다 (아키텍처 리스크)
현 설계는 `?token=` 이 URL 에 있다는 **가정에 의존**한다. 그러나 사용자별로 진입 경로가 다를 수 있다(북마크·SPA 라우팅으로 쿼리 소실·포털/SSO 경유·쿠키 세션 등).
- **A-0 라이브 판정 항목에 추가**: `token_source` = `"URL_QUERY"|"ABSENT"` 를 반드시 기록.
- 토큰 부재 시 **fail-closed** — 다른 곳에서 토큰을 긁어오는 우회 조립 **금지**. 사용자 안내로 처리.
- **다수 사용자에서 `ABSENT` 가 나오면 background 직접조회 아키텍처는 일반화 불가** → 대안 설계 필요(가로채기 복귀가 아니라 별도 검토).

### A1-3. ★★ consultantid 스코프 격리 (CRM 단계 치명)
토큰 payload 에 **`consultantid`** 가 들어있고, API 응답(플랜·user_coverages 등)은 **그 설계사에게 종속**된다.
- 한 PC/브라우저를 **여러 설계사가 사용**하거나 계정을 오가면, **A설계사 데이터가 B설계사 상담 히스토리에 저장**될 수 있다.
- **필수 안전장치**:
  1. 실행 시 토큰의 `consultantid` 로 **세션 소유자 식별**.
  2. **직전 실행과 `consultantid` 가 다르면** 이전 캡처·미리보기 상태를 **즉시 폐기**(섞임 방지).
  3. CRM 저장 시 **반드시 설계사 단위로 분리**. 교차 조회·교차 저장 **금지**.
  4. **`consultantid` 원문 저장·출력 금지**(§8 유지) → **salted hash** 로 세션 격리 키만 사용하거나, 서버측 FA 계정 ID 로 매핑해 저장.
- 위반 시 = **타 설계사 고객정보 혼입** → 개인정보 사고. B(서버/CRM) 설계의 **1순위 제약**.

## 10. 실행 순서 (동결)
0. 보안 조치(토큰 재발급 권고) → 1. **본 문서 V1 동결(완료)** → 2. **A-0 background 직접조회 실증 스파이크** → 3. 서버 ingest API·CRM schema·동의·감사로그 → 4. 확장 로컬 분석·미리보기(실전송 OFF) → 5. 명시적 저장 버튼으로 A↔B 연결(테스트 고객만) → 6. 계산엔진·UI 연결 → 7. 제한 파일럿 → 8. production 활성화

**병렬 허용**(계약 동결 후): B의 schema+mock ingest / C의 fixture 기반 계산엔진·UI 프로토타입
**병렬 금지**: 보안구조 미확정 상태의 실제 서버전송 · A 미확정 상태의 C 실데이터 연결 · 진단브랜치 전체 production 머지 · 동의 없는 CRM 히스토리 생성
