Files
C.E.L_Slide_test2/docs/history/COMPREHENSIVE_VALIDATION_REPORT.md
kyeongmin c42e01f060 문서 정리: Phase 히스토리 md를 docs/history/로 이동 + 오래된 테스트/에셋 정리
- 루트의 IMPROVEMENT-PHASE-*.md, PHASE-*.md 등 45개 → docs/history/로 이동
- docs/block-tests/ 오래된 블록 테스트 HTML 삭제 (figma_to_html_agent로 대체)
- docs/figma-analysis/, docs/figma-assets/, docs/figma-screenshots/ 정리
- docs/test-*.html 등 초기 테스트 파일 정리
- 참고 페이지/ 스크린샷 정리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:56:23 +09:00

17 KiB
Raw Permalink Blame History

Design Agent — 3건 수정사항 종합 검증 보고서

검증 일시: 2026-03-28 10:00
검증 범위: space_allocator.py, slide_measurer.py, catalog.yaml 연계 파이프라인
방법론: 코드 추적 + 파이프라인 시뮬레이션 + MD 문서 동기화 확인


📊 검증 결과 요약

# 항목 구현 통합 문서 종합
1 space_allocator.py: topic당 높이 판단 🟡 95%
2 slide_measurer.py: container 감지 100%
3 catalog.yaml: schema 글자수 구조 🔴 🟡 ⚠️ 60%

전체 평가: 수정 의도는 정확하나 #3 catalog schema가 실제로 파이프라인에서 사용되지 않음


🔍 상세 검증

1 space_allocator.py: topic당 높이 기반 height_cost 판단

구현 상태

파일: src/space_allocator.py 라인 51-61

# 블록 내부 제약 계산 — topic당 높이로 판단
topic_count = max(1, len(topic_ids))
per_topic_px = height_px // topic_count  # ← 컨테이너 높이 / topic 개수

# height_cost 허용 범위: topic당 높이 기준 (컨테이너 전체가 아님)
max_cost = _max_allowed_height_cost(per_topic_px)  # ← 핵심 함수 호출
# ...

함수 정의 (라인 129-137):

def _max_allowed_height_cost(container_height_px: int) -> str:
    """컨테이너 높이에서 허용되는 최대 height_cost."""
    if container_height_px >= 350:
        return "xlarge"
    elif container_height_px >= 200:
        return "large"
    elif container_height_px >= 80:
        return "medium"
    else:
        return "compact"

파이프라인 통합

pipeline.py 라인 68-82에서 호출:

container_specs = calculate_container_specs(
    page_structure=page_struct,        # Kei 비중 판단 {"본심": {"topic_ids": [3], "weight": 0.6}, ...}
    topics=analysis.get("topics", []), # 5개 topic 정보
    preset=preset,
    ...
)

데이터 흐름:

1. Kei: page_structure 판단 
   ↓ (page_structure = {"본심": {"topic_ids": [3], "weight": 0.6}}, ...)
2. O-1: calculate_container_specs()
   - per_topic_px = 180 // 1 = 180px (본심 컨테이너 180px / 1개 topic)
   - max_cost = _max_allowed_height_cost(180) = "medium" ✅
   ↓
3. O-3: finalize_block_specs()
   - 블록에 _container_height_px=180, _max_items=2, _max_chars_total=320 설정
   ↓
4. 편집자 (stage 3): 
   - "최대 글자 수: 320자, 항목 수: 2개" 가이드 전달

🟡 문서 동기화 상태

ARCHITECTURE_OVERVIEW.md 라인 156-170:

Phase O-1 과정:
3. 비중 비율로 높이 할당 (zone 예산 복분)
4. 높이 → height_cost 매핑 (compact/medium/large/xlarge)

문제:

  • "height_cost 매핑"만 기술되어 있음
  • "topic당 높이로 판단"이 명시되지 않음 ← 개선 필요

개선된 설명:

Phase O-1 과정:
3. 비중으로 컨테이너 높이 할당 (ex. 180px)
4. topic당 높이로 max_height_cost 판단 (180px / 1 topic = 180px → medium) ← 중요
5. 글자 수/항목 수 제약 계산

검증 결과

항목 상태 근거
코드 구현 _max_allowed_height_cost() 함수 정확
파이프라인 호출 pipeline.py 68-82줄 O-1 통합
데이터 흐름 per_topic_px 계산 후 height_cost 결정
문서 정확도 🟡 "topic당 판단" 명시 필요

2 slide_measurer.py: container 감지 및 overflow 체크

구현 상태

파일: src/slide_measurer.py 라인 14-62

JavaScript 측정 스크립트:

// Phase O: 컨테이너 측정 (container-* 클래스)
var containerDivs = slide.querySelectorAll('[class*="container-"]');
for (var k = 0; k < containerDivs.length; k++) {
    var container = containerDivs[k];
    var containerMatch = container.className.match(/container-(.+)/);
    if (!containerMatch) continue;
    var containerName = containerMatch[1];

    result.containers[containerName] = {
        scrollHeight: Math.round(container.scrollHeight),
        clientHeight: Math.round(container.clientHeight),
        allocatedHeight: parseInt(container.style.height) || 0,
        overflowed: container.scrollHeight > container.clientHeight + 2,
        excess_px: Math.max(0, Math.round(container.scrollHeight - container.clientHeight)),
        ...
    };
}

측정 결과 구조:

{
    "containers": {
        "본심": {
            "scrollHeight": 190,
            "clientHeight": 180,
            "allocatedHeight": 180,
            "overflowed": true,
            "excess_px": 10,
            "blocks": [
                {"block_type": "topic-left-right", "scrollHeight": 95, "overflowed": false},
                {"block_type": "topic-left-right", "scrollHeight": 95, "overflowed": false}
            ]
        },
        "배경": {
            "scrollHeight": 50,
            "clientHeight": 180,
            "allocatedHeight": 180,
            "overflowed": false,
            "excess_px": 0,
            "blocks": []
        }
    }
}

파이프라인 통합

pipeline.py 라인 177-202 (Phase L):

# Phase L: 렌더링 측정 + 피드백 루프 (최대 3회)
for measure_round in range(MAX_MEASURE_ROUNDS):
    measurement = await asyncio.to_thread(measure_rendered_heights, html)
    
    # overflow 감지 — zone + container 양쪽 체크
    has_overflow = False
    for zone_name, zone_data in measurement.get("zones", {}).items():
        if zone_data.get("overflowed"):
            has_overflow = True
            break
    # Phase O: container 레벨 overflow도 체크  ← 핵심
    for cont_name, cont_data in measurement.get("containers", {}).items():
        if cont_data.get("overflowed"):
            has_overflow = True
            logger.warning(
                f"[측정] container-{cont_name}: "
                f"scroll={cont_data.get('scrollHeight')}px > "
                f"allocated={cont_data.get('allocatedHeight')}px "
                f"(+{cont_data.get('excess_px')}px)"
            )
            break

    if not has_overflow:
        logger.info(f"[측정] 모든 zone/container 정상 (round {measure_round + 1})")
        break

overflow 감지 후 조치 (라인 203-230):

# 추출: container overflow 정보
for cont_name, cont_data in measurement.get("containers", {}).items():
    if cont_data.get("overflowed"):
        for block_m in cont_data.get("blocks", []):  # ← container 내 블록 단위
            if block_m.get("overflowed"):
                trim_chars = calculate_trim_chars(
                    block_m.get("excess_px", excess),
                    width_px,
                )
                # 해당 블록의 _max_chars_total 축소

📊 시뮬레이션: container overflow 피드백 루프

시나리오:

  • 본심 컨테이너 할당 높이: 180px
  • 실제 렌더링 높이: 190px (overflow +10px)
  • 블록 1: topic-left-right (95px, 정상)
  • 블록 2: card-icon-desc (120px, +15px 초과)

동작 순서:

Round 1: 측정
├─ measurement.containers["본심"] = {
│  ├─ allocatedHeight: 180,
│  ├─ scrollHeight: 190,
│  ├─ overflowed: true,
│  ├─ excess_px: 10,
│  └─ blocks: [{...95px...}, {...120px...}]  ← 블록 2가 +15px 초과
│
├─ Phase L 감지: "container-본심에서 +10px overflow"
│
├─ 조치: card-icon-desc의
│  └─ _max_chars_total: 300 → 285 (15자 축약)

Round 2: 재렌더링 + 재측정
├─ content_editor 재호출 (글자 수 제약 적용)
├─ 블록 2 텍스트 축약됨
│
├─ 재측정 결과:
│  └─ container

-본심: scrollHeight 185px / allocatedHeight 180px
│      → 여전히 +5px overflow

Round 3: 재조치
├─ 추가 5자 축약
├─ 재렌더링 + 재측정
│
└─ OK: container-본심 정상 (scrollHeight == allocatedHeight)

문서 동기화 상태

PROGRESS.md 라인 xxx (Selenium container 감지):

Phase L: 렌더링 측정 + feedback loop
- zone 레벨 overflow 감지 ✅
- container 레벨 overflow 감지 ✅ (NEW)

ARCHITECTURE_OVERVIEW.md 라인 xxx:

Phase L (Measurement):
1. Selenium headless Chrome으로 렌더링
2. JavaScript로 zone 높이 측정
3. NEW: container-* 클래스로 역할별 높이 측정  ← 개선됨
4. 초과분 감지 → 피드백 루프
5. 최대 3회 재조정

검증 결과

항목 상태 근거
코드 구현 slide_measurer.py 14-62줄
JavaScript 정확성 container-* 셀렉터 + overflow 계산 정확
파이프라인 호출 pipeline.py 177-202줄
피드백 루프 재렌더링 + 재측정 최대 3회
문서 정확도 PROGRESS.md, ARCHITECTURE_OVERVIEW.md 반영됨

3 catalog.yaml: schema 글자수 필드 추가

구현 상태

파일: templates/catalog.yaml 라인 44-46 (예. section-header-bar)

- id: section-header-bar
  name: 섹션 헤더 바
  height_cost: compact
  ...
  schema:  # ← NEW: 글자수 가이드 구조화
    title: {max_lines: 1, font_size: 18, ref_chars: {body: 25, sidebar: 20}, note: '18px bold white, 중앙정렬'}
    subtitle: {max_lines: 1, font_size: 13, ref_chars: {body: 40, sidebar: 30}, note: '13px, 1줄'}

- id: topic-left-right
  name: 좌우 꼭지 헤더
  height_cost: compact
  ...
  schema:
    title: {max_lines: 2, font_size: 24, ref_chars: {body: 20}, note: '24px bold, 240px 고정폭'}
    description: {max_lines: 2, font_size: 16, ref_chars: {body: 100}, note: '16px, 510px 너비'}

schema 필드 구조:

schema:
  {slot_name}:
    max_lines: N        # 텍스트 라인 수 (줄바꿈 횟수)
    font_size: N        # 픽셀 단위
    ref_chars:          # zone별 글자 수 가이드
      body: N           # body zone(65% 너비)에서의 한 줄 글자 수
      sidebar: N        # sidebar(35%)에서의 글자 수
    note: "..."         # 추가 설명

🔴 파이프라인 통합 실패

문제 1: catalog 로더가 schema를 읽지 않음

src/block_search.py 라인 xxx에서:

with open(META_PATH, encoding="utf-8") as f:
    _metadata = json.load(f)  # ← block_metadata.json에서 로드

# block_metadata.json 생성 스크립트: scripts/build_block_index.py
# 이 스크립트가 catalog.yaml의 schema를 추출하여 metadata에 포함하는가? → 불명

문제 2: metadata가 content_editor에 전달되지 않음

src/content_editor.py 라인 xxx:

# BLOCK_SLOTS를 사용 (설계 단계에 정의)
slots = BLOCK_SLOTS.get(block_type, {})  # ← catalog.yaml 아님

# catalog.yaml의 schema는 사용되지 않음!

문제 3: 글자 수 가이드 전달 경로 불명

현재 flow:

1. BLOCK_SLOTS (design_director.py에 하드코딩)
   ↓ (slot_desc만 전달, schema 아님)
2. content_editor.py (slot requirements 생성)
   ├─ slot_desc 포함
   ├─ char_guide 포함 (block에 설정되어 있으면)
   └─ schema (catalog에만 있음, 사용 안 됨!)
   ↓
3. Kei 프롬프트에 전달

🟡 코드 검증: schema 실제 접근 시도

전체 grep으로 schema 사용 검색:

grep -r "schema" src/*.py templates/*.yaml

Result:
- templates/catalog.yaml: 37개 블록에 schema 정의 ✅
- src/*.py: 검색 결과 0

결론: schema 필드가 catalog.yaml에 정의되어 있지만 코드에서 사용하지 않음

⚠️ 문서 동기화 상태

PROGRESS.md (라인 xxx):

Phase O-3: finalize_block_specs()로 블록 내부 제약 계산

  • max_items, max_chars_total, font_size 등

문제:

  • catalog.yaml schema 필드 추가를 기록하지 않음
  • "schema 글자수 구조 변환"이 완료된 것처럼 보이지만 실제로는 미사용

ARCHITECTURE_OVERVIEW.md:

  • catalog.yaml schema에 대한 언급 없음
  • "catalog 37개 블록" 기술만 있음

⚠️ 문제점 분석

Issue #1: catalog.yaml schema가 파이프라인에서 사용되지 않음

근본 원인:

  1. BLOCK_SLOTS가 설계 단계 (design_director.py)에서 하드코딩됨
  2. catalog.yaml은 렌더러에서만 사용 (템플릿 경로 매핑)
  3. schema 필드: 메타데이터로 정의되었으나 읽는 함수 없음

현재 상태:

catalog.yaml (37개 블록)
├─ id, name, template ✅ (renderer.py에서 사용)
├─ height_cost ✅ (design_director.py에서 사용)
├─ visual, when, not_for, purpose_fit ❓ (사용 불명)
└─ schema ❌ (완전히 미사용)

개선 필요 사항:

옵션 A: catalog 로더 추가 (권장)

# src/catalog_loader.py (신규)
def load_catalog_schema() -> dict[str, dict]:
    """catalog.yaml에서 블록별 schema 추출"""
    catalog_path = Path(__file__).parent.parent / "templates" / "catalog.yaml"
    with open(catalog_path, encoding="utf-8") as f:
        data = yaml.safe_load(f)
    return {
        b["id"]: b.get("schema", {})
        for b in data.get("blocks", [])
    }

# src/content_editor.py에서
schema_map = load_catalog_schema()
schema = schema_map.get(block_type, {})
# schema를 Kei 프롬프트에 전달

옵션 B: BLOCK_SLOTS에 schema 병합

# design_director.py
BLOCK_SLOTS = {
    "topic-left-right": {
        "required": [...],
        "optional": [...],
        "schema": {  # ← catalog.yaml과 동기화
            "title": {"max_lines": 2, "font_size": 24, ...},
            ...
        }
    }
}

📈 종합 평가

수정 의도 분석

수정 의도 실제 평가
#1 topic당 높이로 height_cost 판단하여 블록 선택 정확도↑ 정확히 구현됨 완성
#2 container 레벨 overflow 감지 → 피드백 루프로 정확도↑ 정확히 구현됨 완성
#3 schema로 글자수 메타데이터 구조화 → content_editor 정확도↑ 작성됨 / 미사용 ⚠️ 불완전

수정율 (완성도)

  • #1 space_allocator: 100%
  • #2 slide_measurer: 100%
  • #3 catalog schema: 60% ⚠️ (정의만 함, 사용 안 함)

다음 단계

🔴 즉시 필요:

# src/content_editor.py에 schema 로더 추가
def _load_block_schema() -> dict[str, dict]:
    from pathlib import Path
    import yaml
    catalog_path = Path(__file__).parent.parent / "templates" / "catalog.yaml"
    with open(catalog_path, encoding="utf-8") as f:
        data = yaml.safe_load(f)
    return {
        b["id"]: b.get("schema", {})
        for b in data.get("blocks", [])
    }

# fill_content() 함수에서 schema 전달
schema = _load_block_schema().get(block_type, {})
if schema:
    req_text += f"\n  슬롯 상세 스키마:\n"
    for slot, spec in schema.items():
        req_text += (
            f"    {slot}: "
            f"{spec.get('max_lines', '?')}줄, "
            f"{spec.get('font_size', '?')}px, "
            f"본심:{spec.get('ref_chars', {}).get('body', '?')}\n"
        )

📋 검증 체크리스트

[✅] 1. space_allocator.py _max_allowed_height_cost() 함수 구현
[✅] 2. pipeline.py에서 O-1로 호출
[✅] 3. container_specs 결과가 O-3에 전달
[✅] 4. finalize_block_specs()에서 _container_height_px 설정
[✅] 5. content_editor에서 char_guide로 사용

[✅] 6. slide_measurer.py container-* 셀렉터 추가
[✅] 7. measure_rendered_heights()에서 containers 반환
[✅] 8. pipeline.py Phase L에서 overflow 감지
[✅] 9. 피드백 루프: 재렌더링 + 재측정

[✅] 10. catalog.yaml schema 필드 37개 블록 모두 작성
[❌] 11. catalog 로더에서 schema 읽기 ← 미구현
[❌] 12. content_editor에서 schema 전달 ← 미구현
[❌] 13. Kei 프롬프트에 schema 포함 ← 미구현

매트릭스: MD 문서 vs 코드 동기화

파일 항목 MD 기재 코드 동기화
PROGRESS.md BF-4 해결
ARCHITECTURE_OVERVIEW.md Phase O-1 설명 기본 상세 🟡
ARCHITECTURE_OVERVIEW.md Phase L 측정 기본 상세(container 추가) 🟡
ARCHITECTURE_OVERVIEW.md catalog schema
README.md catalog 필드 언급 없음 37개 블록 정의

권고사항

🔴 우선순위 1 (즉시)

catalog schema를 content_editor에 통합

  • 파일: src/content_editor.py
  • 작업: _load_block_schema() 함수 추가 + fill_content()에서 호출
  • 소요시간: 30분
  • 영향: #3 완성도 60% → 100%

🟡 우선순위 2 (이번 주)

MD 문서 업데이트

  • ARCHITECTURE_OVERVIEW.md: Phase O container 로직 상세 기술
  • PROGRESS.md: catalog schema 활용 추가 기록
  • 소요시간: 1시간

🟢 우선순위 3 (다음 주)

통합 테스트

  • 3개 수정사항 end-to-end 테스트
  • 컨테이너 overflow 시나리오 검증
  • catalog schema 가이드 실제 사용 확인