Phase P~S 전체 작업물: 검증 스크립트, 블록 템플릿, 설계 문서, 코드 수정

포함 내용:
- Phase P/Q/R/S 설계 문서 (IMPROVEMENT-PHASE-*.md)
- 영역별 검증 스크립트 (scripts/verify_*.py, test_*.py)
- 블록 템플릿 추가 (cards, emphasis 변형)
- 코드 수정: block_search, content_editor, design_director, slide_measurer
- catalog.yaml 블록 목록 업데이트
- CLAUDE.md, PROGRESS.md, README.md 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 08:38:06 +09:00
parent 0e4b8c091c
commit 29f56187c0
44 changed files with 9431 additions and 313 deletions

View File

@@ -1,176 +1,155 @@
# Phase P: 블록 재구성 + 실제 렌더링 비교 선택
> 작성일: 2026-03-27
> 상태: 계획 수립 중 (사용자 승인 대기)
> 상태: 계획 확정 (사용자 승인 완료, 실행 대기)
> 선행 완료: Phase O (컨테이너 기반 레이아웃)
---
## 핵심 원칙
**"블록을 컨테이너에 맞게 재구성하고, 실제 렌더링해보고, Kei가 목적에 맞는 것을 고른다."**
**"블록을 컨테이너에 맞게 재구성하고, 실제 렌더링해보고, Kei가 스크린샷을 보고 목적에 맞는 것을 고른다."**
```
컨테이너 58px 확정 (Phase O)
컨테이너 px 확정 (Phase O)
후보 3개 선택 (FAISS 2개 + Opus 1개)
3개 블록을 각각 58px에 맞게 재구성
(폰트, 패딩, 항목 수, 레이아웃 변형)
3개 블록을 컨테이너 크기에 맞게 재구성 (폰트/패딩/항목수/레이아웃 — 동적 계산)
재구성된 3개를 실제 렌더링 (Selenium)
Kei가 3개 각각에 맞게 텍스트 편집
Kei가 "당초 목적에 가장 적합한 것은?" 선택 → 최종 1개
3개 실제 렌더링 (Selenium) + 스크린샷 캡처 (.png)
Kei가 스크린샷을 보고 "당초 목적에 가장 적합한 것" 선택
전부 안 맞으면 정확도 가장 높은 것으로 배치
```
---
## 이것이 해결하는 문제
## 해결하는 문제 (14건)
| 문제 | 해결되나 |
|------|-----------|
| P-1: 키워드 반응으로 잘못된 블록 선택 | 3개를 실제 렌더링해서 Kei가 목적 기준으로 최종 선택. 키워드 반응으로 골라도 목적에 안 맞으면 탈락 |
| P-4: 블록이 콘텐츠 의미 왜곡 | compare-pill-pair에 "혼용 문제"를 넣어봤더니 의미가 안 맞으면 Kei가 탈락시킴 |
| P-5/6/7: height_cost 부정확 | catalog의 height_cost를 믿지 않음. 실제 렌더링으로 확인 |
| P-8: 58px에 콘텐츠 전달 불가 | 블록을 58px에 맞게 재구성하므로 어떤 블록이든 넣을 수 있음 |
| P-14: 피드백 루프 무력 | 처음부터 맞는 걸 고르니까 피드백 필요성 감소 |
| # | 문제 | 해결 방법 |
|---|------|---------|
| P-1 | Kei가 표면 키워드 반응하여 블록 선택 | 후보 3개 렌더링 Kei가 스크린샷 보고 목적 기준으로 최종 선택 |
| P-2 | purpose_fit 위반 블록 통과 | Kei가 스크린샷으로 판단하므로 부적합 블록 탈락 |
| P-3 | 같은 블록 반복 사용 | 같은 컨테이너 topic을 함께 보여주고 "서로 다른 블록 선택" 명시 |
| P-4 | 블록이 콘텐츠 의미 왜곡 | 왜곡된 결과를 Kei가 스크린샷으로 보고 탈락 |
| P-5 | compare-pill-pair height_cost 부정확 | catalog를 믿지 않음. 실제 렌더링으로 확인. 추가로 Selenium 실측 스크립트로 전체 검증 |
| P-6 | banner-gradient height_cost 부정확 | P-5와 동일 |
| P-7 | 38개 전체 height_cost 미검증 | Selenium 실측 검증 스크립트 작성 → catalog 갱신 |
| P-8 | 배경 58px에 콘텐츠 전달 불가 | ① 블록을 58px에 맞게 재구성 ② Kei가 비중 판단 시 topic 수 고려 ③ 또는 Kei가 topic을 합침 |
| P-9 | sidebar 490px에 247px만 사용 | P-10 해결 시 공간 활용도 상승 |
| P-10 | 용어 정의 빈약 (출처 누락) | EDITOR_PROMPT 하드코딩 제거 완료. 컨테이너 제약에 맞게 Kei가 편집 |
| P-11 | EDITOR_PROMPT 하드코딩 분량 | **해결 완료** |
| P-12 | 블록 추천 프롬프트가 의미/논리 구조 미전달 | 후보 3개 렌더링 + Kei 스크린샷 판단으로 대체. 프롬프트 정확도에 의존하지 않음 |
| P-13 | 스크린샷 .txt로 저장 | base64 디코딩하여 .png 파일로 저장 |
| P-14 | 피드백 루프에 블록 교체 기능 없음 | 처음부터 3개 렌더링해서 맞는 걸 고르므로 피드백 부담 감소 |
---
## 단계별 상세
### P-Step 1: 후보 선택
각 topic에 대해 후보 3개를 뽑는다.
**FAISS 2개:**
- 기존 `search_blocks_for_topics()`로 검색
- topic의 purpose, relation_type, expression_hint를 쿼리에 포함 (이미 Phase M에서 구현)
- 상위 2개 선택
**Opus 1개:**
- 기존 `_opus_block_recommendation()`에서 Kei가 추천한 블록
- 도메인 지식 + 콘텐츠 성격 기반
**중복 제거:** FAISS 결과에 Opus 추천이 이미 포함되어 있으면 FAISS 3번째를 올림. 항상 서로 다른 3개.
### P-Step 2: 블록 재구성
각 후보 블록을 해당 topic의 **컨테이너 크기에 맞게 재구성**한다.
**재구성 항목:**
- 폰트 크기: 컨테이너 높이에 따라 조정 (Phase O `_determine_typography()` 사용)
- 패딩: 컨테이너 높이에 따라 조정
- 항목 수: 컨테이너에 들어가는 만큼만 (Phase O `_calculate_block_constraints()` 사용)
- 글자 수: 항목당 최대 글자 수 계산
- 레이아웃: 세로 → 가로 전환 등 (컨테이너 가로/세로 비율에 따라)
**이 단계에서 텍스트도 채운다:**
- 각 후보 블록의 슬롯에 맞게 Kei 편집자가 텍스트를 채움
- 3개 블록 × 같은 원본 텍스트 → 3개 다른 편집 결과
- 또는: 1회 편집 후 3개 블록 슬롯에 재배치 (API 호출 절약)
### P-Step 3: 실제 렌더링
재구성된 3개 블록을 각각 Selenium으로 렌더링한다.
**측정 항목:**
- 실제 높이(scrollHeight) vs 컨테이너 높이
- overflow 여부
- 렌더링 결과 스크린샷 (base64 PNG)
**렌더링 방법:**
- `render_standalone_block(block_type, data)`로 각 블록 단독 렌더링
- 컨테이너 크기를 감싼 div 안에서 렌더링
- `slide_measurer.py`의 기존 Selenium 인프라 재사용
### P-Step 4: Kei 최종 선택
렌더링된 3개 결과를 Kei(Opus)에게 보여주고 최종 선택.
**Kei에게 전달하는 정보:**
- 이 topic의 원래 목적 (purpose, relation_type, expression_hint)
- 3개 렌더링 결과 (스크린샷 이미지 또는 HTML 요약)
- 각 블록의 overflow 여부
- 원본 콘텐츠
**Kei 판단 기준:**
1. 당초 목적에 적합한가? (문제 제기인데 비교 블록이면 부적합)
2. 콘텐츠 의미가 왜곡되지 않는가?
3. 컨테이너에 맞게 렌더링되었는가?
**Kei API 호출:** 1회. 3개를 한꺼번에 보여주고 1개 선택.
---
## 파이프라인 흐름 변경
## 실행 파이프라인
```
현재:
1A → 1B → 컨테이너 → A-2(Kei 1개 확정) → 블록 스펙 → 3(편집) → 4(렌더링) → 측정 → 5(검수)
Step 1: Kei 분석 (기존 그대로)
1A: classify_content() → topics, page_structure(비중)
1B: refine_concepts() → relation_type, expression_hint
컨테이너 계산: calculate_container_specs() → 역할별 px 확정
Phase P 후:
1A → 1B → 컨테이너
→ 각 topic마다:
후보 3개 (FAISS 2 + Opus 1)
→ 3개 재구성 (컨테이너에 맞게)
→ 3개 렌더링 (Selenium)
→ Kei 최종 선택 (목적 적합성)
→ 선택된 블록으로 전체 슬라이드 조립
→ 4(CSS 조정 + 최종 렌더링)
→ 측정
→ 5(검수)
Step 2: 후보 선택 (Kei API 1회)
FAISS가 topic당 상위 2개 자동 검색
+ Opus에게 전체 topic을 한꺼번에 보여주고 각각 1개 추천
= topic당 후보 3개 (중복 시 FAISS 3번째로 대체)
Step 3: 블록 재구성 + 텍스트 편집 (Kei API 5회)
각 topic마다:
3개 후보를 컨테이너 크기에 맞게 재구성
(폰트/패딩/항목수/레이아웃 — Phase O 동적 계산)
Kei 편집자에게 "3개 블록 슬롯 각각에 맞게 텍스트 편집" 1회 호출
Step 4: 실제 렌더링 (Selenium 15회, 병렬)
15개 후보 블록을 각각 컨테이너 안에서 렌더링
스크린샷 캡처 (base64 → .png 파일 저장)
Step 5: Kei 최종 선택 (Kei API 3회)
같은 컨테이너의 topic을 묶어서 제시:
1회차: 배경 (topic 1+2) — 스크린샷 6개, "서로 다른 블록 선택" 명시
2회차: 본심 (topic 3) + 첨부 (topic 4) — 스크린샷 6개
3회차: 결론 (topic 5) — 스크린샷 3개
Kei가 각 topic별 "당초 목적에 가장 적합한 것" 선택
전부 안 맞으면 정확도 가장 높은 것으로 배치
Step 6: 전체 슬라이드 조립 (기존 그대로)
선택된 블록으로 4단계 (CSS 조정 + 렌더링)
Phase L 측정
5단계 Kei 검수
```
---
## 기존 코드 영향
## 비용
| 파일 | 변경 |
|------|------|
| `pipeline.py` | Step A-2 단일 선택 → topic별 3후보 + 렌더링 + Kei 선택 루프로 변경 |
| `design_director.py` | `_opus_block_recommendation()` — 기존 유지. 추가로 단일 topic 추천 함수 필요 |
| `block_search.py` | 기존 유지. topic별 상위 2개 추출 함수 추가 |
| `renderer.py` | `render_standalone_block()` — 기존 유지. 컨테이너 감싼 렌더링 함수 추가 |
| `slide_measurer.py` | 기존 유지. 단일 블록 높이 측정 함수 추가 |
| `space_allocator.py` | `finalize_block_specs()` — 기존 유지. 후보별 스펙 계산에 재사용 |
| `content_editor.py` | 기존 유지. 후보별 텍스트 채우기에 재사용 |
| `kei_client.py` | 3후보 비교 선택 프롬프트 함수 신규 |
| 항목 | 횟수 | 시간 |
|------|------|------|
| Kei API (기존 1A+1B+3+5) | 4회 | ~4분 |
| Step 2: Opus 추천 배치 | 1회 | ~30초 |
| Step 3: 텍스트 편집 배치 | 5회 | ~2.5분 |
| Step 5: 최종 선택 | 3회 | ~1.5분 |
| Step 4: Selenium 렌더링 | 15회 (병렬) | ~0.8초 |
| Step 6: CSS + 검수 | 2회 | ~2분 |
| **총합** | **~15회** | **~10.5분** |
---
## 하드코딩 검증
## 하드코딩 없음 검증
| 항목 | 하드코딩? | 근거 |
|------|---------|------|
| 후보 수 3개 (FAISS 2 + Opus 1) | 구조적 설계 | 블록 추가해도 변경 불필요. 3개는 비교에 적절한 수 |
| 블록 재구성 (폰트/패딩/항목수) | 동적 계산 | Phase O `_determine_typography()`, `_calculate_block_constraints()` 사용 |
| 후보 수 3개 (FAISS 2 + Opus 1) | 구조적 설계 | 블록 추가해도 변경 불필요 |
| 블록 재구성 | 동적 계산 | Phase O `_determine_typography()`, `_calculate_block_constraints()` |
| 텍스트 분량 | 동적 계산 | 컨테이너 제약에서 자동 산출 |
| 최종 선택 | Kei 판단 | 코드가 선택하지 않음 |
| 컨테이너 크기 | Kei 비중에서 동적 계산 | Phase O `calculate_container_specs()` |
| 컨테이너 크기 | 동적 계산 | Kei 비중에서 자동 산출 |
| 최종 선택 묶음 (2+2+1) | 동적 그룹핑 | 컨테이너 역할별 자동 |
| 배경 topic 수 처리 | Kei 판단 | Kei가 비중 판단 시 topic 수 고려. 코드가 비중 덮어쓰지 않음 |
---
## 비용 분석
## 기존 코드 변경 범위
| 항목 | 현재 | Phase P 후 |
|------|------|-----------|
| Kei API 호출 | 1A + 1B + A-2 + 3(편집) + 5(검수) = 5회 | + topic별 최종 선택 1회 × 5topics = +5회. 총 ~10회 |
| Selenium 렌더링 | 1회 (전체 슬라이드) | + topic별 3후보 × 5topics = +15회. 단독 블록이라 빠름 (~50ms/회) |
| Sonnet 호출 | 4단계 CSS 1회 | 변경 없음 |
| 총 시간 증가 | — | Selenium +750ms, Kei API +5회 (각 ~30초) ≈ +2.5분 |
| 파일 | 변경 | 신규/수정 |
|------|------|---------|
| `pipeline.py` | Step A-2 단일 선택 → Step 2~5 루프로 교체 | 수정 |
| `block_search.py` | topic별 상위 2개 반환 함수 | 추가 |
| `design_director.py` | 전체 topic 배치 Opus 추천 함수 | 추가 |
| `renderer.py` | 컨테이너 감싼 단독 블록 렌더링 함수 | 추가 |
| `slide_measurer.py` | 단독 블록 스크린샷 캡처 함수 + .png 저장 | 추가 |
| `kei_client.py` | 3후보 스크린샷 비교 선택 프롬프트 함수 | 추가 |
| `content_editor.py` | 3블록 한꺼번에 편집 프롬프트 | 수정 |
| `space_allocator.py` | 기존 그대로 (재사용) | 변경 없음 |
| `catalog.yaml` | 기존 그대로 | 변경 없음 |
---
## 미해결 사항 (사용자 논의 필요)
## 중간 산출물 추가
1. **3개 후보에 텍스트를 어떻게 채우나?**
- 방법 A: Kei 편집자를 3회 호출 (각 블록 슬롯에 맞게) — 정확하지만 느림
- 방법 B: 1회 편집 후 3개 블록 슬롯에 기계적 재배치 — 빠르지만 슬롯 구조가 다르면 부정확
- 방법 C: Kei 편집자 1회 호출에 "3개 블록 각각의 슬롯에 맞게" 한꺼번에 요청 — 중간
| 파일 | 내용 |
|------|------|
| `step2_candidates.json` | topic별 3개 (FAISS 2 + Opus 1) |
| `step3_edited_variants.json` | topic별 3개 블록의 편집된 텍스트 |
| `step4_candidate_screenshots/` | 15개 .png 스크린샷 |
| `step5_selection.json` | Kei 최종 선택 결과 (topic별 선택 블록 + 이유) |
2. **Kei 최종 선택에 스크린샷을 보여줄까, HTML 요약을 보여줄까?**
- 스크린샷: Opus 멀티모달로 실제 렌더링을 보고 판단 — 정확
- HTML 요약: 텍스트 기반 — 빠르지만 시각 판단 불가
---
3. **3개 후보가 전부 목적에 안 맞으면?**
- Kei가 "없음"을 선택할 수 있게 할까? 그러면 추가 후보를 어디서?
## 충돌/회귀 검증
| 체크 항목 | 결과 |
|----------|------|
| 기존 함수 호환 | ✅ render_standalone_block, search_blocks, measure_rendered_heights, capture_slide_screenshot 전부 존재 |
| Phase O 컨테이너 시스템 | ✅ 그대로 사용. calculate_container_specs, finalize_block_specs 재사용 |
| Phase N fallback 제거 | ✅ 회귀 없음. fallback 코드 재도입 안 함 |
| Phase N 무한 재시도 | ✅ 유지. 모든 Kei API 호출에 적용 |
| 하드코딩 | ✅ 없음. 모든 수치가 동적 계산 또는 Kei 판단 |
| EDITOR_PROMPT | ✅ 하드코딩 분량 이미 제거됨 |