Phase W: - weight 비율 초기 배정 (space_allocator header 높이 반영) - block_assembler 공통 조립 함수 (filled/assembled 통합) - filled → Selenium 측정 → context 저장 - sidebar overflow 확장 + body 재배분 - sub_layouts 사전 계산 (이미지 누락 해결) Phase V': - 팝업 링크 우측상단 배치 (인라인 → position:absolute) - 표 내용 Kei 판단 (공란 크기 계산 → 행/열 산출 → Kei 요약) - 출처 라벨 삭제 + 이미지 아래 캡션 배치 - after 공란 제거 (결론 바로 위까지 body/sidebar 채움) 추가: - V-10 bold 키워드: 기계적 추출 → Kei 문맥 판단 - ** 마크다운 → <strong> 변환 - [이미지:] 마커 제거 (bold 변환 전 처리) - grid-template-rows AFTER 크기 반영 (Sonnet final) - assemble_stage2 CSS font-size override, white-space fix - 하드코딩 전수 검토 완료 - 본심 여러 topic 텍스트 합침 Phase X 계획 문서 작성 (동적 역할 구조) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 KiB
14 KiB
Phase V (Verification) — 콘텐츠-컨테이너 적합성 검증 + Kei 에스컬레이션
작성일: 2026-04-02 근거: Phase T' 디버깅 과정에서 발견된 파이프라인 구조적 결함 선행: Phase T (파이프라인 구조), Phase T' (시각 품질)
배경
발견된 구조적 문제
Phase T' 디버깅 중 step-by-step 시각 검토를 진행하면서 다음이 드러남:
-
컨테이너 크기가 콘텐츠 분량과 무관하게 결정됨
- Stage 1.5a에서 weight(0.6, 0.2, 0.1, 0.1) 고정 배분
- 배경에 꼭지 2개(220자)가 배정되었으나 117px밖에 안 됨 → 넘침
- 본심은 345px인데 실제 필요 260px → 85px 남음
-
"들어가는지" 검증 단계가 없음
- 블록 선택(Stage 1.7) 후 바로 HTML 생성(Stage 2)으로 넘어감
- 콘텐츠가 컨테이너에 실제로 들어가는지 아무도 확인하지 않음
- Sonnet이 넘치는 내용을 받아서 overflow/스크롤/잘림 발생
-
안 될 때 판단하는 주체가 없음
- 공간 부족 시 옵션(합치기, 축약, 팝업 이동, 구조 변경)을 생성하고
- Kei 페르소나에게 결정을 요청하는 프로세스가 없음
- 현재는 그냥 Sonnet에게 "넣어라"만 함
-
영역당 블록 1개만 선택됨
- 배경에 꼭지 2개가 있어도 블록 1개(callout-warning)만 선택
- 1꼭지 = 1블록 원칙이 지켜지지 않음
절대 원칙
- 하드코딩 금지 — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작
- 스크롤 절대 금지 — overflow:auto/scroll 어떤 영역에서도 불허
- 1꼭지 = 1블록 — 컨테이너에 꼭지 N개면 블록 N개가 개별 선택
- 콘텐츠 분량 → 컨테이너 크기 — weight 고정이 아니라 콘텐츠 필요 높이 기반 배분
- AI가 옵션 생성, Kei가 결정 — 안 될 때 하드코딩 대응이 아니라 Kei 판단 요청
개선된 파이프라인
기존:
1A → 1B → 1.5a(weight고정) → 1.5b → 1.7(영역당1블록) → 2(HTML) → 3 → 4
↑
여기서 넘치거나 잘림
개선:
1A → 1B → 1.7(꼭지별1블록) → 1.8★(적합성검증) → 2(HTML) → 3 → 4
│
├ 필요 높이 계산
├ 컨테이너 재배분
└ 안 되면 → Kei 에스컬레이션
변경 사항 요약
| Stage | 기존 | 개선 |
|---|---|---|
| 1.5a | weight 고정 배분 | 삭제 — 1.8에서 콘텐츠 기반 계산 |
| 1.7 | 영역당 블록 1개 | 꼭지당 블록 1개 |
| 1.8 (신규) | 없음 | 적합성 검증 + 재배분 + Kei 에스컬레이션 |
Stage 1.8: 적합성 검증 (신규)
입력
- 꼭지 목록 + 영역 배정 (Stage 1A/1B)
- 꼭지별 선택된 블록 + 블록 최소 높이 (Stage 1.7)
- 슬라이드 크기 (1280×720), padding, gap 등 고정 스펙
처리 흐름 (AI가 자동 — 하드코딩 아님)
Step 1: 필요 높이 계산
각 컨테이너별로:
- 배정된 꼭지들의 텍스트 분량(자수) 파악
- 해당 영역 font-size + line-height로 필요 줄 수 계산
- 선택된 블록의 padding, 제목, 마진 등 오버헤드 합산
- → 필요 최소 높이(px) 산출
Step 2: 슬라이드 공간 배분
720px 에서:
- header(고정) + footer(고정) + gap 빼기
- 남은 공간을 각 영역의 필요 높이 비율로 배분
- sidebar는 body와 같은 row이므로 sidebar 높이 = body 영역 합계
Step 3: 적합성 검증
각 컨테이너별로:
- 배분된 높이 ≥ 필요 높이 → 통과
- 배분된 높이 < 필요 높이 → Step 4로
Step 4: 재배분 시도
여유 있는 영역에서 부족한 영역으로 공간 이동:
- 각 영역의 (배분 높이 - 필요 높이) = 여유분 계산
- 여유분 > 0인 영역에서 부족 영역으로 재분배
- 재배분 후 모든 영역이 필요 높이 이상 → 통과
- 아직 부족 → Step 5로
Step 5: Kei 에스컬레이션
AI가 현황 + 시도 결과 + 옵션을 정리하여 Kei에게 요청:
[현황]
- 어떤 영역이 몇 px 부족한지
- 어떤 영역에 여유가 있는지
[시도 결과]
- 재배분으로 해결 가능한지/불가능한지
- 해결 가능하면 어떤 영역에서 얼마를 가져오는지
[옵션]
A. 꼭지 합치기 — 여러 꼭지를 하나의 블록 안에서 흐름으로 연결
B. 인라인 축약 — 사례 등을 괄호 한 줄로 축약
C. 팝업 이동 — 상세 내용을 팝업으로 빼고 링크만 남김
D. 컨테이너 재조정 — 다른 영역에서 공간을 가져옴
E. 그리드 구조 변경 — 배경 전체폭 등 레이아웃 자체 변경
F. 기타 (Kei 판단)
[결정 요청]
위 옵션 중 선택하거나 다른 방향을 제시해주세요.
Stage 1.8 내부 루프
Step 1: 부족/여유 검증 (calculate_fit)
Step 2: 재배분 시도 (redistribute)
Step 3: 부족 시 → Kei 에스컬레이션 (call_kei_fit_escalation)
Step 4: 여유 시 → 보충 콘텐츠 탐색 (analyze_enhancements)
├ 관련 팝업에 구조화 콘텐츠(표/비교) 있으면 제안
├ 영역 핵심 결론 → 강조 블록 제안
└ 텍스트 핵심 키워드 → bold 목록 생성
Step 5: Kei 확인 (AI가 제안, Kei가 승인/수정)
Step 6: 보충 블록 선택 + fit 재검증
├ Kei가 승인한 보충 콘텐츠에 맞는 블록을 catalog에서 선택
├ 추가 블록의 높이가 여유 공간에 들어가는지 재검증
└ 안 들어가면 축소 (행 수 줄이기) 또는 제외
Step 7: 세부 컨테이너 배치 계산
├ 메인 컨테이너 안에서 세부 컨테이너 배치 (SVG/텍스트/표/key-msg)
├ 각 세부 컨테이너 크기를 콘텐츠에서 동적 계산
├ 빈 공간 측정 → 보충 콘텐츠 크기 결정 (표 행 수 등)
├ 세부 컨테이너 간 정렬 (좌우 높이 다르면 짧은 쪽 중앙맞춤)
└ 최종 overflow 검증
Step 8: 확정 출력
출력
- 확정된 컨테이너 크기 (재배분 반영)
- 각 컨테이너별 꼭지-블록 매핑 (Kei 결정 반영)
- 보충 블록 목록 (여유 공간에 추가된 블록)
- 강조 블록 목록 (핵심 결론용)
- bold 키워드 목록 (Stage 2 프롬프트에 전달)
- 콘텐츠 정리 방향 (합치기/축약/팝업 등 — Kei 결정 반영)
태스크 목록
V-1: Stage 1.7 수정 — 꼭지별 블록 선택
- 현재:
select_reference_block()이 영역(배경/본심/첨부/결론) 단위로 1개 블록 선택 - 변경: 각 영역 내 꼭지마다 개별적으로 블록 선택
- 파일:
src/block_reference.py,src/pipeline.py(Stage 1.7 호출부) - 완료 기준: 배경에 꼭지 2개 → 블록 2개 선택. 꼭지 1개면 블록 1개.
V-2: Stage 1.8 신규 구현 — 적합성 검증
- 파일:
src/fit_verifier.py(신규) - 내용:
- Step 1~3: 필요 높이 계산 + 공간 배분 + 적합성 검증
- 모든 계산은 동적 (font-size, line-height, 블록 padding 등에서 도출)
- 완료 기준: 배경 117px → 부족 감지 → 재배분 시도
V-3: Stage 1.8 재배분 로직
- 파일:
src/fit_verifier.py - 내용:
- Step 4: 여유 영역 → 부족 영역으로 공간 재분배
- 재배분 후 결과를 Stage 2에 전달
- 완료 기준: 배경 117→151px, 본심 345→311px 자동 재배분
V-4: Stage 1.8 Kei 에스컬레이션
- 파일:
src/fit_verifier.py,src/kei_client.py - 내용:
- Step 5: 재배분으로도 안 될 때 옵션 생성 + Kei API 호출
- Kei 응답 파싱 → 결정에 따라 컨테이너/콘텐츠 조정
- 의존성: V-2, V-3
- 완료 기준: Kei에게 옵션 전달 → Kei 결정 수신 → 파이프라인 계속
V-5: Stage 1.5a 리팩터
- 파일:
src/space_allocator.py,src/pipeline.py - 내용:
- 기존 weight 고정 배분 로직을 V-2의 콘텐츠 기반 계산으로 교체
- 또는 1.5a를 삭제하고 1.8이 컨테이너 계산을 전담
- 완료 기준: weight 하드코딩 제거. 콘텐츠 분량 기반 동적 배분.
V-6: 통합 검증 — 완료
- 전수 하드코딩 스캔: 레이아웃 하드코딩 0개
- 통합 테스트: 31/31 통과
- step-by-step HTML: step1~step3 생성 + 시각 검토
Phase V-2: 콘텐츠 품질 강화 (Step 3 디버깅에서 발견)
Step 3 시각 검토에서 발견된 4가지 개선 사항. 모두 "AI가 분석, Kei가 확인"하는 동일 프로세스.
V-7: 주종 관계 블록 내 종속 꼭지 처리
- 발견: 배경에 꼭지1(intro)+꼭지2(supporting) → 블록 1개로 합쳤지만, 종속 꼭지를 어떻게 표현할지 미정
- 규칙 (동적):
- 종속 꼭지 콘텐츠 분량 확인 (fit_verifier의 텍스트 분량 계산 활용)
- 짧으면 (팝업 참조 1~2줄) → 인라인 (주 블록 안에 한 줄)
- 길거나 구조 있으면 → 하위 블록 (블록 안의 블록)
- 독립성 있으면 → 보조 블록 (나란히)
- 판단 기준: 종속 꼭지의 source_data 길이 + 팝업 참조 여부 + purpose
- Kei 확인: AI가 "인라인/하위블록/보조블록" 중 제안 → Kei가 확인
- 파일:
src/fit_verifier.py,src/block_reference.py
V-8: 여유 공간 콘텐츠 보충
- 발견: 본심 컨테이너에 ~53px 여유. 관련 팝업(DX vs BIM 비교표 1135자)이 있는데 활용 안 됨
- 규칙 (동적):
- 재배분 후 여유 공간 감지 (shortfall < -threshold)
- 해당 영역의 꼭지에 관련 팝업이 있는지 확인 (source_data에 [팝업:] 참조)
- 팝업에 구조화 콘텐츠(표, 비교, 목록)가 있으면
- 여유 공간에 맞는 블록 추가 선택 (catalog에서)
- 팝업 핵심만 요약하여 배치 제안
- Kei 확인: "53px 여유. 비교표 핵심 3행을 넣을까요?" → Kei가 확인
- 파일:
src/fit_verifier.py
V-9: 영역 핵심 결론 강조 블록
- 발견: 배경의 "체계적 정립 필요"가 단순 불릿과 동급. 시각적 강조 없음
- 규칙 (동적):
- 각 영역의 꼭지 purpose에서 핵심 결론 추출
- purpose=문제제기 → 마지막 문장이 결론적 패턴("~필요", "~해야")이면 강조 블록
- purpose=핵심전달 → core_message와 관련된 문장이면 강조 블록
- 강조 블록: highlight-strip, callout 내 강조 div 등 catalog에서 선택
- Kei 확인: "이 문장을 강조 블록으로 처리할까요?" → Kei가 확인
- 파일:
src/fit_verifier.py,src/block_reference.py
V-10: 텍스트 핵심 키워드 bold
- 규칙 (동적):
- source_data에서 핵심 용어 추출 (꼭지 title에 포함된 키워드, bold 마크된 텍스트)
- 해당 키워드를 HTML 생성 시 bold 처리하도록 Sonnet에게 전달
- 파일:
src/html_generator.py(Stage 2 프롬프트에 키워드 목록 포함)
의존 관계
Phase V-1 (완료):
V-1 → V-2 → V-3 → V-4 → V-5 → V-6 ✅
Phase V-2 (신규):
V-7 (종속 꼭지 처리) ← fit_verifier 활용
V-8 (여유 공간 보충) ← fit_verifier 확장
V-9 (강조 블록) ← purpose 분석
V-10 (bold 키워드) ← source_data 분석
→ 전체 통합 검증 (step-by-step HTML 재생성)
하드코딩 전수 점검 결과
반드시 제거 (Phase V에서 동적 계산으로 교체)
| # | 파일 | 라인 | 값 | 문제 | 교체 방향 |
|---|---|---|---|---|---|
| 1 | space_allocator.py |
161 | 474 |
body zone 높이 고정 | 슬라이드에서 header+footer+gap 빼고 동적 계산 |
| 2 | space_allocator.py |
160 | 0.35*0.85 |
sidebar 비율+패딩 고정 | container_ratio + 실제 padding에서 계산 |
| 3 | html_generator.py |
543 | 720-80-66-footer_h-40 |
body zone 높이 산술 하드코딩 | containers에서 받은 height_px 사용 |
| 4 | html_generator.py |
604,921 | 380 |
sidebar width fallback | containers에서 받은 width_px 사용 (fallback 제거) |
| 5 | html_generator.py |
621 | 1200 |
footer width fallback | containers에서 받은 width_px 사용 |
| 6 | design_director.py |
314 | 490 |
FRAME_AVAILABLE_HEIGHT 고정 | 슬라이드 스펙에서 동적 계산 |
| 7 | design_director.py |
328-366 | budget_px 다수 |
프리셋별 zone budget 고정 | Stage 1.8에서 콘텐츠 기반 재계산 |
| 8 | space_allocator.py |
301,583 | 0.85 |
패딩 비율 고정 | (slide_width - padding*2) / slide_width 로 계산 |
fallback 값 (정상 흐름에서는 도달하면 안 됨)
| # | 파일 | 라인 | 값 | 비고 |
|---|---|---|---|---|
| 9 | space_allocator.py |
299 | 490 |
zone_budget 기본값 — preset에서 반드시 와야 함 |
| 10 | space_allocator.py |
304,313 | 0.25 |
weight 기본값 — Kei가 반드시 제공해야 함 |
| 11 | pipeline.py |
960 | 490 |
budget_px fallback |
교체 원칙
- 모든 px 값: 이전 Stage의 계산 결과(containers, font_hierarchy 등)에서 받아 사용
- 비율 값(0.85 등): 실제 padding/gap에서 역산하여 계산
- fallback: 정상 흐름에서 절대 도달하지 않도록 이전 Stage에서 반드시 값을 제공
- font-size만 예외: 디자인 토큰으로 정의된 텍스트 크기는 하드코딩 허용
이전 Phase와의 관계
- Phase T: 파이프라인 Stage 0
5 구조 완성 → Phase V는 Stage 1.71.8 개선 - Phase T' (TP-1~6): 시각 품질 개선 (프롬프트, 후처리) → Phase V 적용 후 재검증 필요
- Phase T' 후처리: sidebar width:100%, overflow 제거, bold 변환, 폰트 캡 → 유지