---
task_id: "2863"
team: 2팀
level: 3
priority: P2
scope: task
status: completed
depends_on: ["2862"]
---

# task-2863 — 웹앱 상담이력 통합 매트릭스 (담보명·가입한도 왼쪽 1회, 조합은 회사·보험료만)

## SCQA 요약
- **S**: task-2862가 상담이력 최적조합을 1사/2사/3사 **3개의 별도 Card 테이블**로 만들었고, 각 테이블이 `담보명|가입한도|회사|보험료`를 **중복** 표기함.
- **C**: 회장 refinement — 담보명·가입한도는 제일 왼쪽에 **한 번만** 고정, 각 조합은 **회사·보험료만** 표시(오마이매니저 비교표 구조). 단 webapp-only(서버/확장 변경 0)·합계는 서버 total 단일소스 유지.
- **Q**: 서버 변경 없이 웹앱 렌더만으로 3타일 → 단일 통합 매트릭스로 재구성 가능한가?
- **A**: 가능. `CompositeComboResult`를 단일 `<table data-testid="composite-matrix">`로 리팩터링. tsc 0에러·912/912 전체 회귀·프로덕션 빌드 성공.

## Situation
InsuRo "분석&도구 · 복합설계 계산기" 상담이력 화면의 최적조합(1·2·3사)이 task-2862에서 3개 Card 테이블로 렌더되어 담보명·가입한도가 조합마다 반복됨.

## Complication
- 담보명·가입한도 중복 → 회장 refinement 대상. 오마이매니저 비교표처럼 좌측 담보축 고정 + 조합별 회사·보험료 열이 목표.
- webapp-only 제약(서버/확장/config/integrations 변경 0). 합계는 서버 total 단일소스 유지(행 재계산 금지).
- task-2862 산출물(resolveCoverageName 코드→이름, honest-sign 절감, plan_name 헤더, 선택담보 테이블)은 유지·재사용해야 함.

## Question
서버 변경 없이 웹앱 렌더 재구성만으로 단일 통합 매트릭스를 달성할 수 있는가?

## Answer
가능. `CompositeComboResult` 컴포넌트만 리팩터링(+co-located 테스트 2종 갱신). 서버/확장 변경 0.

---

## 구현 내역

### 통합 매트릭스 리팩터링 — `src/components/composite/CompositeComboResult.tsx`
- 기존 3-Card 그리드(SingleBestTile + MultiBestTile×2) **삭제**, 단일 `<div className="overflow-x-auto"><table data-testid="composite-matrix">`로 대체.
- **좌측 고정 2열(sticky)**: `담보명`(`sticky left-0`) · `가입한도`(`sticky left-[7rem]`) — rowSpan=2, 불투명 배경(`bg-background`)+z-index로 가로 스크롤 시 겹침 없이 고정. 담보 1건당 좌측에 **1회만** 렌더.
- **thead 2행**: (1행) 담보명·가입한도(rowSpan) + 존재하는 각 조합 그룹 헤더(colSpan=2) = 조합명 + 보험사 구성(1사=insurer / 2·3사=insurers.join(" + ")) + **절감 뱃지**(2·3사, saving_pct>0만) + **honest-sign 소형 텍스트**(1사 대비 N원 절감/1사와 동일/1사보다 N원 비쌈). (2행) 각 조합 `회사`·`보험료` 서브헤더.
- **tbody(담보축 1행씩)**: 담보명(좌측 sticky, resolveCoverageName) · 가입한도(단일소스) · 각 조합의 `회사`·`보험료`. 그 조합에 없는 담보 셀은 **"-"**.
  - **담보축 산출**: 참조축 = 존재하는 첫 조합(single>dual>triple). 다른 조합이 참조축에 없는 담보(coverage_cd)를 가지면 뒤에 append(중복 없이, 순서 보존). coverage_cd 키 맵 O(1) 조회.
  - **가입한도 단일소스**: 조합 순회(참조축 우선)에서 처음 발견되는 amount 채택. 전부 없으면 "-".
- **tfoot 2행**: (합계행) `월보험료 합계` + 각 조합 **서버 total 단일소스**(`combo-total-single/dual/triple`, 행 재계산 금지). (20년 절감행, 합계행 **아래**) `20년(240개월) 총 납입보험료 절감` + 1사="기준" / 2·3사=savingAmount>0이면 `savingAmount*240`(초록 강조) 그 외 "-"(`year20-saving-dual/triple`).
- honest-sign(부호정직)·resolveCoverageName 로직 **의미 변경 없음**(위치/렌더만 변경). 미사용화된 Card/CardContent/CardHeader import 및 SingleBestTile/MultiBestTile 함수 제거(tsc/lint 클린, 전 코드베이스 잔여 참조 0 확인).

