# Phase X-BX': 유형 B 미완료 사항 정리 > 최종 업데이트: 2026-04-07 > 전제: **유형 A 코드 절대 건드리지 않음.** A는 완벽하게 동작 중. 수정도 재검증도 하지 않음. > 유형 B의 code_assembled + 파이프라인만 수정. > **02번 MDX 먼저 → 03번 확장** 순서로 진행. --- ## MDX 원본 위치 `D:\ad-hoc\cel\src\content\docs\Civil DX\BIM과 DX의 이해\` --- ## 근본 원인 Type A는 Kei가 역할명을 `"배경"`, `"본심"`, `"첨부"`, `"결론"`으로 내려주고, 하류 코드가 `containers["배경"]` 처럼 **역할명 글자**로 매칭한다. → 동작함. Type B는 Kei가 역할명을 `"필수요건"`, `"과정혁신"` 등으로 내려주는데, 하류 코드가 여전히 `containers["배경"]`을 찾는다. → **키가 없어서 빈 것.** **해결:** Type B일 때는 역할명 글자가 아니라 `containers`에 있는 키를 순회하고, zone 정보(`top`, `bottom_left` 등)로 위치를 결정한다. ```python # Type A (기존 그대로): for role in ["배경", "본심", "첨부", "결론"]: container = containers[role] # Type B (분기 추가): for role in containers: zone = containers[role].zone # top, bottom_left, bottom_right, footer ``` --- ## XBX-1: 들여쓰기 계층 ### 현상 MDX의 2단 계층(`* > *`)이 동일 레벨로 평탄화됨. ``` MDX 원본: 현재 HTML: - 안전과 품질 (소제목) → • 안전과 품질 ← 소제목인데 불릿과 동일 - 시설물의 요구 성능을... → • 시설물의 요구 성능을... ← 구분 없음 ``` ### 목표 ``` ■ 안전과 품질 ← 소제목 (bold, 색상 구분) • 시설물의 요구 성능... ← 본문 불릿 (들여쓰기) ``` ### 수행 방향 **1단계: normalizer에서 불릿 depth 보존** 현재 `src/mdx_normalizer.py`의 section content: ``` "**안전과 품질**\n시설물의 요구 성능을..." ← flat, depth 정보 없음 ``` 수정 후: ``` "- **안전과 품질**\n - 시설물의 요구 성능을..." ← depth 마커 보존 ``` markdown-it의 `list_item_open` 토큰에 이미 indent 정보 있음 (88번째 줄). section content 수집 시 indent level을 보존하면 됨. **2단계: 조립 로직에서 depth별 스타일 분기** `scripts/assemble_stage2.py` `_assemble_type_b` + `src/block_assembler.py` `_assemble_slide_html_type_b`: - depth 1 (`- `) → 소제목 스타일 (bold, 색상 구분, 카드) - depth 2 (` - `) → 본문 불릿 (들여쓰기, normal weight) ### 검증 02번 상단 "안전과 품질/생산성 향상/소통과 신뢰" 3개 소제목이 카드로 분리, 각각의 하위 불릿 2줄이 들여쓰기되어 보임. --- ## XBX-2: overflow → 콘텐츠 맞춤 프로세스 ### 현상 상단 zone(255px)에 소제목 3개 + 불릿 6줄 + 이미지 → overflow. 하단 우측에 표 데이터가 너무 많아서 overflow. ### 프로세스 (네가 말한 것) ``` 넘침 감지 → 최대 몇 줄까지 가능? → 몇 자 이내로 정리 → Kei에게 요약 요청 → 재수취 후 정리 ``` ### 왜 안 되는가 (원인 3개) **원인 1: Selenium 측정 실패** - `slide_measurer.py` 144줄: 2.2MB HTML을 `data:` URI로 로드 → 브라우저 크기 제한 - Type A는 ~214KB라 동작, Type B는 이미지 base64 포함 2.2MB라 실패 - **수정:** 임시 파일로 저장 후 `file://` URI로 로드 (크기 제한 없음) **원인 2: overflow 분기에 Type B zone 없음** - `pipeline.py` 538-553줄: `sidebar`와 `body`만 처리 - Type B zone(`top`, `bottom`)은 분기 없음 → overflow 감지돼도 무시됨 - **수정:** `if layout_template == "B":` 분기 추가. top/bottom overflow 시 처리 **원인 3: calculate_fit에서 Type B 역할 인식 불가** - `fit_verifier.py` 307줄: `role_font_map = {"본심": "core", "배경": "bg", ...}` - Type B 역할명이 이 dict에 없어서 항상 `"core"` fallback - overflow 계산이 부정확 → `needs_escalation`이 항상 `False` - **수정:** `if layout_template == "B":` 분기. zone 기반 font 매핑 ### 수행 순서 1. **Selenium 측정 수정** — data URI → 임시파일 방식 (slide_measurer.py) 2. **overflow 분기 추가** — Type B zone 처리 (pipeline.py) 3. **calculate_fit Type B 지원** — zone 기반 font 매핑 (fit_verifier.py) 4. **에스컬레이션 → Kei 요약 요청** — 이미 있는 코드 활용 (pipeline.py 584-606) 5. **검증** — 02번 파이프라인 돌려서 상단 overflow 해소 확인 ### 검증 - Selenium 측정에서 상단/하단 zone overflow 감지 - overflow 시 Kei에게 요약 요청 → 줄어든 콘텐츠로 재조립 - 결과 스크린샷에서 overflow 없음 --- ## XBX-3: 하단 구조 — 중제목 별도 행 ### 현상 "DX 기반 Process 혁신에 따른 주체별 기대효과"가 별도 행 → 공간 낭비. ``` 현재: 목표: ┌──────────────────────────────┐ ┌─────────────┬──────────────┐ │ DX 기반 Process 혁신에 따른...│ ← 별도행 │ 2.1 업무 수행│ 2.2 DX 시행 │ ├──────────────┬───────────────┤ │ 과정의 변화 │ 주체별 기대효과│ │ 2.1 업무 수행 │ 2.2 DX 시행 │ │ (불릿) │ (표) │ │ 과정의 변화 │ 주체별 기대효과│ └─────────────┴──────────────┘ └──────────────┴───────────────┘ 중제목은 2분할 상단에 작게 표시 ``` ### 수행 방향 `scripts/assemble_stage2.py` `_assemble_type_b` + `src/block_assembler.py` `_assemble_slide_html_type_b`: - 하단 대목차(level=2)를 별도 행으로 배치하지 않음 - 2분할 각 칸의 상단에 작은 라벨로 표시하거나, 2분할 위에 한 줄 라벨로 통합 - 절약된 높이를 2분할 콘텐츠에 할당 ### 검증 하단 영역 전체가 2분할 콘텐츠로 사용됨. 중제목이 별도 행을 차지하지 않음. --- ## XBX-4: 하단 좌/우 높이 불균형 ### 현상 02번 컨테이너: - bottom_left (업무 프로세스 변화): **124px** - bottom_right (주체별 기대효과): **321px** 높이가 2.5배 차이. ### 수행 방향 `src/space_allocator.py`의 `build_containers_type_b` (544-556줄): - 현재 코드에서 `height_px=bottom_h`로 동일하게 주고 있음 - **문제는 Kei가 준 weight가 다른 것** → weight에 의해 top/bottom 비율이 달라지고, 그 결과 bottom_h 자체가 줄어드는 구조인지 추적 필요 - 하단 좌/우는 무조건 **동일 높이**(`bottom_h`)로 고정 ### 검증 하단 좌/우 컨테이너가 동일 높이로 나옴. --- ## XBX-5: before→filled→after 파이프라인 연결 ### 현상 Type B의 filled HTML이 2,742 bytes (거의 빈 HTML). Type A는 214KB. ### 원인 하류 코드가 `containers["배경"]`, `containers["본심"]` 처럼 **Type A 역할명 글자**로 매칭. Type B의 역할명(`"필수요건"`, `"과정혁신"` 등)은 이 키에 없어서 빈 것. ### 수행 방향 **원칙: Type A 코드 그대로 두고, `if layout_template == "B":` 분기만 추가.** #### 5-1. `src/step_visualizer.py` (9곳+) 현재: ```python COLORS = {"배경": "#dc2626", "본심": "#2563eb", "첨부": "#16a34a", "결론": "#7c3aed"} for role in ["배경", "본심", "첨부", "결론"]: container = containers[role] ``` 수정: Type B 분기 추가. 기존 Type A 코드는 **한 글자도 안 건드림.** ```python if layout_template == "B": for role in containers: zone = containers[role].zone color = ZONE_COLORS.get(zone, "#333") # zone 기반 색상 # ... Type B 시각화 else: # 기존 Type A 코드 그대로 for role in ["배경", "본심", "첨부", "결론"]: ... ``` 수정 대상 함수 (9곳): - `_gen_stage_1_5a` (271줄) - `_gen_stage_1_5a_content` (297줄) - `_gen_stage_1_5b` (334줄) - `_gen_stage_1_7` (371줄) - `_gen_stage_1_8_fit_before` (419줄) - `_gen_stage_1_8_fit_after` (465줄) - `_gen_stage_1_8_blocks` (534줄) - `_gen_stage_2` (650줄, 683줄) #### 5-2. `src/fit_verifier.py` - `ROLE_ZONE_MAP` (488-493줄) — 이미 부분 수정됨. containers에 zone 있으면 그걸 사용. - `role_font_map` (307줄) `{"본심": "core", ...}` — Type B 분기 추가: zone 기반 매핑 (`"top" → "core"`, `"bottom_left" → "core"` 등) - `role_line_height` (308줄) — 동일하게 분기 - **Type A 코드 안 건드림.** `ROLE_ZONE_MAP`, `role_font_map`은 그대로 두고 fallback으로만 사용. #### 5-3. `src/renderer.py` - `_find_h` fallback 이미 추가됨. Type A는 `_find_h("배경")` 그대로 동작. - Type B에서 `body_row_h` 계산이 맞는지 확인 필요 — Type B는 body_row가 없고 top+bottom 구조. - 필요시 `if layout_template == "B":` 분기 추가. #### 5-4. `src/slide_measurer.py` - CSS 클래스 `area-*`로 zone 탐색 → **역할명 하드코딩 없음. 수정 불필요.** - `_assemble_slide_html_type_b`가 `area-top`, `area-bottom`, `area-footer` 클래스를 생성하므로 Selenium 측정이 그대로 동작. ### 검증 02번 MDX로 파이프라인 실행 → filled HTML이 10KB+ → Selenium 측정 정상 → after HTML 생성. --- ## XBX-6: Sonnet HTML 재구성 프로세스 분리 ### 현상 Stage 2(`src/pipeline.py` 901-957줄)에서 Sonnet(`generate_with_retry`)이 HTML 재구성. Type B에서는 품질 불안정. ### 수행 방향 `src/pipeline.py` stage_2 함수에 Type B 분기 추가: ```python async def stage_2(context: PipelineContext) -> dict: if context.analysis.layout_template == "B": # Type B: code_assembled 결과를 직접 사용, Sonnet 재구성 스킵 from src.block_assembler import assemble_slide_html generated = assemble_slide_html(context) return {"generated_html": generated} # Type A: 기존 Sonnet 재구성 코드 그대로 from src.content_verifier import generate_with_retry ... ``` - Sonnet 코드 삭제하지 않음 - Type B일 때만 스킵 - code_assembled HTML은 `assemble_slide_html(context)`로 생성 (이미 동작 확인됨) ### 검증 - Type A: 기존대로 Sonnet 재구성 → 결과 동일 - Type B: code_assembled 직접 사용 → 결과가 스크린샷에서 정상 --- ## 작업 순서 **02번 먼저 완성 → 03번 확장:** 1. **XBX-1** (들여쓰기) — normalizer depth 보존 + 조립 로직 분기 2. **XBX-3** (하단 구조) — 중제목 별도행 → 라벨로 통합 3. **XBX-4** (하단 높이) — 좌/우 균등 확인 + 수정 4. **XBX-5** (파이프라인 연결) — step_visualizer/fit_verifier/renderer Type B 분기 5. **XBX-2** (overflow) — 파이프라인 연결 후 Selenium으로 자동 확인 6. **XBX-6** (Sonnet 분리) — pipeline.py Type B 분기 7. **03번 확장** — 03번 MDX에서도 동작 확인 (표 보존, 3단 계층 등) --- ## 02번 run 정보 - 최신 run: `data/runs/20260406_121405` - 스크린샷: `steps/code_assembled_02_2x.png` - containers: top=255px(w=847px), bottom_left=124px, bottom_right=321px, footer=83px --- ## 핵심 원칙 - 하드코딩 절대 금지 - HTML 결과물 고치지 말고 파이프라인 프로세스 고칠 것 - 제목/텍스트는 원본 MDX에서 그대로 - **유형 A 코드 절대 건드리지 않음** — A는 완벽하게 동작 중. 수정도 재검증도 하지 않음. - Type B 코드는 기존 코드에 분기(`if layout_template == "B"`) 추가로만 구현 - 검증은 반드시 렌더링(스크린샷)으로