# Phase R: 하이브리드 블록 시스템 — 기존 블록 활용 + 변형 + 자유 생성

> 작성일: 2026-03-30
> 상태: 설계 확정, 실행 대기
> 선행: Phase Q (제약 기반 블록 선택 + 글자수 예산) 코드 완료
> 근거: Phase Q 6차 테스트 + 하이브리드 시뮬레이션 검증

---

## 1. 배경: 왜 Phase R이 필요한가

### Phase Q까지의 성과
- ✅ 유령 블록 제거 (catalog 검증)
- ✅ relation_type → 카테고리 결정론적 매핑
- ✅ 글자수 예산 사전 계산
- ✅ fill_candidates topic별 개별 호출 복원
- ✅ 비전 모델 품질 게이트
- ✅ overflow 수학적 조정 (LaTeX 글루 모델)

### Phase Q에서 해결 못한 문제
- ❌ **블록이 콘텐츠 구조에 안 맞는 경우** — 38개 고정 블록 중 "정확히 맞는 것"이 없을 때 억지로 끼워 맞춤
- ❌ **topic 1개 = 블록 1개 고정 규칙** — topic 합침/분리 불가
- ❌ **콘텐츠 전달 의도 반영 부족** — "이해시키는 시각화"가 아닌 "텍스트 나열"

### 하이브리드 시뮬레이션으로 증명된 것
DX 시행 목표 콘텐츠로 테스트한 결과:

| 블록 | 용도 | 활용 방식 | 블록 재사용률 |
|------|------|----------|-------------|
| `card-icon-desc` | 목표 3카드 | 기존 블록 **100%** | 그대로 |
| `dark-bullet-list` | 프로세스 변화 | 기존 색상/구조 + **Before→After 변형** | 80% |
| `divider-text` | 섹션 구분 | 기존 블록 **100%** | 그대로 |
| `table-simple-striped` | 주체별 효과 | 기존 블록 **100%** | 그대로 |
| `banner-gradient` | 결론 | 기존 블록 **100%** | 그대로 |

**결론: 38개 블록의 80%는 그대로 사용 가능. 빠진 것은 "변형 능력" 1가지.**

---

## 2. 핵심 원칙

```
블록 선택 → 맞는 블록 있으면 그대로 사용 (기존 Phase Q)
          → 80% 맞는 블록 있으면 변형해서 사용 (Phase R 추가)
          → 전혀 안 맞으면 디자인 토큰 안에서 자유 생성 (Phase R 추가)
```

### 3단계 렌더링 우선순위

| 우선순위 | 방식 | 조건 | 품질 안정성 |
|---------|------|------|-----------|
| **1순위** | 기존 블록 그대로 | catalog에 정확히 맞는 블록이 있을 때 | 가장 높음 (검증된 템플릿) |
| **2순위** | 기존 블록 변형 | 80% 맞는 블록 + variant로 보완 | 높음 (기존 CSS 기반) |
| **3순위** | 디자인 토큰 기반 자유 생성 | 어떤 블록으로도 안 맞을 때 | 중간 (토큰 제약 + 검증 필요) |

---

## 3. 구체적 설계

### R-1: catalog.yaml에 variants 메타데이터 추가

기존 블록에 "변형 가능한 형태"를 정의한다. 변형은 기존 CSS를 유지하면서 내부 구조만 달라지는 것.

```yaml
- id: dark-bullet-list
  category: emphasis
  # ... 기존 필드 유지 ...
  variants:
    - id: default
      description: 기존 불릿 나열
      template: blocks/emphasis/dark-bullet-list.html
    - id: before-after
      description: Before→After 2열 구조 (프로세스 변화)
      template: blocks/emphasis/dark-bullet-list--before-after.html
      when: "기존 방식 → 새 방식으로의 전환/변화를 보여줄 때"

- id: card-icon-desc
  category: cards
  variants:
    - id: default
      description: 아이콘 + 제목 + 설명 (기본)
    - id: compact
      description: 아이콘 축소, 설명 2줄 제한 (높이 부족 시)
    - id: horizontal
      description: 아이콘-제목-설명 가로 배치 (좁은 공간)

- id: comparison-2col
  category: emphasis
  variants:
    - id: default
      description: 좌우 텍스트 비교
    - id: cards-in-container
      description: 큰 박스 안에 카드 N개 (포함 관계 시각화)
      when: "hierarchy/inclusion — A 안에 B,C,D가 포함됨을 보여줄 때"
```

