# Phase R': 접근 C 기반 — 블록 CSS 참고 + AI 구조 결정

> 작성일: 2026-03-30
> 상태: 설계
> 선행: Phase P(20점), Q(블록 선택 개선), R(실패 — 기존 구조 위에 패치)
> 근거: C_reference.png + hybrid_simulation.png 시뮬레이션 검증

---

## 1. Phase P, Q, R에서 무엇이 문제였는가

**P, Q, R 전부 같은 구조:**
```
블록을 선택한다 → 그 블록의 슬롯에 텍스트를 채운다
```

이 구조의 근본 한계:
- **블록이 구조를 결정한다.** 콘텐츠가 아니라 블록 템플릿이 HTML 구조를 고정.
- topic 1개 = 블록 1개. 합침/분리 불가.
- 38개 고정 템플릿에 없는 구조(포함 관계, Before→After 등)는 표현 불가.
- 콘텐츠 전달 의도(expression_hint)가 HTML 구조에 반영되지 않음.

Phase Q에서 블록 선택 정확도를 올렸고, Phase R에서 variant를 추가했지만,
**근본 구조("블록 선택 → 슬롯 채우기")는 P = Q = R 동일.**
결과물도 동일 수준.

---

## 2. 접근 C: 무엇이 달라지는가

### 핵심 전환

```
현재 (P=Q=R):
  블록이 구조를 결정 → 콘텐츠를 슬롯에 채움
  (블록 중심)

접근 C:
  콘텐츠가 구조를 결정 → 블록 CSS를 참고하여 HTML 생성
  (콘텐츠 중심)
```

### "블록을 참고하여 만든다"의 정확한 의미

블록을 버리는 것이 아니다. 블록을 "선택"하는 것도 아니다.

| | 현재 (블록 선택) | 접근 C (블록 참고) |
|---|---|---|
| 블록의 역할 | HTML 구조를 결정하는 템플릿 | CSS 스타일(색상, 폰트, 배경, radius)의 참고 자료 |
| 누가 구조를 결정 | 블록 템플릿 (Jinja2) | AI가 콘텐츠 전달 의도를 보고 결정 |
| topic 합침/분리 | 불가 (1 topic = 1 블록) | 가능 (AI가 판단) |
| 포함 관계 시각화 | 해당 블록이 없으면 불가 | AI가 큰 박스 안에 카드를 넣는 구조를 직접 생성 |
| CSS 일관성 | 블록 템플릿이 보장 | 디자인 토큰 + 블록 CSS 참고로 보장 |

### C_reference.png에서 증명된 것

제가 수동으로 했던 판단:
1. topic 1(문제제기) + topic 2(사례) → **1영역에 통합**, dark-bullet-list의 CSS 색상 사용
2. 사례 2건 → **가로 2열 카드**, 같은 구조로 나란히
3. topic 3(핵심) → **DX 큰 박스 안에 GIS/BIM/DT 카드** = 포함 관계 시각화
4. "BIM ≠ DX" → **별도 강조 박스** (topic에 없는 요소를 추가)
5. sidebar 용어 → **풀 정의 + 출처**, card-numbered의 CSS 스타일 참고

**이 판단들을 AI가 하는 것이 접근 C.**

---

## 3. 프로세스 설계

### 기존 프로세스 (P=Q=R)

```
1단계: Kei 분석 (topics, relation_type, expression_hint, page_structure)
1.5단계: Kei 컨셉 구체화 (source_data)
컨테이너 계산 (비중 → px)
프리셋 선택 (sidebar-right 등)

2단계: 블록 선택 (block_selector → catalog → Kei 선택)     ← 여기가 문제
3단계: 텍스트 채우기 (fill_candidates → 슬롯에 텍스트)      ← 여기가 문제

4단계: CSS 조정 + 렌더링
검증: Selenium 측정
품질 게이트: 비전 모델
```

### 접근 C 프로세스

