# Phoenix 평가엔진 · 디자인 → CLI 구현 핸드오프 가이드

> 본 문서는 `design/` 디렉터리의 5개 정적 시안(DSN-01 ~ DSN-05)을 Claude Code CLI에서 실제 코드로 구현할 때 참조해야 하는 결정 사항을 한 곳에 모은 핸드오프 가이드입니다. **본 세션은 정적 HTML/CSS 시안까지만**이며, 알고리즘·데이터 연동·서버 구현은 별도 진행됩니다.

---

## 0. TL;DR — 구현팀이 먼저 알아야 할 5가지

1. **5 화면 모두 같은 디자인 토큰을 공유한다.** `:root` CSS 변수만 분리해서 `tokens.css`로 추출하면 brand-dna.json 확정 시 한 번에 교체 가능.
2. **Rails로 갈지 Next.js로 갈지 결정 안 됨.** 본 시안은 양쪽 모두에 무난하게 매핑되도록 작성. `phoenix_design/` 폴더는 이미 Rails 베이스가 있으므로 거기서 출발하는 것을 권장.
3. **차트 라이브러리는 ECharts 우선 권장 (D3는 옵션).** 이유: ECharts는 PDF 출력 품질·한국어 툴팁·내장 인터랙션이 우수. D3는 정밀 컨트롤이 필요한 워터폴(DSN-05)에서 보강.
4. **모든 GA 데이터는 익명화 default.** 실명 모드는 별도 권한 토큰 + audit log. 코드/뷰 어디서도 실명을 하드코딩 금지.
5. **brand-dna.json (DSN-00)은 다른 트랙에서 진행 중.** 확정 전까지는 본 시안의 임시 토큰을 그대로 쓰고, 확정 후 PR 1개로 토큰만 교체.

---

## 1. 산출 파일 매핑표

| 화면 ID | 이슈 | HTML | Spec | 연계 알고리즘 |
|---|---|---|---|---|
| DSN-01 | ISS-041 | `01_dashboard_main.html` | `01_dashboard_main.spec.md` | F1 |
| DSN-02 | ISS-043 | `02_layer1_retention.html` | `02_layer1_retention.spec.md` | E1 (Layer 1) |
| DSN-03 | ISS-045 | `03_scenario_compare.html` | `03_scenario_compare.spec.md` | G1 |
| DSN-04 | ISS-047 | `04_watchlist.html` | `04_watchlist.spec.md` | B3 |
| DSN-05 | ISS-049 | `05_fair_value.html` | `05_fair_value.spec.md` | F2 / F3 |
| 인덱스 | ISS-051 | `index.html` | `HANDOFF.md` (현재 문서) | — |

각 spec.md는 다음을 포함합니다:
- 데이터 계약(JSON 스키마 예시)
- 상호작용·상태 명세
- 가독성·접근성 룰
- 핸드오프 체크리스트

**구현 시작 전 반드시 해당 화면의 spec.md를 먼저 읽으세요.**

---

## 2. 디자인 토큰 — 한 파일로 추출하기

5개 HTML의 `<style>:root { ... }` 블록은 거의 동일합니다. 다음 단계로 통합하세요:

### Step 1 — `tokens.css` 추출
```css
/* app/assets/stylesheets/tokens.css (Rails) 또는 src/styles/tokens.css (Next.js) */
:root {
  /* Color */
  --hero: #1F4E79;
  --hero-deep: #143656;
  --accent: #2E844A;
  --accent-soft: #ECFDF5;
  --warn: #B95000;
  --warn-soft: #FFF6EE;
  --danger: #BA0517;
  --danger-soft: #FEF1F2;
  --gold: #F08A3E;
  --gold-soft: #FFF4E5;

  --surface: #FFFFFF;
  --surface-alt: #F7F9FB;
  --surface-mute: #EEF1F4;
  --border: #DDE2E7;
  --border-strong: #C9D1D9;

  --text-primary: #161B22;
  --text-secondary: #4B5563;
  --text-muted: #6B7280;
  --text-inverse: #FFFFFF;

  /* Type */
  --font-body: -apple-system, BlinkMacSystemFont, "Pretendard", "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  --fs-display: 28px;
  --fs-h1: 22px;
  --fs-h2: 17px;
  --fs-body: 14px;
  --fs-small: 12px;
  --fs-micro: 11px;

  /* Shape (moderate) */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(15,23,42,.05);
  --shadow-md: 0 4px 16px rgba(15,23,42,.06), 0 1px 2px rgba(15,23,42,.04);
  --shadow-lg: 0 12px 32px rgba(15,23,42,.10);

  /* Motion */
  --t-fast: 120ms ease-out;
  --t-mid: 200ms ease-out;
}

/* Scenario palette (DSN-03) */
:root {
  --opt:  var(--accent);
  --base: var(--hero);
  --pess: var(--warn);
}
```

