# task-2853 보고서 — 웹앱 상담이력 카드 → 1·2·3사 최적조합 표시

- 팀: dev1-team (헤르메스 팀장 / 이리스 프론트 / 아르고스 테스터)
- 레벨: Lv.4 | merge_policy: none (ANU가 원격 검증·머지·CF Pages 배포)
- 브랜치: `task/task-2853-dev1` | base: `origin/main` (d2de7b6)
- worktree: `/home/jay/workspace/projects/insuro/.worktrees/task-2853-dev1`

## Situation
파일럿 왕복(캡처→저장→조회)은 성공했으나, insuro.biz `ConsultationHistoryView`(상담이력 카드)가 **저장된 담보 원본만 나열**하고 회장이 원하는 **1사/2사/3사 최적조합**을 표시하지 못했다. 데이터(레코드 `analysis_matrix` 격자)와 계산 엔진(`POST /api/insuro/composite-calculate-from-matrix`)은 이미 존재.

## Complication
- 상담이력 카드는 per-record로 계산 엔드포인트를 호출해야 하며, hydrate/loading/error 상태 관리가 필요.
- **서버 변경 0** 제약 — src/만 수정.
- 기존 vpath/crm-vpath e2e가 "분석 매트릭스"/"보험사 2곳"의 `toBeVisible()`를 검사 → 매트릭스 요약을 접이식으로 숨기면 **회귀**. (설계 시 이 리스크를 선제 식별해 방지)

## Question
저장된 per-FA 스코프 레코드의 `analysis_matrix`+`selected_coverages`만으로, 서버 변경 없이, 기존 회귀 0으로 1·2·3사 조합을 상담이력 카드에 렌더할 수 있는가?

## Answer (구현 요약)
### 신규 `src/components/composite/CompositeComboResult.tsx` (205줄)
- 타입: `ComboCoverage` / `ComboSingleBest` / `ComboMultiBest` / `CompositeMatrixResponse` (서버 스키마 그대로).
- `calculateCompositeFromMatrix(matrix, selectedCoverages, getHeaders)` — `POST /api/insuro/composite-calculate-from-matrix` 호출. `getHeaders` 주입형(테스트 용이). `!res.ok` → throw.
- `CompositeComboResult({ result })` — 1사(최저가·총액)/2사·3사(조합·총액·`saving_pct` 절감배지·1사 대비 절감액)·담보별 배정 접이식. 전 필드 방어(옵셔널 체이닝·Array.isArray). 전부 null → "조합 계산 결과가 없습니다".

### 수정 `src/components/composite/ConsultationHistoryView.tsx` (+134/-11)
- `getAuthHeader()` 로컬 정의(HistoryReassignDialog 패턴 동일, supabase 세션 → publishable key fallback).
- `ConsultationHistoryView`/`ConsultationHistoryCard`/`ConsultationRecordCard`에 **옵셔널** `calculateCombo` prop 추가. 미전달 시 `defaultCalculateCombo`(= `record.analysis_matrix`/`record.selected_coverages ?? null` + `getAuthHeader`)로 실제 서버 호출. → 기존 호출부(CompositeDesign.tsx, CrmCustomers.tsx) **무수정 자동 호환**.
- `CompositeComboSection`: 마운트 시 자동 hydrate. loading(`최적 조합 계산 중…`)·error(`조합 계산 실패` + `다시 계산` 재시도 버튼)·success(`CompositeComboResult`). `aborted` 플래그 언마운트 안전, retryKey deps.
- `hasMatrix` 레코드 → **주 표시**=1·2·3사 조합, 그 아래 **분석 매트릭스 요약은 가시 유지**(e2e 회귀 방지). matrix 없으면 기존 `PerInsurerBlock` fallback.

### 안전(임의 matrix 주입 금지)
matrix는 반드시 이미 per-FA 스코프된 GET 레코드(`record.analysis_matrix`)에서만 가져와 계산 엔드포인트에 전달.

## 변경 파일 (git diff --stat vs origin/main)
- `src/components/composite/CompositeComboResult.tsx` (신규, +205)
- `src/components/composite/ConsultationHistoryView.tsx` (+134/-11)
- `src/components/composite/__tests__/CompositeComboResult.test.tsx` (신규, +120)
- `src/components/composite/__tests__/ConsultationHistoryView.test.tsx` (+123)
- `tests/e2e/composite-design-vpath.spec.ts` (+56)
- **서버/extension/.github 변경 0** — allowed_resources 준수.

## 테스트 결과
- **vitest**: `CompositeComboResult.test.tsx`(4) + `ConsultationHistoryView.test.tsx`(9) = **13/13 PASS**.
  - 정상 3조합 렌더 / 계산 실패 error 상태 / 매트릭스 요약 가시 유지(회귀) / 순수 컴포넌트 정상·null·single 없음·coverages 누락 방어.
- **npx tsc (goal_assertion)**: exit 0. 관련 파일 에러 0. (기존 무관 파일 사전 에러는 base에도 존재)
- **npm run build**: 성공 (built in 11.58s, exit 0).

## L1 스모크테스트 결과 (실제 브라우저)
- **서버 재시작**: 해당없음(프론트, 서버 변경 0). 대신 vite dev(:8080) + Playwright 실브라우저 렌더.
- **API 응답 확인**: e2e에서 `composite-calculate-from-matrix` 라우트 스텁 → 실제 fetch 왕복 후 조합 렌더 확인.
- **스크린샷**: `tests/e2e/screenshots/vpath/a-2-record-rendered.png`
  - 실브라우저 렌더 확인: 1사 최저가(테스트생명 50,000원) / 2사 조합(테스트생명+샘플화재, 10.0% 절감, 45,000원, 1사 대비 5,000원 절감) / 3사 조합(+제3보험사, 20.0% 절감, 40,000원, 1사 대비 10,000원 절감) / 담보별 배정 접이식 / 분석 매트릭스 요약 보조 유지.
- **e2e (composite-design-vpath)**: 케이스 A(flag ON, 1·2·3사 조합 가시) + 케이스 B(flag OFF 403 준비중 안내) **2/2 PASS** (7.5s). legacy link POST 0회 유지 → 회귀 0.

## 발견 이슈 및 해결
- **(설계 단계 선제 식별)** e2e가 매트릭스 요약 `toBeVisible()`를 검사 → 접이식 은닉 시 회귀. → 조합을 주 표시로 추가하되 매트릭스 요약은 가시 유지하는 설계로 회귀 원천 차단. e2e 실행으로 검증 완료.
- e2e 케이스 A에 `composite-calculate-from-matrix` 라우트 스텁을 catch-all보다 나중 등록(Playwright LIFO)해 실 조합 렌더까지 검증.

## 머지 판단
- **머지 필요**: Yes (단, merge_policy=none → ANU가 원격 검증·머지·CF Pages 배포 소유)
- **브랜치**: `task/task-2853-dev1`
- **워크트리**: `/home/jay/workspace/projects/insuro/.worktrees/task-2853-dev1`
- **머지 의견**: diff는 src/+tests/만(서버 0). vitest 13/13, tsc 0, build OK, 실브라우저 e2e 2/2 PASS(회귀 0). 회장 요구(1·2·3사 조합 표시) 스크린샷으로 실증. 안전(per-FA 스코프 matrix만 사용). 충돌 위험 낮음.

## 모델 사용 기록
- 이리스(프론트 구현): sonnet — 일반 코딩/로직.
- 아르고스(테스트 작성): sonnet — 테스트 로직.
- haiku 미사용.

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