```
1단계: Kei 분석 (동일)
1.5단계: Kei 컨셉 구체화 (동일)
컨테이너 계산 (동일)
프리셋 선택 (동일)

2-3단계 통합: AI가 HTML 구조를 직접 생성                    ← 여기가 바뀜
  입력: Kei 분석 결과 + 원본 텍스트 + 디자인 토큰 + 블록 CSS 참고 + 컨테이너 스펙
  출력: 각 컨테이너 영역의 HTML (슬라이드 body, sidebar, footer)

  AI가 결정하는 것:
    - 각 topic을 어떤 구조로 보여줄지 (불릿? 비교? 포함관계? 카드?)
    - topic을 합칠지 분리할지
    - 핵심 메시지를 별도 강조할지
    - 텍스트를 어떻게 배치할지

  AI가 참고하는 것:
    - 디자인 토큰 (CSS 변수 — 색상, 폰트, 간격)
    - 기존 블록의 CSS 패턴 (다크 배경, 카드 스타일, 배너 스타일 등)
    - 컨테이너 크기 (px)
    - expression_hint ("포함 관계 시각화", "프로세스 변화")
    - 예시 슬라이드 (few-shot)

4단계: 렌더링 (AI가 생성한 HTML을 슬라이드 프레임에 삽입)
검증: Selenium 측정 (동일)
품질 게이트: 비전 모델 (동일)
```

### 변경되는 것 / 변경되지 않는 것

| 항목 | 변경 여부 |
|------|----------|
| 1단계 Kei 분석 | 변경 없음 |
| 1.5단계 컨셉 구체화 | 변경 없음 |
| 컨테이너 계산 (space_allocator) | 변경 없음 |
| 프리셋 선택 | 변경 없음 |
| **2단계 블록 선택 (block_selector)** | **제거** — AI가 직접 구조 결정 |
| **3단계 슬롯 채우기 (fill_candidates)** | **제거** — AI가 HTML에 텍스트 직접 포함 |
| 4단계 CSS 조정 | 변경 — AI 생성 HTML을 프레임에 삽입 |
| Selenium 측정 | 변경 없음 |
| 비전 모델 품질 게이트 | 변경 없음 |
| slide-base.html | 변경 없음 |
| tokens.css, base.css | 변경 없음 |

---

## 4. AI HTML 생성의 구체적 설계

### 4-1. AI에게 주는 입력

```
1. 원본 콘텐츠 (MDX 텍스트 전체)
2. Kei 분석 결과:
   - topics[] (id, title, purpose, relation_type, expression_hint, source_data)
   - page_structure (본심/배경/첨부/결론 비중)
   - core_message
3. 컨테이너 스펙:
   - 각 역할별 높이(px), 너비(px)
   - 프리셋 (sidebar-right, two-column 등)
4. 디자인 토큰 (tokens.css 전문)
5. 블록 CSS 패턴 참고 (주요 블록의 CSS만 발췌):
   - 다크 배경 패턴 (.block-dark-bullets의 색상/배경)
   - 카드 패턴 (.cid-card의 border/radius/padding)
   - 배너 패턴 (.block-banner-grad의 gradient)
   - 비교 패턴 (.block-comparison의 좌우 분할)
   - 테이블 패턴 (.block-table-striped의 헤더/행)
6. 예시 슬라이드 2-3개 (few-shot):
   - C_reference.png의 HTML
   - hybrid_simulation의 HTML
   - (다른 콘텐츠 예시)
```

### 4-2. AI에게 요구하는 출력

```
각 영역(body, sidebar, footer)의 HTML 조각.
슬라이드 프레임(slide-base.html)에 삽입할 수 있는 형태.

{
  "body_html": "<div class=\"container-배경\">...</div><div class=\"container-본심\">...</div>",
  "sidebar_html": "<div class=\"sidebar-label\">용어 정의</div><div class=\"def-item\">...</div>...",
  "footer_html": "<div class=\"block-banner-grad\"><div class=\"bg-text\">...</div></div>"
}
```

### 4-3. AI HTML 생성 규칙 (프롬프트에 포함)

```
## 규칙

1. 원본 텍스트를 그대로 사용한다 (자유도 15-20). 축약은 공간 부족 시에만.
2. 디자인 토큰(CSS 변수)만 사용한다. 하드코딩 색상/폰트 금지.
3. 블록 CSS 패턴을 참고하되, 구조는 콘텐츠에 맞게 자유롭게 구성한다.
4. 컨테이너 높이를 초과하지 않는다.
5. expression_hint를 반드시 반영한다:
   - "포함 관계" → 큰 박스 안에 작은 카드
   - "프로세스 변화" → Before→After 구조
   - "독립 사례 나열" → 가로 카드 비교
6. topic을 합치거나 분리할 수 있다 (page_structure의 역할 기준).
7. 핵심 메시지(core_message)는 별도 강조 요소로 만들 수 있다.
```

### 4-4. 품질 보장 (5중 방어)

```
Layer 1: 디자인 토큰 제약 (CSS 변수만 사용)
Layer 2: 컨테이너 크기 제약 (px 명시)
Layer 3: HTML 정화 (nh3 — script 태그 등 제거)
Layer 4: Selenium 측정 (overflow 감지)
Layer 5: 비전 모델 품질 게이트 (시각적 평가)
```

