# Phase W — Stage 1.8 before→filled→after 파이프라인 완성

> 작성일: 2026-04-03
> 근거: Phase V 이후 코드 조립/연결 과정에서 발견된 문제 8건
> 선행: Phase V (적합성 검증 + Kei 에스컬레이션)

---

## 배경

Phase V에서 설계한 before→filled→after 프로세스가 실제로 동작하지 않음.
코드는 부분적으로 존재하지만 연결이 안 되어 있고, 각 단계가 따로 놀고 있음.

### 발견된 문제 8건

1. **before→filled→after 파이프라인 연결 안 됨** — filled Selenium 측정 실패, 판단 미동작
2. **space_allocator 불안정** — weight 비율로 전체 공간 100% 사용해야 하는데 안 됨
3. **공통 조립 함수 불완전** — filled/assembled/stage_2가 각각 다른 코드 사용
4. **Kei 에스컬레이션 결정 미반영** — trim/popup/restructure 결정이 컨테이너에 반영 안 됨
5. **본심 OVERFLOW 미해소** — 재배분이 충분하지 않음
6. **filled 시각화 품질** — font_hierarchy, 카드 간격, 팝업 링크 등 미반영
7. **이미지 SVG 샘플 사용** — 실제 이미지(samples/images/) 대신 하드코딩 샘플
8. **Sonnet/코드 조립 경로 분리** — 두 경로가 각각 존재. Phase W 완성 후 판단

---

## 핵심 프로세스

```
before: weight 비중대로 전체 가용 공간 100% 배정 (초기 컨테이너)
  ↓
filled: before 컨테이너에 블록+텍스트 채움 (block_assembler 공통 함수)
  ↓
측정: Selenium으로 실제 넘침 확인
  ↓
판단:
  1. 다른 블록으로 바꿀 수 있나? (font_hierarchy 유지)
  2. sidebar 넘침 → 세로 확장 (예외)
  3. body 넘침 → 배경↔본심 재배분
  4. 그래도 안 되면 → Kei 에스컬레이션 (trim/popup/restructure)
  5. Kei 결정을 컨테이너에 실제 반영
  6. 텍스트 85% 보존 우선
  ↓
after: 조정된 컨테이너
  ↓
assembled = after 기준으로 block_assembler 공통 함수로 최종 조립
```

---

## 절대 원칙

1. **하드코딩 금지** — 어떤 MDX가 들어와도 동일하게 동작
2. **결과물 HTML 직접 수정 안 함** — 파이프라인 프로세스를 수정
3. **이미지는 실제 파일만** — samples/images/에서 가져옴. SVG 샘플 금지
4. **텍스트 85% 보존** — 공간 부족 시 컨테이너를 늘림. 그래도 안 되면 Kei가 팝업 분리
5. **weight = 초기 배정 비율 + 충돌 시 우선순위** — 최종 높이가 아님
6. **배치는 시선 흐름** — 좌→우, 상→하 (배경 상단좌, 본심 중앙좌, 첨부 우측, 결론 하단)
7. **font_hierarchy 준수** — 핵심 14px, 본심 12px, 배경 10-12px, 첨부 9-11px
8. **조립 로직은 한 곳** — block_assembler.py. filled/assembled/stage_2 모두 이 함수 사용
9. **임의로 코드 되돌리지 않음** — 이해 안 되면 물어보고, 제안하고, 허락받고 실행

---

## W-1: space_allocator — weight 비율 초기 배정

> 파일: `src/space_allocator.py`

| Task | 완료 기준 |
|------|-----------|
| W-1-1 | `calculate_container_specs()`에서 zone_budget을 `전체가용 × zone_weight_sum / all_weight_sum`으로 계산하는 코드가 있음 |
| W-1-2 | weight 합 1.0일 때 모든 컨테이너 높이 합 ≥ 전체 가용 공간의 95% |
| W-1-3 | 배경이 본심보다 위에, 첨부가 우측에, 결론이 하단에 배치되는 좌표 계산이 `_calc_coords()`에서 동작함 |

---

## W-2: block_assembler — 공통 조립 함수 완성

> 파일: `src/block_assembler.py`