### Step 2 — Tailwind 통합 (선택)
`phoenix_design/` 베이스가 Tailwind를 쓰는 경우, `tailwind.config.js`에 매핑:
```js
theme: {
  extend: {
    colors: {
      hero:    { DEFAULT: '#1F4E79', deep: '#143656' },
      accent:  { DEFAULT: '#2E844A', soft: '#ECFDF5' },
      warn:    { DEFAULT: '#B95000', soft: '#FFF6EE' },
      danger:  { DEFAULT: '#BA0517', soft: '#FEF1F2' },
      gold:    { DEFAULT: '#F08A3E', soft: '#FFF4E5' },
    },
    fontFamily: {
      body: ['Pretendard', 'system-ui', 'sans-serif'],
      mono: ['"JetBrains Mono"', '"SF Mono"', 'monospace'],
    },
    borderRadius: {
      sm: '6px', md: '10px', lg: '14px', xl: '20px',
    },
  }
}
```

### Step 3 — brand-dna.json 확정 시 교체 절차
1. `brand-dna.json`의 `design_tokens` 필드 확정
2. `tokens.css` 또는 `tailwind.config.js`의 값을 brand-dna 값으로 교체
3. PR 1개로 통합 (HTML/ERB/JSX 코드는 변경 없음)
4. 시각 회귀 테스트 (Percy/Chromatic) 권장

---

## 3. 컴포넌트 분해 — 재사용 가능한 단위

### 공통 컴포넌트 (5 화면 전반)

| 컴포넌트 | 사용 화면 | Props |
|---|---|---|
| `<Sidebar>` | 5/5 | `activeKey: string` |
| `<TopBar>` | 5/5 | `crumbs: BreadcrumbItem[]`, `actions: ReactNode` |
| `<KpiCard>` | DSN-01, DSN-02, DSN-04 | `label, value, delta?, tone, lead?` |
| `<MetaTag>` | 5/5 | `tone?: 'ok'\|'warn'\|'danger'\|'info'`, `dot?: bool` |
| `<Pill>` | 5/5 | `tone, children` |
| `<Badge>` | DSN-01, DSN-04 | `tone, children` |
| `<SegmentedToggle>` | DSN-01, DSN-04 | `options: Item[], value, onChange` |
| `<DocNote>` | 5/5 | `children` |

### 화면별 핵심 컴포넌트

| 컴포넌트 | 화면 | 비고 |
|---|---|---|
| `<RecommendationHero>` | DSN-01 | 권고 인수가 + 레인지 트랙 + 4 stat |
| `<LayerCard>` | DSN-01 | 6 Layer 카드 (spotlight prop) |
| `<RetentionCurve>` | DSN-02 | ECharts/D3 곡선 + ±15% band |
| `<CohortHeatmap>` | DSN-02 | 4×5 매트릭스 |
| `<ScenarioCard>` | DSN-03 | 비관/기준/낙관 카드 |
| `<DriverDecomp>` | DSN-03 | 드라이버 분해 표 |
| `<Tornado>` | DSN-03 | 민감도 가로 bar |
| `<Stage0Ceiling>` | DSN-03 | 한도 띠 + NPV 마커 |
| `<WatchTable>` | DSN-04 | 가상화 권장 (30+행) |
| `<Sparkline>` | DSN-04 | 8 포인트 mini line |
| `<DistressSignalChip>` | DSN-04 | 카탈로그 7종 |
| `<FvWaterfall>` | DSN-05 | 13행 워터폴 (D3 권장) |
| `<NegotiationPanel>` | DSN-05 | 6 인풋 + debounce 재계산 |
| `<RecommendationBand>` | DSN-05 | 가격 범위 + Walk-away/Top-up |
| `<ConsumerProtectionList>` | DSN-05 | 7 보호장치 체크 |
| `<FinalRecommendationCard>` | DSN-05 | gold gradient hero |