---

## 5. 구현 스텝

| 스텝 | 내용 | 파일 | 비고 |
|------|------|------|------|
| R'-1 | 디자인 토큰 + 블록 CSS 패턴을 프롬프트용 텍스트로 추출 | 신규 `src/design_tokens.py` | tokens.css + 주요 블록 CSS 발췌 |
| R'-2 | few-shot 예시 슬라이드 정리 (2-3개) | `data/examples/` | C_reference.html, hybrid_simulation.html |
| R'-3 | AI HTML 생성 함수 구현 | 신규 `src/html_generator.py` | Kei 분석 + 토큰 + 예시 → HTML 생성 |
| R'-4 | pipeline.py 2-3단계를 html_generator로 교체 | `src/pipeline.py` | block_selector, fill_candidates 호출 제거 |
| R'-5 | 렌더러에 AI 생성 HTML 삽입 함수 추가 | `src/renderer.py` | 기존 render_slide와 별도로 render_slide_from_html 추가 |
| R'-6 | HTML 정화 + 토큰 위반 검증 | 신규 `src/html_validator.py` | nh3 + tinycss2 |
| R'-7 | 테스트 (2개 콘텐츠로 검증) | `scripts/test_phase_r_prime.py` | DX 이해 + DX 목표 |

### 의존 관계

```
R'-1 (토큰 추출) ──→ R'-3 (HTML 생성)
R'-2 (예시 정리) ──→ R'-3
                     R'-3 ──→ R'-4 (파이프라인 교체) ──→ R'-7 (테스트)
                     R'-3 ──→ R'-5 (렌더러 추가)
R'-6 (검증) ←── 독립, R'-4와 병렬
```

---

## 6. pipeline.py 변경 상세 — 정확히 어디가 교체되는가

### 유지되는 코드 (1단계 ~ 컨테이너 계산: 줄 96~165)

```python
# 줄 96~109: 1단계 Kei 분석 — 유지
analysis = await _retry_kei(classify_content, content)
_save_step(run_dir, "step1_analysis.json", analysis)

# 줄 111~120: 1.5단계 컨셉 구체화 — 유지
analysis = await refine_concepts(content, analysis)
_save_step(run_dir, "step1b_concepts.json", ...)

# 줄 122~140: 제목 중복 검증, 이미지 측정 — 유지

# 줄 142~164: 컨테이너 스펙 계산 — 유지
preset_name = select_preset(analysis)
preset = LAYOUT_PRESETS.get(preset_name, {})
container_specs = calculate_container_specs(...)
_save_step(run_dir, "step1c_containers.json", ...)
```

### 제거되는 코드 (2-3단계: 줄 166~339)

```python
# 줄 166~207: Phase Q 블록 선택 — 전체 제거
#   block_selector.select_block_candidates()
#   select_fallback_candidates()
#   calculate_budgets_for_candidates()

# 줄 209~257: Kei 블록 선택 — 전체 제거
#   select_block_for_topics()
#   selected_blocks 딕셔너리 생성
#   finalize_block_specs()

# 줄 259~298: layout_concept 조립 — 전체 제거
#   final_blocks 리스트, sidebar label, 역할 순서 배치

# 줄 300~339: fill_candidates 호출 — 전체 제거
#   topic별 fill_candidates()
#   결과 검증
```

### 교체되는 코드 (R'-4에서 신규 작성)

```python
# 2-3단계 통합: AI HTML 생성 (줄 166~ 교체)
yield {"event": "progress", "data": "2/5 슬라이드 HTML 생성 중..."}

from src.html_generator import generate_slide_html
from src.html_validator import validate_html

# AI가 HTML 직접 생성
generated = await generate_slide_html(
    content=content,
    analysis=analysis,
    container_specs=container_specs,
    preset=preset,
)

# HTML 정화 + 토큰 위반 검증
validated_html = validate_html(generated)

_save_step(run_dir, "step2_generated.json", {
    "body_html_length": len(generated.get("body_html", "")),
    "sidebar_html_length": len(generated.get("sidebar_html", "")),
    "footer_html_length": len(generated.get("footer_html", "")),
})
```

### 유지되는 코드 (4단계 이후: 줄 341~)