| Task | 완료 기준 |
|------|-----------|
| W-2-1 | `assemble_role_html()`이 블록 CSS의 font-size를 font_hierarchy 값으로 override한 HTML을 반환함 |
| W-2-2 | 팝업 `[팝업: 제목]` 마커가 별도 줄이 아니라 이전 불릿 텍스트 옆에 `[제목→]` 형태로 붙어있음 |
| W-2-3 | sidebar 역할일 때 상단에 꼭지 title 라벨이 있음 |
| W-2-4 | card-numbered에서 주불릿(indent=0)만 카드 제목, 하위불릿(indent=1)은 카드 설명으로 분리됨 |
| W-2-5 | 카드 내 불릿 사이에 빈 줄(엔터) 없음 — white-space: normal 적용 |
| W-2-6 | 이미지는 `ctx.slide_images`의 실제 파일(base64)만 사용, design_reference_html의 SVG 샘플 미사용 |
| W-2-7 | `_gen_stage_1_8_filled()`와 assembled가 모두 `assemble_slide_html()`을 호출함 |
| W-2-8 | Stage 0에서 팝업 콘텐츠의 마크다운 테이블이 HTML `<table>`로 변환되어 `stage_0_context.json`의 popups에 저장됨 |

---

## W-3: filled → Selenium 측정 연결

> 파일: `src/block_assembler.py`, `src/pipeline.py`

| Task | 완료 기준 |
|------|-----------|
| W-3-1 | `assemble_slide_html()` 출력 HTML에 `.slide` 클래스가 최외곽 div에 있음 |
| W-3-2 | 각 역할 컨테이너에 `.area-body`, `.area-sidebar`, `.area-footer` 클래스가 있음 |
| W-3-3 | filled HTML을 `measure_rendered_heights()`에 넣으면 `{'error': 'slide not found'}`가 아닌 정상 측정 결과가 반환됨 |
| W-3-4 | steps 폴더에 `stage_1_8_fit_before.html` → `stage_1_8_filled.html` → `stage_1_8_fit_after.html` 순서로 생성되고, before의 컨테이너 크기 → filled의 넘침 → after의 조정된 크기가 시각적으로 확인 가능 |

---

## W-4: 측정 결과 기반 조정 판단

> 파일: `src/pipeline.py`, `src/fit_verifier.py`, `src/kei_client.py`

| Task | 완료 기준 |
|------|-----------|
| W-4-1 | pipeline.py Stage 1.8에서 filled 측정 후 sidebar overflow 감지 시 해당 role의 컨테이너 height_px가 scrollHeight로 확장됨 |
| W-4-2 | body overflow 감지 시 `redistribute()`가 호출되어 배경↔본심 간 높이가 재배분됨 |
| W-4-3 | 재배분 후에도 overflow이면 `call_kei_fit_escalation()`이 호출됨 |
| W-4-4 | Kei가 trim 결정 시 해당 role의 structured_text가 축약되거나, popup 결정 시 해당 콘텐츠가 팝업으로 분리됨 |
| W-4-5 | Kei가 restructure 결정 시 해당 role의 컨테이너 height_px가 변경됨 |
| W-4-6 | after의 컨테이너 크기가 stage_1_8_context.json에 저장됨 |
| W-4-7 | Stage 1.8에서 `call_kei_enhancement_review()`가 호출되고, Kei 응답이 `enhancement_result`에 저장됨 |

---

## W-5: after 기반 최종 조립 + 검증

> 파일: `src/pipeline.py`, `src/block_assembler.py`

| Task | 완료 기준 |
|------|-----------|
| W-5-1 | stage_2의 `generated_html`이 after 컨테이너 크기를 기준으로 생성됨 (stage_2_context.json의 containers == stage_1_8_context.json의 containers) |
| W-5-2 | stage_3_rendered.html에서 overflow가 없음 (Stage 4 측정에서 모든 zone excess ≤ 0) |
| W-5-3 | final.html에 모든 역할의 structured_text가 85% 이상 포함됨 |

---

## 의존 관계

```
W-1 (weight 초기 배정)
  ↓
W-2 (공통 조립 함수) ← 독립적으로 병행 가능
  ↓
W-3 (filled → Selenium 측정) ← W-1 + W-2 필요
  ↓
W-4 (측정 기반 판단) ← W-3 필요
  ↓
W-5 (after 기반 최종) ← W-4 필요
```

---

## 미결정 사항

- **Sonnet vs 코드 조립**: Stage 2를 Sonnet으로 유지할지 block_assembler 코드 조립으로 교체할지는 W-1~W-5 완성 후 결과를 보고 판단.

---

## 입력 데이터

- MDX 파일: `samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx`
- 이미지: `samples/images/` (DX1.png 등)
- 테스트: `scripts/run_from_stage1b.py` — Stage 1B 데이터 고정 실행
- 좋았던 Kei 데이터: `data/runs/20260403_133746` (또는 `20260403_163508`)