### 테스트 갱신
- `CompositeComboResult.test.tsx` — 3타일 구조 결합 테스트를 **통합 매트릭스 구조**로 재작성(8 케이스): 통합표 렌더/좌측 담보명 1회/조합 헤더·보험사구성·절감뱃지/합계행 서버total/20년절감행/담보축 정렬 "-"/null 폴백/dual-only(single없음 20년 "-")/honest-sign 3분기/빈배열·필드누락 크래시 없음.
- `ConsultationHistoryView.test.tsx` — 통합테스트가 참조하던 옛 testid(`single-best-total`/`single-best-table`/`dual-best-table`/`triple-best-table`) → 새 testid(`combo-total-single/dual/triple`, `composite-matrix`)로 갱신(회귀 수정).

---

## 수정/생성 파일 목록 (전부 allowed_resources 범위 내)
- `src/components/composite/CompositeComboResult.tsx` (수정 — 통합 매트릭스)
- `src/components/composite/__tests__/CompositeComboResult.test.tsx` (테스트 재작성)
- `src/components/composite/__tests__/ConsultationHistoryView.test.tsx` (통합테스트 testid 갱신 — 회귀 수정)
- 서버/확장/config/integrations/ConsultationHistoryView 본체 변경 **0**.

## 새 testid
- `composite-matrix` (단일 통합 테이블)
- `combo-total-single` / `combo-total-dual` / `combo-total-triple` (합계행 = 서버 total)
- `year20-saving-dual` / `year20-saving-triple` (20년 절감행)
- 기존 `composite-combo-result` 컨테이너 + null 폴백 문자열("조합 계산 결과가 없습니다") 그대로 유지(e2e 의존).

## 테스트 결과
- `npx tsc --noEmit`: **0 에러**
- 전체 회귀: **912 passed / 912 (62 files)**, 신규 실패 0
- 옛 testid 잔존(src): **0** (grep 재확인)

## L1 스모크테스트 결과
- **서버 재시작**: 해당없음 (webapp-only, 서버/확장 변경 0)
- **API 응답 확인**: 해당없음 (서버 변경 0)
- **빌드**: **성공** — `npm run build` (Vite) ✓ built in 12.49s, PWA 173 entries, dist/ 2026-07-26 14:55 갱신
- **컴포넌트 DOM 렌더**: jsdom+React Testing Library로 통합 매트릭스 실제 DOM(thead 2행/tbody 담보축/tfoot 합계·20년행/sticky class/testid/담보축 정렬·"-"/honest-sign) **912/912** 검증
- **스크린샷**: 없음 — 실 브라우저 인증 E2E는 incar JWT + 라이브 데이터 필요(auth-gated 페이지). **미통과(사유: 인증/라이브 환경)**. task 완료 순서 B에 따라 실 브라우저 E2E·머지·CF Pages 배포는 ANU 담당.

## 발견 이슈 및 해결
- **회귀 1건**: `ConsultationHistoryView.test.tsx`가 옛 combo testid를 참조 → 전체 스위트 1 fail. 새 매트릭스 testid로 갱신하여 해소(912/912 green). 보고서 작성 전 완결.

## 머지 판단
- **머지 필요**: Yes — 단, **merge_policy=none** → 머지·Codex 검토·CF Pages 배포는 **ANU** 담당(순서 B).
- **브랜치**: task/task-2863-dev2 (origin push 완료, HEAD=850c270)
- **워크트리 경로**: /home/jay/projects/InsuRo/.worktrees/task-2863-dev2
- **base**: origin/main=01b070a (task-2862 #135 머지 후)
- **머지 의견**: webapp-only 3파일(컴포넌트 1 + 테스트 2), 전부 allowed_resources 내. tsc 0·912/912·빌드 성공. 서버/확장 불변. 충돌 위험 낮음(선행 task-2862 코드 위에 얹음, 담보축 정렬/honest-sign 로직 의미 불변).

## 모델 사용 기록
- 프레이야(프론트엔드) — **sonnet**: 통합 매트릭스 구현 + 테스트 재작성 + 회귀 수정. haiku 미사용.
- 팀장(오딘, Opus): 설계·검토·독립검증(tsc/전체회귀/빌드/diff scope)만 수행, 직접 코딩 없음.

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