```python
# 줄 341~350: 4단계 렌더링 — 유지하되 render_slide → render_slide_from_html로 변경
html = render_slide_from_html(generated, analysis, preset)
_save_step(run_dir, "step4_rendered.html", html)

# 줄 352~446: Selenium 측정, overflow 감지, 수학적 조정 — 유지
#   단, overflow 시 텍스트 압축(fill_content 호출, 줄 442)은 제거
#   대신 AI HTML 재생성 요청

# 줄 448~474: 비전 모델 품질 게이트 — 유지

# 줄 476~483: 이미지 삽입, final.html 저장 — 유지
```

### _adjust_design (줄 490~589) — 제거 또는 변경

```
현재: layout_concept의 블록별 텍스트 양을 보고 CSS 변수 조정
R':  AI가 HTML 생성 시 이미 CSS를 포함하므로, 별도 CSS 조정 단계 불필요
     → 제거하거나, AI 생성 HTML에 대한 보조 조정으로 역할 축소
```

### _review_balance, _apply_adjustments (줄 592~730) — 변경

```
현재: 블록 기반 layout_concept를 검수/조정
R':  AI 생성 HTML을 검수. 조정 시 block_selector/fill_candidates가 아닌 html_generator 재호출
     줄 729의 fill_content() 호출 → 제거
```

### _build_overflow_context, _convert_kei_judgment (줄 733~800) — 변경

```
현재: layout_concept의 블록 데이터에서 overflow 컨텍스트 추출
R':  AI 생성 HTML에서 직접 추출하도록 변경
```

---

## 7. 다른 파일에 대한 영향 (충돌/회귀 체크)

### 영향 없음 (건드리지 않음)

| 파일 | 이유 |
|------|------|
| `src/kei_client.py` — classify_content, refine_concepts | 1단계에서 호출. R'에서 변경 없음 |
| `src/kei_client.py` — vision_quality_gate | 품질 게이트. R'에서 변경 없음 |
| `src/space_allocator.py` — calculate_container_specs | 컨테이너 계산. R'에서 변경 없음 |
| `src/design_director.py` — select_preset, LAYOUT_PRESETS | 프리셋 선택. R'에서 변경 없음 |
| `src/slide_measurer.py` | Selenium 측정. R'에서 변경 없음 |
| `src/config.py` | 설정. 변경 없음 |
| `src/sse_utils.py` | SSE 스트리밍. 변경 없음 |
| `src/image_utils.py` | 이미지 크기 측정. 변경 없음 |
| `src/main.py` | FastAPI 엔드포인트. generate_slide 호출은 동일 |
| `static/index.html` | 프론트엔드. 변경 없음 |
| `templates/slide-base.html` | 슬라이드 프레임. 변경 없음 |
| `static/tokens.css`, `static/base.css` | 디자인 토큰. 변경 없음 (프롬프트에서 읽기만 함) |
| `templates/blocks/*.html` | 38개 블록. 변경 없음 (CSS 참고용으로만 유지) |
| `templates/catalog.yaml` | 변경 없음 (프롬프트에서 CSS 패턴 인덱스로 참고만) |

### 변경되는 파일

| 파일 | 변경 내용 | 기존 코드 영향 |
|------|----------|-------------|
| `src/pipeline.py` | 줄 166~339 교체 (블록 선택+채우기 → html_generator 호출) | 2-3단계만 교체, 나머지 유지 |
| `src/pipeline.py` | _adjust_design 제거 또는 축소 | 4단계 CSS 조정 불필요 |
| `src/pipeline.py` | _apply_adjustments에서 fill_content 호출 제거 | overflow 시 html_generator 재호출 |
| `src/renderer.py` | `render_slide_from_html()` 신규 함수 추가 | 기존 `render_slide()` 변경 없음 (하위 호환) |

### 신규 파일

| 파일 | 역할 |
|------|------|
| `src/html_generator.py` | 핵심 — Kei 분석 + 토큰 + 예시 → AI HTML 생성 |
| `src/design_tokens.py` | tokens.css + 블록 CSS 패턴을 프롬프트용 텍스트로 추출 |
| `src/html_validator.py` | nh3 정화 + CSS 토큰 위반 검증 |
| `data/examples/C_reference.html` | few-shot 예시 1 |
| `data/examples/hybrid_simulation.html` | few-shot 예시 2 |
| `scripts/test_phase_r_prime.py` | R' 테스트 스크립트 |

### 호출하면 안 되는 것 (자기 검증)