- **파일:** `templates/catalog.yaml`
- **변경:** 기존 블록에 `variants[]` 필드 추가
- **변형 템플릿:** `blocks/{category}/{block-id}--{variant-id}.html` 파일 추가

### R-2: variant 템플릿 제작

블록별 변형 HTML 템플릿을 추가한다. 기존 블록의 CSS(색상, 배경, radius 등)를 그대로 사용하고 내부 구조만 변경.

**우선 제작 대상 (시뮬레이션에서 검증된 변형):**

| 블록 | variant | 용도 |
|------|---------|------|
| `dark-bullet-list` | `before-after` | 프로세스 변화 (Before→After 2열) |
| `comparison-2col` | `cards-in-container` | 포함 관계 (DX ⊃ GIS+BIM+DT) |
| `card-icon-desc` | `compact` | 높이 부족 시 축소 |
| `card-numbered` | `horizontal` | 사례 가로 비교 |

- **파일:** `templates/blocks/{category}/` 에 `--{variant}.html` 추가
- **원칙:** 기존 블록의 CSS 클래스/색상을 재사용. 새 CSS는 최소한만 추가.

### R-3: block_selector.py에 variant 선택 로직 추가

블록 선택 시 variant도 함께 결정. Kei에게 "이 블록의 어떤 변형이 적합한가"를 함께 제시.

```python
# block_selector.py 수정

def select_block_candidates(topic, container_spec, used_blocks, catalog):
    # ... 기존 필터링 로직 유지 ...

    # 각 후보 블록의 variants도 함께 반환
    for block in candidates:
        variants = block.get("variants", [{"id": "default"}])
        # expression_hint와 매칭되는 variant 우선 정렬
        block["_available_variants"] = variants

    return candidates
```

### R-4: Kei 블록 선택 프롬프트에 variant + expression_hint 전달

Q-4 프롬프트를 확장하여 variant 선택과 expression_hint를 포함.

```
## 후보 블록
1. dark-bullet-list (다크 배경 불릿)
   변형:
   - default: 기존 불릿 나열
   - before-after: Before→After 2열 구조

   ★ 표현 의도: "기존 방식에서 새 방식으로의 전환을 보여주는 구조"
   → before-after 변형이 적합

## 선택 (JSON)
{"block_id": "dark-bullet-list", "variant": "before-after", "reason": "..."}
```

### R-5: renderer.py에 variant 렌더링 지원

variant가 지정되면 해당 변형 템플릿을 사용하여 렌더링.

```python
# renderer.py 수정

def _resolve_template_path(env, block_type, variant="default"):
    if variant and variant != "default":
        # 변형 템플릿 우선
        variant_path = f"blocks/{category}/{block_type}--{variant}.html"
        if template_exists(env, variant_path):
            return variant_path

    # 기존 템플릿 fallback
    return f"blocks/{category}/{block_type}.html"
```

### R-6: 3순위 자유 생성 (디자인 토큰 기반)

어떤 블록+변형으로도 안 맞을 때, AI가 디자인 토큰 안에서 HTML을 직접 생성.

```python
# 자유 생성 조건
if not suitable_block_found:
    # 디자인 토큰 + 3-5개 예시 슬라이드를 프롬프트에 포함
    # AI가 HTML 생성
    # Selenium으로 검증
    html = await generate_free_block_html(topic, container_spec, design_tokens)
```

**제약 사항:**
- 디자인 토큰(CSS 변수)만 사용 가능 — 하드코딩 색상/폰트 금지
- 감사 스크립트로 토큰 위반 검출 (Atlassian 방식)
- Selenium 측정으로 overflow 검증
- 비전 모델 품질 게이트 통과 필수

### R-7: expression_hint를 fill_candidates에 전달

1단계에서 Kei가 판단한 `expression_hint`를 편집자(fill_candidates)에게 전달하여 텍스트 구성에 반영.

```python
# fill_candidates 프롬프트에 추가
section += f"\n  ★ 표현 의도: {topic.get('expression_hint', '')}"
section += f"\n  ★ 변형: {block.get('_variant', 'default')}"
```

---

## 4. 실행 계획

### 의존 관계

```
R-1 (catalog variants) ──→ R-2 (variant 템플릿) ──→ R-5 (renderer variant 지원)
                        ──→ R-3 (selector variant)──→ R-4 (Kei 프롬프트 확장)
                                                  ──→ R-7 (expression_hint 전달)
R-6 (자유 생성) ← R-5 완료 후 독립 작업
```

### 우선순위