---

## 4. 차트 라이브러리 결정

| 화면 | 차트 | 권장 | 이유 |
|---|---|---|---|
| DSN-02 | 회차별 유지율 곡선 | **ECharts** | 한국어 툴팁 / PDF 우수 / mark line 내장 |
| DSN-02 | 코호트 히트맵 | ECharts heatmap 또는 plain CSS table | CSS table이 더 가벼움 |
| DSN-03 | Tornado | plain CSS (현재 시안 그대로) | 라이브러리 불필요 |
| DSN-03 | Stage 0 ceiling | plain CSS (현재 시안 그대로) | 라이브러리 불필요 |
| DSN-04 | Sparkline | inline SVG (현재 시안) 또는 ECharts mini | SVG 8포인트면 충분 |
| DSN-05 | 워터폴 | **D3** 또는 ECharts waterfall | 13행 + spotlight + 동적 폭. D3 더 유연 |

**최종 권고**:
- 1순위 — ECharts (DSN-02 곡선·히트맵)
- 2순위 — D3 (DSN-05 워터폴)
- 나머지는 plain CSS / inline SVG

---

## 5. 데이터 연동 가드라인

### API 응답 스키마는 spec.md를 따른다
각 화면의 spec.md §3에 JSON 예시가 있습니다. 백엔드 구현팀에 OpenAPI 작성 요청 시 그 예시를 그대로 출발점으로 쓰세요.

### 응답 캐시 전략
| 데이터 | TTL | 비고 |
|---|---|---|
| 평가 결과 (DSN-01) | 1시간 | 모델 버전·기준일·시나리오별 키 |
| Layer 1 곡선 (DSN-02) | 1시간 | 코호트별 키 (4축 조합) |
| 시나리오 비교 (DSN-03) | 1시간 | 동일 평가 결과에서 파생 |
| Watch List (DSN-04) | 5분 | 자금난 신호 실시간성 우선 |
| Fair Value (DSN-05) | 평가 시점 고정 | 협상 인풋 변경은 클라이언트 재계산 또는 서버 idempotent recompute |

### 비동기 잡
- 백테스트 재실행 (DSN-02): Sidekiq/Celery, 30s+
- LOI 초안 생성 (DSN-05): PDF 생성 비동기, 진행 화면 별도

### Polling vs WebSocket
- 자금난 신호 자동 수집 (DSN-04): 일 1회 cron으로 충분. WebSocket 불필요.
- 백테스트 진행도: poll 5초 간격 (or WS 옵션).

---

## 6. 가독성·접근성 절대 룰 (slds-ui-readability)

전 화면 공통:

- ✅ **모든 색상 정보는 텍스트 또는 모양과 동시 표기** (색맹 대응)
- ✅ **form 필드 보더는 1px 명시색** (회색 음영만 금지)
- ✅ **카드 보더 1px 명시** (그림자만으로 경계 표현 금지)
- ✅ **font-size 11px 미만 절대 금지** (`--fs-micro` 11px가 최소)
- ✅ **다크 배경 위 텍스트는 `--text-inverse` 명시 사용**
- ✅ **키보드 포커스: 인풋·셀렉트·버튼 모두 `:focus` 시 outline 3px 청색 ring**
- ✅ **SVG 차트는 `role="img"` + `aria-label` 필수**
- ✅ **테이블은 `<th scope>` + 정렬 가능 시 `aria-sort`**

---

## 7. 익명화 규약 (전역 CLAUDE.md)

- 모든 GA는 **"후보 GA #N"** 라벨로 표시 (실명 금지)
- API 응답에서 `label`은 항상 익명. `real_name`은 권한 토큰 보유 시에만 별도 endpoint로 fetch
- CSV/PDF 내보내기는 **익명화 default**. 실명 export는 확인 다이얼로그 + audit log + 워터마크
- 인수가·보험사 협상 내용은 권한 격벽 (CFO/대표만)
- 자금난 신호의 출처(특정 뉴스 기사 등)는 메모 모달에서만, 테이블 직접 표시 금지

---

## 8. 화면당 주요 CTA (brand-dna 미확정 단계 룰)