| 함수 | 위치 | 이유 |
|------|------|------|
| `select_block_candidates()` | `block_selector.py` | 블록 선택 시스템 — R'에서 제거 |
| `select_fallback_candidates()` | `block_selector.py` | 동일 |
| `select_block_for_topics()` | `kei_client.py` | 블록 선택 AI — R'에서 제거 |
| `fill_candidates()` | `content_editor.py` | 슬롯 채우기 — R'에서 제거 |
| `fill_content()` | `content_editor.py` | 동일 |
| `finalize_block_specs()` | `space_allocator.py` | 블록 스펙 — 블록 없으므로 불필요 |
| `calculate_budgets_for_candidates()` | `space_allocator.py` | 블록 예산 — 블록 없으므로 불필요 |

### 호출해야 하는 것 (유지)

| 함수 | 위치 | 이유 |
|------|------|------|
| `classify_content()` | `kei_client.py` | 1단계 Kei 분석 |
| `refine_concepts()` | `kei_client.py` | 1.5단계 컨셉 구체화 |
| `calculate_container_specs()` | `space_allocator.py` | 컨테이너 px 계산 |
| `select_preset()` | `design_director.py` | 프리셋 선택 |
| `measure_rendered_heights()` | `slide_measurer.py` | Selenium 측정 |
| `capture_slide_screenshot()` | `slide_measurer.py` | 스크린샷 캡처 |
| `vision_quality_gate()` | `kei_client.py` | 비전 모델 품질 게이트 |
| `generate_slide_html()` | `html_generator.py` (신규) | AI HTML 생성 |
| `validate_html()` | `html_validator.py` (신규) | HTML 정화+검증 |
| `render_slide_from_html()` | `renderer.py` (신규 함수) | AI HTML → 슬라이드 프레임 삽입 |

---

## 8. 자기 검증 체크리스트

구현 시작 전 반드시 확인:

- [ ] **block_selector.py에서 블록을 "선택"하는 코드를 호출하고 있는가?** → 호출하면 안 됨
- [ ] **fill_candidates/fill_content로 "슬롯에 텍스트를 채우는" 코드를 호출하고 있는가?** → 호출하면 안 됨
- [ ] **finalize_block_specs/calculate_budgets_for_candidates를 호출하고 있는가?** → 호출하면 안 됨
- [ ] **AI가 HTML 구조를 결정하고 있는가, 블록 템플릿이 구조를 결정하고 있는가?** → AI가 결정해야 함
- [ ] **기존 코드를 "수정"하는 것인가, "교체"하는 것인가?** → 2-3단계는 교체
- [ ] **C_reference.png 수준의 결과가 나올 수 있는 구조인가?** → topic 합침, 포함 관계, 핵심 메시지 분리가 가능해야 함
- [ ] **_adjust_design에서 블록 기반 로직을 사용하고 있는가?** → 사용하면 안 됨
- [ ] **_apply_adjustments에서 fill_content를 호출하고 있는가?** → 호출하면 안 됨

---

## 9. 기대 효과

| 지표 | Phase R (실패) | Phase R' (목표) |
|------|---------------|----------------|
| 결과물 수준 | 34점 (블록 선택+슬롯 한계) | C_reference.png 수준 (70점+) |
| 구조 결정 주체 | 블록 템플릿 | AI (콘텐츠 기반) |
| topic 합침/분리 | 불가 | 가능 |
| 포함 관계 시각화 | 해당 블록 없으면 불가 | AI가 직접 구성 |
| 원본 텍스트 보존 | 편집자 의존 (축약됨) | AI가 원본 직접 삽입 |
| CSS 일관성 | 블록 템플릿 보장 | 디자인 토큰 + 블록 CSS 참고 + 검증 |

---

## 10. 회귀 방지 — Phase P=Q=R 반복 금지

### 이 Phase에서 절대 하면 안 되는 것

1. **block_selector.py의 select_block_candidates를 호출하면 안 됨** — 블록 "선택" 시스템 회귀
2. **content_editor.py의 fill_candidates/fill_content를 호출하면 안 됨** — "슬롯 채우기" 회귀
3. **catalog.yaml에서 블록을 매칭하면 안 됨** — 블록 매칭 회귀
4. **variant를 추가하면 안 됨** — Phase R의 실패 패턴 회귀
5. **"최소 변경"으로 기존 코드 위에 패치하면 안 됨** — P=Q=R 반복의 원인

### 이 Phase에서 반드시 해야 하는 것

1. **html_generator.py에서 AI가 HTML 구조를 직접 생성** — 콘텐츠가 구조를 결정
2. **블록 CSS는 참고만** — "선택"이 아닌 "참고"
3. **pipeline.py 줄 166~339를 교체** — 패치가 아닌 교체
4. **C_reference.png와 동일 수준의 결과를 자동으로 생성** — 이것이 합격 기준
