# 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": "
...
...
", "sidebar_html": "
용어 정의
...
...", "footer_html": "
...
" } ``` ### 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와 동일 수준의 결과를 자동으로 생성** — 이것이 합격 기준