# task-2887 보고서 — 생보 3대질병 계단식 재설계(담보별 비교표 + 손보 양식 직각화살표 계단식)

## S — Situation
회장 실사용 피드백: 현재 생보 3대질병 계단식(단순 카드형)이 **위 손보 계단식과 양식이 다르고**, 담보별 상세 비교가 없음. 요구 3가지:
1. **납입연수 하드코딩(20년/240개월) 절대 금지** → 오마이매니저 선택값(`query_condition.payment_years`) 동적 사용. 없으면 총액 차액 graceful.
2. 시나리오별 **비교표**(담보·회사·월보험료) + **계단식 직각화살표**(월/N년총액 차액) 둘 다.
3. 손보 계단식과 동일 양식(직각 화살표·회사명·정확한 원·굵은 차액 폰트), "절감"→"차액".

base = 현 origin/main(62da2c1, task-2886 머지 #162). **webapp-only, 서버·확장 무변경**, merge_policy=none.

## C — Complication
- 기존 `LifeThreeCriticalDiff`는 계산 결과(newTotal/diffVsBaseline)만 그리는 카드형 → **담보별 손보사·손보료 / 생보사·생보료 / 담보별 차액**을 노출할 데이터가 lib에 없었음.
- 기존 컴포넌트 `const months = ... : 240` — 정확히 회장이 제거를 원한 **20년 하드코딩 폴백**.
- 시나리오2(3대질병)는 **단일 생보사**(3담보 합 최저) 기준이라, 담보별 argmin(회사가 담보마다 다름)과 **다른 값**을 써야 함(예: 뇌혈관 argmin이 아닌 선택된 단일사 값).

## Q — Question
고객 노출 숫자(차액)를 오매칭 0·손보 양식 통일로 재설계하되, payment_years 동적·exact 원·회귀 0·e2e strict-mode 회귀 방지를 유지하려면?

## A — Answer (구현 — webapp-only 5파일)

### 1. `src/lib/lifeThreeCritical.ts` — 시나리오에 담보별 상세 노출 (+64/−3)
- `LifeScenarioCoverageRow` 인터페이스 신설: `key/coverageName/sonboCompany/sonboPremium/lifeCompany/lifePremium/diff`.
- `LifeStaircaseScenario`에 `rows[]`(cancer_only=1행, all_three=3행) + `sonboSubtotal`/`lifeSubtotal` 추가.
- `computeLifeStaircase`의 내부 `TargetData`에 `sonboCompany`(single combo면 fallbackInsurerName, 아니면 company??insurer_code)·`coverageName`(생보 display 우선) 추가.
- cancer_only push: rows 1행 + subtotal. all_three push: `data.map`으로 **선택된 단일 생보사(bestCode) 값**을 담보별 lifePremium으로 채움(담보별 argmin 아님) + subtotal.
- 계산 로직(argmin/sonbo 매칭/triple_best 게이트)은 **재구현 없이 재사용**. task-2886 담보매칭(code우선+정확allowlist+유사암배제) 불변.

### 2. `src/components/composite/LifeThreeCriticalDiff.tsx` — 표+계단식 재작성 (125→195줄)
- **payment_years 동적화(★)**: `240` 폴백 완전 제거. `hasPositiveYears`일 때만 `years=payment_years`, 아니면 `years=null`. `StairArrow`가 `years=null`이면 **"납입연수 미선택 — 총액 차액 생략"** graceful.
- `StairArrow`: 직각(세로막대+CSS삼각형, 곡선 없음). **월 차액**(작게) → **N년납 총액 차액**(가장 큰 폰트, 손보 위계와 동일). 총액 = 월차액 × 12 × N.
- 시나리오별 `<table>` 비교표: `담보 | 손보사·월보험료 | 생보사·월보험료 | 차액`. 다중행(all_three)이면 **합계행**(`life-scenario-total-*`). 손보=red, 생보=blue. exact 원.

### 3~5. 테스트 갱신
- `lifeThreeCritical.test.ts`(+39, append): rows/subtotal 검증 2건. **all_three가 단일사 값(뇌혈관 9,500=나생명, 담보별 최저 7,000 아님)** 명시 검증.
- `LifeThreeCriticalDiff.test.tsx`(+81): 마지막 2 it → 5 it 교체. payment_years=20/15/30 동적(총액=월차액×12×N), **미지정 graceful 생략(20 하드코딩 부재)**, 비교표+합계행.
- `composite-design-vpath.spec.ts`(+29): 케이스 C를 표(`life-scenario-table-*`)+합계행+화살표(월+20년납 총액) 새 testId로 갱신. 전부 testId 스코프 locator(strict-mode 회귀 방지).

## 테스트 결과 (팀장 독립 검증)
- `npx tsc --noEmit`: **에러 0** (exit 0).
- `npx vitest run` (전체): **73 files / 1094 tests passed, 0 failed**. base(62da2c1) 1090 대비 +4(신규 테스트). **회귀 0**.
- 240 하드코딩 잔존 검사: 실제 코드 0건(주석 1건만 — 규칙 설명).

## L1 스모크테스트 결과 (★ 실동작 검증)
- **서버 재시작**: Playwright webServer `npm run dev`(:8080) 자동 기동 — 실제 앱 렌더.
- **API 응답 확인**: 해당없음(서버/API 무변경). composite/consultation-history는 e2e route mock.
- **스크린샷**: `tests/e2e/screenshots/vpath/c-life-staircase.png` — 팀장 육안 확인 완료.
  - baseline 손보 3사 복합 총액 57,000원
  - 시나리오1: 직각화살표 "월 차액 −5,000원 → 20년납 총액 차액 −1,200,000원", 표 [암진단비|테스트화재 30,000원|행복생명 25,000원|5,000원], 대체 후 총액 52,000원
  - 시나리오2: "월 차액 −8,000원 → 20년납 총액 차액 −1,920,000원", 담보별 3행(암/뇌혈관/허혈성), **생보=단일사 행복생명(25,000/14,000/10,000)**, 합계행 손보 57,000/생보 49,000, 대체 후 총액 49,000원
- **e2e 케이스 C(clean worktree)**: **1 passed (5.2s)** — 실제 브라우저에서 표+화살표+동적 납입연수·단일사 일관성·합계행 전부 검증. strict-mode 위반 0.
  - (kakao CDN integrity 에러는 외부 스크립트 무관 pre-existing, 렌더/판정에 영향 없음)

## 발견 이슈 및 해결
- 시나리오2 단일사 일관성: lib all_three가 `bestCode`(단일사) 값을 담보별로 채우도록 하여, 담보별 argmin과 혼동되지 않게 rows 구성. 테스트(vitest+e2e 모두)로 "뇌혈관=단일사 값(argmin 아님)" 회귀 고정.
- payment_years 미선택 graceful: 20 폴백 제거로 인해 총액 표기가 사라지는 케이스 → "납입연수 미선택 — 총액 차액 생략" 명시 문구 + 월 차액은 유지. 컴포넌트 테스트로 고정.

## 모델 사용 기록
- 이리스(프론트엔드): **sonnet** — 5파일(lib 계산 확장 + 컴포넌트 표/계단식 + 테스트 3종) 구현. 상호의존 웹앱 단위(파일 충돌 회피)라 단일 페르소나 배정(task-2886과 동일 근거). haiku 미사용.
- 팀장(Opus/헤르메스): 설계·정밀 스펙·독립 검증(grep/tsc/전체 vitest 회귀/e2e 실행/스크린샷 육안)·커밋·통합.

## 머지 판단
- **머지 필요**: Yes — 단, `merge_policy: none` → **팀장 머지 안 함. ANU가 재검증·머지·CF Pages 배포**.
- **브랜치**: task/task-2887-dev1 (62da2c1 → e9a3fe8 push 완료)
- **워크트리 경로**: /home/jay/projects/InsuRo/.worktrees/task-2887-dev1
- **머지 의견**: webapp-only 5파일(src/lib+src/components+tests/e2e), 서버/확장/마이그레이션 불변. tsc 0·전체 vitest 1094 passed 회귀 0·e2e 케이스 C clean worktree PASS·스크린샷 회장 요구사항 정합. 충돌 위험 낮음(순수 계산+렌더). ANU가 PR 원격 CI(e2e 포함) GREEN 재확인 후 머지 → `source .env.keys && wrangler pages deploy dist --project-name insuro --branch main` 배포 권장.

## 비고 (순서 B)
봇=push+.done까지. 검증·머지·배포는 ANU(독립 재검증). 회장 rollout(CF Pages 프로덕션 배포)은 ANU 소관.

## 세션 통계
- 총 도구 호출: 0회

