diff --git a/PHASE-X-BX.md b/PHASE-X-BX.md new file mode 100644 index 0000000..4adbc20 --- /dev/null +++ b/PHASE-X-BX.md @@ -0,0 +1,281 @@ +# 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 가능. + +### 수행 방향 +- **XBX-5(파이프라인 연결) 완료 후 자동 해결 확인** +- filled → Selenium 측정 → overflow 감지 → 재배분 +- 재배분으로 안 되면 텍스트 압축 또는 팝업 분리 (글자수 기반, 코드 판단) +- **선행: XBX-5 완료 필수** + +### 검증 +Selenium 측정에서 상단 zone 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"`) 추가로만 구현 +- 검증은 반드시 렌더링(스크린샷)으로 diff --git a/PHASE-X-C.md b/PHASE-X-C.md new file mode 100644 index 0000000..a294e1a --- /dev/null +++ b/PHASE-X-C.md @@ -0,0 +1,142 @@ +# Phase X-C: 서브존 프리셋 기반 범용 레이아웃 + +> 최종 업데이트: 2026-04-07 +> 전제: Type A, Type B 건드리지 않음. Type C로 새 접근. +> 의존: Phase X-BX' 완료 후 시작 (zone 기반 코드 전환 완료 필요) + +--- + +## 핵심 아이디어 + +**AI는 "고르는 것"에만 쓰고, "만드는 것"은 코드가 한다.** + +### 고정 구조 + +모든 슬라이드는 3개 zone: +``` +┌───────────────────────────┐ +│ header (제목) │ ← 고정 +├───────────────────────────┤ +│ body (본문) │ ← 서브존 프리셋 적용 +├───────────────────────────┤ +│ footer (핵심 요약) │ ← 고정 +└───────────────────────────┘ +``` + +### 서브존 프리셋 + +body 안의 배치를 row 조합으로 정의: + +``` +row 유형: + F = 전체폭 (1칸) + H = 2분할 + T = 3분할 + Q = 4분할 + +프리셋 예시: + S1: F → 1단 전체폭 + S2: H → 2단 (좌/우) + S3: T → 3단 균등 + S4: F+H → 상단 전체폭 + 하단 2분할 (현재 Type B에 해당) + S5: H+F → 상단 2분할 + 하단 전체폭 + S6: H+H → 상하 각 2분할 (2x2) + S7: F+T → 상단 전체폭 + 하단 3분할 + S8: T+F → 상단 3분할 + 하단 전체폭 + S9: F+F → 전체폭 2단 (현재 Type A에 가까움) +``` + +### Kei 역할 (최소화) + +1. **꼭지 추출** — 콘텐츠를 몇 개 덩어리로 나눌지 +2. **꼭지 간 관계** — 비교/나열/종속/독립 +3. **프리셋 선택** — 번호로 고르기 (또는 코드가 자동 매핑) + +### 코드 역할 + +1. 프리셋 → zone/sub-zone px 계산 (사칙연산) +2. 텍스트량 기반 비율 계산 +3. before→filled→after 파이프라인 (Selenium 측정 → 재배분) +4. MDX 원본 텍스트 배치 +5. 블록 선택 (프리셋별 적합 블록 매핑) + +--- + +## 질문: Kei가 프리셋을 고를 수 있는가? + +### 자동 매핑 (코드가 결정) — 안정적이지만 제한적 +```python +if len(topics) == 1: preset = "S1" # 1단 +if len(topics) == 2: preset = "S2" # 2분할 +if len(topics) == 3: preset = "S3" # 3단 +if len(topics) == 4: preset = "S6" # 2x2 +``` +→ 꼭지 관계 무시. 비교 3개 + 정리 1개 같은 경우 대응 못 함. + +### Kei 선택 — 유연하지만 불안정 위험 +```json +{"topics": [...], "preset": "S4", "reason": "상단 3개 비교 + 하단 종합"} +``` +→ 블록 선택도 불안한데 프리셋 선택이 안정적일지 미지수. + +### 하이브리드 (유력) — 코드가 후보 제시, Kei가 선택 +``` +코드: "꼭지 4개이므로 후보: S4, S6, S7" +Kei: "비교 관계이므로 S4" +``` +→ 선택지를 3개 이하로 좁히면 Kei가 잘 고를 가능성 높음. + +--- + +## 기술적 구현 방향 + +### sub-zone px 계산 + +```python +def calculate_sub_zones(preset: str, body_width: int, body_height: int, gap: int): + rows = parse_preset(preset) # "F+H" → [F, H] + row_count = len(rows) + row_height = (body_height - gap * (row_count - 1)) // row_count + + zones = [] + for i, row_type in enumerate(rows): + col_count = {"F": 1, "H": 2, "T": 3, "Q": 4}[row_type] + col_width = (body_width - gap * (col_count - 1)) // col_count + for j in range(col_count): + zones.append({ + "row": i, "col": j, + "width": col_width, "height": row_height, + }) + return zones +``` + +### 비율 조정 +```python +# 텍스트량 기반 비율 +text_lengths = [len(topic.text) for topic in row_topics] +total = sum(text_lengths) +ratios = [l / total for l in text_lengths] +# 최소 20%, 최대 60% 제한 +ratios = [max(0.2, min(0.6, r)) for r in ratios] +``` + +--- + +## 단계별 진행 계획 + +1. **X-C-1: 프리셋 정의** — S1~S9 구조 + px 계산 함수 +2. **X-C-2: 자동 매핑 먼저** — 꼭지 수 → 프리셋 (코드만으로) +3. **X-C-3: 조립 범용화** — zone 기반으로 어떤 프리셋이든 조립 +4. **X-C-4: Kei 선택 실험** — 하이브리드 방식 테스트 +5. **X-C-5: before→filled→after 연결** — X-BX'의 zone 기반 코드 활용 +6. **X-C-6: 검증** — 01/02/03번 + 새 MDX로 범용성 테스트 + +--- + +## Type A/B와의 관계 + +- Type A, B는 기존 코드 그대로 유지 +- Type C는 별도 경로로 동작 +- 추후 Type C가 안정화되면 A/B를 C의 프리셋으로 흡수 가능: + - Type A ≈ S9 (F+F) + sidebar + - Type B ≈ S4 (F+H)