`brand-dna.user_decision_clarity` — 화면당 주요 CTA 1개 이상.

| 화면 | 주요 CTA | 보조 CTA |
|---|---|---|
| DSN-01 Dashboard | "Stage 0 보고서 생성" | PDF 내보내기, 재평가 실행 |
| DSN-02 Layer 1 | "백테스트 재실행" | ← Dashboard, CSV 내보내기 |
| DSN-03 Scenarios | "시나리오 편집" | ← Dashboard, PDF |
| DSN-04 Watch List | "신규 GA 추가" | CSV, 필터 저장 |
| DSN-05 Fair Value | "LOI 초안 생성" | ← Dashboard, 방법론 부속서 |

---

## 9. v0 → v1 → v2 단계별 보강 계획

### v0 (현재 시안)
- ✅ 5 화면 정적 HTML/CSS
- ✅ 임시 토큰 (brand-dna 미확정)
- ✅ 더미 데이터

### v1 (CLI 구현 후)
- [ ] brand-dna.json 토큰 교체
- [ ] 실데이터 연동 (모델 v0.4 출력)
- [ ] 차트 라이브러리(ECharts/D3) 통합
- [ ] 7 보호장치 연동 (#4 Fair Value 공시는 여전히 ⚠ 잠정)

### v2 (보험계리사 검증 후)
- [ ] Fair Value v1 모델 (외부 검증 통과)
- [ ] 7 보호장치 모두 ✓ 전환
- [ ] LOI 초안 자동 생성 (PDF 템플릿 + 권고가 변수)
- [ ] 외부 자문 PDF 출력 품질 보강 (A3 가로 1p)
- [ ] 다국어 (영문 보고용)

---

## 10. 구현 순서 권고

다음 순서로 진행하면 의존성 충돌이 없습니다:

```
1. tokens.css 추출 + 공통 컴포넌트 (Sidebar/TopBar/KpiCard/Pill/Badge)
   ↓
2. DSN-04 Watch List (테이블 베이스 — 데이터 모델·필터 검증)
   ↓
3. DSN-01 Dashboard (Watch List에서 진입하는 핵심 화면)
   ↓
4. DSN-02 Layer 1 (차트 라이브러리 결정 + 통합)
   ↓
5. DSN-03 Scenarios (Dashboard 데이터 재활용)
   ↓
6. DSN-05 Fair Value (가장 복잡 — 워터폴·재계산)
```

**근거**: Watch List → Dashboard → Layer 검증 → 시나리오 → Fair Value 라는 사용자 여정 순서와 일치하며, 각 단계가 이전 단계의 데이터·컴포넌트를 재사용합니다.

---

## 11. 체크리스트 — 첫 PR을 올리기 전에

- [ ] `tokens.css` 추출 완료 + tailwind.config 매핑
- [ ] 공통 컴포넌트(Sidebar/TopBar/...) 8개 모두 구현·Storybook 등록
- [ ] 각 화면 spec.md의 §3 데이터 계약을 OpenAPI로 작성
- [ ] 가독성·접근성 룰 자동 점검 (axe-core 또는 Lighthouse)
- [ ] 익명화 default 동작 검증 (실명 모드 토글 + audit log)
- [ ] 시각 회귀 테스트 셋업 (Percy/Chromatic, 5 화면 baseline)
- [ ] 차트 라이브러리 결정 + 의존성 lock
- [ ] PDF 출력 품질 검증 (A3 가로 1p × 5 화면)

---

## 12. 참고

- 마스터 보고서: `01_마스터_보고서.md`
  - 6 Layer 정의: §2.5 / §3 평가모델 트랙
  - Stage 0 한도(30~50억): Executive Summary
  - 7 소비자 보호 장치: §3.3 Track B
- 아젠다: `00_아젠다.md`
  - DSN-01~06 코드 매핑: §7
- 전역 디자인 가이드:
  - `harness-ui-trends-2026` (전역 트렌드)
  - `slds-ui-readability` (가독성 절대 룰)
  - `brand-dna.json` (확정 후 우선 적용)

문의: 본 핸드오프에서 답을 못 찾으면 → 해당 화면의 `spec.md` → `01_마스터_보고서.md` 순으로 추적하세요. 그래도 모호하면 `BRAND_GUARD` 또는 `ARCH_DECISION` 이슈 생성.
