- 루트의 IMPROVEMENT-PHASE-*.md, PHASE-*.md 등 45개 → docs/history/로 이동 - docs/block-tests/ 오래된 블록 테스트 HTML 삭제 (figma_to_html_agent로 대체) - docs/figma-analysis/, docs/figma-assets/, docs/figma-screenshots/ 정리 - docs/test-*.html 등 초기 테스트 파일 정리 - 참고 페이지/ 스크린샷 정리 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
322 lines
11 KiB
Markdown
322 lines
11 KiB
Markdown
# Phase 2 실행 프로세스
|
||
|
||
## 절대 규칙 (모든 작업에 적용)
|
||
|
||
```
|
||
🔴 단발성/하드코딩 금지 — 모든 구현은 N개, M종류에 범용 동작
|
||
🔴 회귀 금지 — Phase 1 확정 구조(catalog 매핑, grid 분리, Kei API 우선) 되돌리지 않음
|
||
🔴 Opus→Sonnet 대체 금지 — Kei API가 기본, Sonnet은 fallback만
|
||
🔴 "일단 돌아가게" 금지 — 설계대로 구현하거나 설계를 먼저 변경
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 1 완료 자산
|
||
|
||
| 항목 | 수량/상태 |
|
||
|------|----------|
|
||
| 블록 라이브러리 | 46개 (6 카테고리) |
|
||
| catalog.yaml | 46개 (when/not_for/slots/height_cost) |
|
||
| BLOCK_SLOTS + _apply_defaults | 46개 동기화 |
|
||
| SVG premium | venn-diagram 3개 고정 검증 |
|
||
| 5단계 파이프라인 | 동작 (BF-4~10 수정) |
|
||
| Kei API 연동 | 1단계(실장) + 3단계(편집자) |
|
||
| grid 역할 분리 | BF-9 (코드가 grid, Sonnet은 blocks만) |
|
||
| catalog→renderer 매핑 | mtime 캐시 (BF-10) |
|
||
|
||
---
|
||
|
||
## 실행 순서
|
||
|
||
```
|
||
Phase 2-A (FAISS 블록 검색)
|
||
↓
|
||
Phase 2-B (SVG N개 자동 배치) ← 2-A와 병렬 가능
|
||
↓
|
||
Phase 2-D (5단계 재검토 강화) ← 2-A/2-B와 병렬 가능
|
||
↓
|
||
Phase 2-E (누락 기능: Pillow, details-block)
|
||
↓
|
||
Phase 2-C (Step A: Opus + FAISS) ← 2-A 완료 필수
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2-A: FAISS 블록 검색 인덱스
|
||
|
||
### 목적
|
||
팀장(Step B) 프롬프트에 46개 catalog 전문 대신, FAISS 검색으로 **관련 블록 5~8개만** 전달.
|
||
|
||
### 수정 파일
|
||
| 파일 | 변경 | 신규/수정 |
|
||
|------|------|---------|
|
||
| `src/block_search.py` | FAISS 인덱스 구축 + 검색 함수 | **신규** |
|
||
| `src/design_director.py` | `_load_catalog()` → 검색 결과로 교체 | 수정 (line 294) |
|
||
| `data/block_index.faiss` | 인덱스 파일 | **신규** |
|
||
| `data/block_metadata.json` | id→블록 매핑 | **신규** |
|
||
| `pyproject.toml` | faiss-cpu, sentence-transformers 의존성 | 수정 |
|
||
|
||
### 기술 상세
|
||
```
|
||
임베딩 모델: BAAI/bge-m3 (1024차원)
|
||
→ Kei persona에서 검증됨 (retriever.py line 49)
|
||
→ 한국어 최적화
|
||
|
||
인덱스 방식: faiss.IndexFlatIP (Inner Product = 코사인 유사도)
|
||
→ Kei와 동일 패턴 (retriever.py line 88)
|
||
|
||
검색 입력: 꼭지별 title + summary + layer + role
|
||
검색 출력: 상위 8개 블록 (id + visual + when + not_for + slots)
|
||
|
||
fallback: FAISS 인덱스 없거나 검색 실패 시 → catalog.yaml 전문 (기존 방식)
|
||
```
|
||
|
||
### 프로세스
|
||
```
|
||
1. scripts/build_block_index.py 실행 (1회성)
|
||
→ catalog.yaml 읽기
|
||
→ 각 블록의 (name + visual + when) 임베딩
|
||
→ data/block_index.faiss + data/block_metadata.json 생성
|
||
|
||
2. src/block_search.py
|
||
→ 서버 시작 시 인덱스 로드
|
||
→ search_blocks(query, top_k=8) → 관련 블록 목록 반환
|
||
|
||
3. src/design_director.py 수정
|
||
→ _load_catalog() 대신 search_blocks() 호출
|
||
→ 꼭지별 검색 → 카테고리별 최소 1개 보장 → 프롬프트에 삽입
|
||
```
|
||
|
||
### 충돌 검토
|
||
```
|
||
design_director.py _load_catalog(): 문자열 반환 → 문자열 반환 (인터페이스 동일) ✅
|
||
renderer.py _load_catalog_map(): 별도 함수, 영향 없음 ✅
|
||
content_editor.py: BLOCK_SLOTS만 참조, 영향 없음 ✅
|
||
pipeline.py: create_layout_concept() 인터페이스 동일 ✅
|
||
```
|
||
|
||
### 점검
|
||
- [ ] FAISS 실패 시 catalog 전문 fallback 동작하는가?
|
||
- [ ] 검색 결과에 카테고리별 최소 1개 보장되는가?
|
||
- [ ] 블록 60개로 늘어나도 인덱스 재구축만으로 동작하는가?
|
||
|
||
---
|
||
|
||
## Phase 2-B: SVG N개 자동 배치
|
||
|
||
### 목적
|
||
venn-diagram의 원 3개 고정 → N개(2~7) 자동 배치. cos/sin 수학적 계산.
|
||
|
||
### 수정 파일
|
||
| 파일 | 변경 | 신규/수정 |
|
||
|------|------|---------|
|
||
| `src/svg_calculator.py` | 좌표 계산 함수 | **신규** |
|
||
| `src/renderer.py` | venn-diagram 렌더링 전 좌표 전처리 | 수정 (render_multi_page 내) |
|
||
| `templates/blocks/visuals/venn-diagram.html` | 하드코딩 좌표 → 동적 `{{ item.cx }}` | 수정 |
|
||
|
||
### 기술 상세
|
||
```
|
||
src/svg_calculator.py:
|
||
calc_circle_positions(n, center_x, center_y, radius) → [{cx, cy}, ...]
|
||
→ angle = (2π × i / n) - π/2 (12시부터 시계방향)
|
||
→ 의존성: Python math (내장)
|
||
|
||
calc_circle_radius(n) → int
|
||
→ n≤3: 120, n≤5: 80, n≤7: 60
|
||
→ 하드코딩 아님: base_radius / (1 + (n-3)*0.15) 공식
|
||
|
||
calc_outer_circle(n) → int
|
||
→ 큰 원 반지름도 N에 따라 조정
|
||
|
||
renderer.py 수정:
|
||
render_multi_page() 안에서 block_type == "venn-diagram" 일 때:
|
||
1. items = block_data.get("items", [])
|
||
2. positions = calc_circle_positions(len(items))
|
||
3. for i, item in enumerate(items): item["cx"] = positions[i]["cx"]
|
||
4. 나머지는 Jinja2가 처리
|
||
|
||
venn-diagram.html 수정:
|
||
현재: cx="265" (하드코딩)
|
||
변경: cx="{{ item.cx }}" (동적)
|
||
fallback: items에 cx가 없으면 기존 3개 고정 좌표 사용
|
||
```
|
||
|
||
### 충돌 검토
|
||
```
|
||
renderer.py: render_multi_page()에 if 분기 추가 — 기존 흐름에 영향 없음 ✅
|
||
(venn-diagram 아닌 블록은 그대로 통과)
|
||
venn-diagram.html: Phase 1 고정 SVG → 동적으로 변경
|
||
→ fallback(cx 없으면 기존 좌표) 필수 ✅
|
||
pipeline.py: 변경 없음 ✅
|
||
content_editor.py: items[].cx는 renderer에서 추가, 편집자는 모름 ✅
|
||
```
|
||
|
||
### 점검
|
||
- [ ] N=2, 3, 4, 5, 6, 7 각각 렌더링 테스트
|
||
- [ ] items에 cx/cy가 없을 때 Phase 1 고정 SVG로 fallback
|
||
- [ ] 원끼리 겹침 없이 배치되는가? (N=7 특히)
|
||
- [ ] 큰 원 안에 모든 작은 원이 들어가는가?
|
||
|
||
---
|
||
|
||
## Phase 2-D: 5단계 재검토 강화
|
||
|
||
### 목적
|
||
_review_balance가 실질적으로 동작하도록 강화. shrink/rewrite 구현.
|
||
|
||
### 수정 파일
|
||
| 파일 | 변경 | 신규/수정 |
|
||
|------|------|---------|
|
||
| `src/pipeline.py` | _review_balance 프롬프트 + _apply_adjustments 3개 action | 수정 |
|
||
|
||
### 기술 상세
|
||
```
|
||
_review_balance 개선:
|
||
현재: 블록별 데이터 양(글자수)만 전달
|
||
변경: 블록별 (area + type + 데이터 양 + height_cost) 전달
|
||
+ 전체 zone 예산 대비 사용량
|
||
|
||
_apply_adjustments 개선:
|
||
현재: expand만 동작 (char_guide * 1.5)
|
||
변경:
|
||
expand: char_guide * 1.5 (현재와 동일)
|
||
shrink: char_guide * 0.7 (신규)
|
||
rewrite: block["data"] 제거 → fill_content 재호출 시 재작성 (신규)
|
||
|
||
재조정 루프:
|
||
MAX_ADJUSTMENTS = 2 (상수, 하드코딩 아닌 설정값)
|
||
for attempt in range(MAX_ADJUSTMENTS): ...
|
||
```
|
||
|
||
### 충돌 검토
|
||
```
|
||
pipeline.py 내부 함수만 수정 ✅
|
||
fill_content 재호출: Kei API 우선 (Phase 1에서 수정됨) ✅
|
||
renderer.py: 변경 없음 ✅
|
||
```
|
||
|
||
### 점검
|
||
- [ ] expand/shrink/rewrite 3개 action 모두 동작하는가?
|
||
- [ ] MAX_ADJUSTMENTS 초과 시 루프 종료되는가?
|
||
- [ ] fill_content 재호출이 Kei API를 거치는가? (Sonnet 직접 아닌지)
|
||
- [ ] rewrite 후 _apply_defaults로 빈 데이터가 처리되는가?
|
||
|
||
---
|
||
|
||
## Phase 2-E: 누락 기능
|
||
|
||
### E-1: Pillow 이미지 크기
|
||
| 파일 | 변경 |
|
||
|------|------|
|
||
| `src/design_director.py` | create_layout_concept() 내 이미지 크기 확인 |
|
||
|
||
```
|
||
수정 위치: topics 순회할 때 content_type=="image" 확인
|
||
→ Pillow Image.open().size로 width, height 읽기
|
||
→ topic에 image_width, image_height, image_ratio 추가
|
||
→ Step B 프롬프트에 이미지 크기 정보 포함
|
||
→ 팀장이 가로형→image-full, 세로형→image-side-text 판단 가능
|
||
|
||
fallback: 이미지 파일 없으면 → 기본 비율 1.5 (가로형 가정)
|
||
⚠️ 이것은 하드코딩이 아닌 "정보 부재 시 안전한 기본값"
|
||
```
|
||
|
||
### E-2: details-block 연결
|
||
| 파일 | 변경 |
|
||
|------|------|
|
||
| `src/design_director.py` | detail_target 꼭지를 "생략" → "details-block 배치"로 |
|
||
| `src/content_editor.py` | detail_target 꼭지에 summary + detail 두 버전 작성 |
|
||
|
||
```
|
||
현재: design_director.py에서 detail_target 꼭지를 "생략 (미구현)"으로 처리
|
||
변경: detail_target 꼭지를 details-block으로 body/sidebar에 배치
|
||
→ 편집자가 summary(3줄) + detail(전체) 작성
|
||
→ renderer가 <details>/<summary>로 조립
|
||
```
|
||
|
||
### 점검
|
||
- [ ] 이미지 없는 콘텐츠에서 Pillow 에러 안 나는가?
|
||
- [ ] detail_target 꼭지가 details-block으로 렌더링되는가?
|
||
- [ ] <details> 접기/펼치기가 브라우저에서 동작하는가?
|
||
- [ ] 인쇄 시 자동 펼침 JavaScript가 동작하는가?
|
||
|
||
---
|
||
|
||
## Phase 2-C: Step A Opus+FAISS
|
||
|
||
### 목적
|
||
규칙 4줄 → Opus가 FAISS 검색으로 구조/블록 선정 + 배치/크기 결정.
|
||
|
||
### 수정 파일
|
||
| 파일 | 변경 | 신규/수정 |
|
||
|------|------|---------|
|
||
| `src/design_director.py` | select_preset() 유지 + _opus_block_selection() 추가 | 수정 |
|
||
|
||
### 기술 상세
|
||
```
|
||
현재 흐름:
|
||
Step A: select_preset() → 규칙 기반 (코드)
|
||
Step B: Sonnet → 블록 매핑
|
||
|
||
Phase 2 흐름:
|
||
Step A-1: select_preset() → 프리셋 선택 (유지, 안정적)
|
||
Step A-2: _opus_block_selection() → Kei API(Opus)로 블록 후보 선정
|
||
입력: 꼭지 분석 + FAISS 검색 결과
|
||
출력: 각 꼭지에 추천 블록 + 배치 방향 + 크기 가이드
|
||
Step B: Sonnet → Opus 추천 기반으로 최종 매핑 + 글자수 가이드
|
||
|
||
핵심: Opus 호출은 반드시 Kei API 경유
|
||
→ kei_client.py의 _call_kei_api() 패턴 재사용
|
||
→ anthropic.AsyncAnthropic 직접 호출 절대 금지
|
||
```
|
||
|
||
### 의존성
|
||
```
|
||
Phase 2-A 완료 필수 (FAISS 인덱스 + search_blocks 함수)
|
||
Kei API(localhost:8000) 안정 동작 필요
|
||
```
|
||
|
||
### 충돌 검토
|
||
```
|
||
select_preset(): 유지 (삭제하지 않음) ✅
|
||
create_layout_concept(): Step A-2 결과를 Step B에 전달하는 구조 추가
|
||
→ 기존 인터페이스(return {"title": ..., "pages": [...]}) 동일 ✅
|
||
pipeline.py: create_layout_concept() 호출 방식 동일 ✅
|
||
```
|
||
|
||
### 점검
|
||
- [ ] Opus 호출이 Kei API를 거치는가? (`grep "AsyncAnthropic" → fallback만`)
|
||
- [ ] Kei API 실패 시 현재 방식(규칙+Sonnet)으로 fallback
|
||
- [ ] FAISS 검색 결과가 Opus에게 전달되는가?
|
||
- [ ] select_preset()이 삭제되지 않았는가? (안정적 규칙은 유지)
|
||
|
||
---
|
||
|
||
## 산출물 체크리스트
|
||
|
||
### 코드 파일
|
||
```
|
||
신규:
|
||
src/block_search.py ← 2-A
|
||
src/svg_calculator.py ← 2-B
|
||
scripts/build_block_index.py ← 2-A
|
||
data/block_index.faiss ← 2-A
|
||
data/block_metadata.json ← 2-A
|
||
|
||
수정:
|
||
src/design_director.py ← 2-A, 2-C, 2-E
|
||
src/renderer.py ← 2-B
|
||
src/pipeline.py ← 2-D, 2-E
|
||
templates/blocks/visuals/venn-diagram.html ← 2-B
|
||
pyproject.toml ← 2-A (의존성)
|
||
```
|
||
|
||
### 문서
|
||
```
|
||
docs/PHASE2-PLAN.md ← 완료
|
||
docs/PHASE2-PROCESS.md ← 이 파일
|
||
docs/PHASE2-TECH-REVIEW.md ← 완료
|
||
PLAN.md ← Phase 2 태스크 추가 필요
|
||
PROGRESS.md ← Phase 2 진행 상황 추적
|
||
```
|