| 순서 | 스텝 | 내용 | 효과 |
|------|------|------|------|
| 1 | R-1 | catalog에 variants 추가 | 데이터 기반 |
| 2 | R-2 | before-after, cards-in-container 템플릿 제작 | 시뮬레이션에서 검증된 변형 우선 |
| 3 | R-5 | renderer variant 렌더링 | 변형 블록이 실제로 렌더링 |
| 4 | R-3 | block_selector variant 필터링 | variant 후보 제시 |
| 5 | R-4 | Kei 프롬프트 확장 | variant + expression_hint |
| 6 | R-7 | fill_candidates에 expression_hint 전달 | 텍스트 구성 개선 |
| 7 | R-6 | 자유 생성 (3순위) | 블록으로 안 맞을 때 대비 |

### 수정 파일

| 파일 | 변경 내용 |
|------|----------|
| `templates/catalog.yaml` | variants[] 필드 추가 |
| `templates/blocks/emphasis/dark-bullet-list--before-after.html` | 신규 |
| `templates/blocks/emphasis/comparison-2col--cards-in-container.html` | 신규 |
| `templates/blocks/cards/card-icon-desc--compact.html` | 신규 |
| `templates/blocks/cards/card-numbered--horizontal.html` | 신규 |
| `src/block_selector.py` | variant 필터링 로직 추가 |
| `src/kei_client.py` | Q-4 프롬프트에 variant + expression_hint |
| `src/renderer.py` | variant 템플릿 해석 |
| `src/content_editor.py` | fill_candidates에 expression_hint 전달 |

---

## 5. 기대 효과

| 지표 | Phase Q (현재) | Phase R (목표) |
|------|---------------|---------------|
| 블록 적합도 | 60% (억지로 끼워 맞춤) | 90%+ (변형으로 맞춤) |
| 콘텐츠 구조 반영 | 낮음 (텍스트 나열) | 높음 (Before→After, 포함관계 등) |
| 블록 재사용률 | 38개 중 5-6개 사용 | 38개 + variants로 실질 50+ |
| 자유 생성 비율 | 0% | 5-10% (안 맞을 때만) |
| 텍스트 보존도 | Phase P 수준 (fill_candidates) | 동일 유지 |

---

## 6. Phase P → Q → R 전체 흐름 정리

```
Phase P (20점): FAISS+Opus → 블록 선택 → 다후보 렌더링 비교 → 느리고 부정확
                ↓
Phase Q (77점): relation_type → 결정론적 필터링 → 예산 사전 계산 → 빠르고 정확
                → 하지만 "맞는 블록이 없으면 억지로 끼워 맞춤"
                ↓
Phase R (목표): Phase Q 유지 + variant 변형 + expression_hint 전달
                → 기존 블록 80% 활용 + 20% 변형/자유 생성
                → 콘텐츠 전달 의도에 맞는 시각화
```

---

## 7. 검증된 시뮬레이션 결과

### 콘텐츠 1: 건설산업 DX의 올바른 이해 (포함 관계)
- `ideal_v2` + `3approaches` 폴더: 접근 A, C가 우수
- `comparison-2col--cards-in-container` 변형이 핵심 (DX ⊃ GIS+BIM+DT)

### 콘텐츠 2: DX 시행 목표 및 기대 효과 (목표/프로세스)
- `hybrid_simulation` 폴더: 블록 80% 활용 확인
- `dark-bullet-list--before-after` 변형이 핵심 (프로세스 변화)
- `card-icon-desc` 기존 블록 그대로 사용 (목표 3카드)
- `table-simple-striped` 기존 블록 그대로 사용 (주체별 효과)

### 공통 확인
- 720px overflow 없음
- 디자인 토큰(색상, 폰트, 간격) 일관성 유지
- sidebar 프리셋 적절히 작동

---

## 8. 참고: 조사 기반 근거

| 출처 | 적용한 인사이트 |
|------|---------------|
| Atlassian + LLM | CSS 변수로 제한 → "10번째도 1번째와 동일 품질" |
| frontend-slides (11.5K stars) | 디자인 토큰 + 예시 기반 → 프로덕션 품질 HTML 생성 |
| TechGrid 인터뷰 | "모델의 자유도를 줄이고 모든 것을 검증" |
| VASCAR | 생성 → 렌더링 → 비전 모델 평가 → 교정 |
| Beautiful.ai | 템플릿 + 제약 엔진 (자동 조정 규칙) |
| AutoPresent (CVPR 2025) | 코드 API 기반 조합 생성 |
