Phase P~S 전체 작업물: 검증 스크립트, 블록 템플릿, 설계 문서, 코드 수정
포함 내용: - Phase P/Q/R/S 설계 문서 (IMPROVEMENT-PHASE-*.md) - 영역별 검증 스크립트 (scripts/verify_*.py, test_*.py) - 블록 템플릿 추가 (cards, emphasis 변형) - 코드 수정: block_search, content_editor, design_director, slide_measurer - catalog.yaml 블록 목록 업데이트 - CLAUDE.md, PROGRESS.md, README.md 업데이트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
201
scripts/test_ideal_layout.py
Normal file
201
scripts/test_ideal_layout.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""해법 방향 시뮬레이션: 콘텐츠 전달 의도에 맞는 블록 배치.
|
||||
|
||||
사용자가 지적한 문제:
|
||||
- t1 (문제제기): 불릿 3줄 → 짧은 1-2줄이면 충분
|
||||
- t2 (사례 비교): 세로 card-numbered → 가로 2열 비교
|
||||
- t3 (핵심 DX≠BIM): 약어 원형 → DX와 BIM의 차이/관계를 보여주는 비교
|
||||
- t4 (용어 정의): 태그 짧은 요약 → 풀 정의
|
||||
|
||||
시뮬레이션: 블록을 "전달 의도"에 맞게 수동 선택하여 렌더링.
|
||||
Kei API 불필요 — 렌더링만.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
async def main():
|
||||
from src.renderer import render_slide
|
||||
from src.slide_measurer import measure_rendered_heights, capture_slide_screenshot
|
||||
from src.design_director import select_preset, LAYOUT_PRESETS
|
||||
from src.space_allocator import calculate_container_specs
|
||||
import base64
|
||||
import copy
|
||||
|
||||
run_dir = ROOT / "data" / "runs" / "1774736083771"
|
||||
analysis = json.loads((run_dir / "step1_analysis.json").read_text(encoding="utf-8"))
|
||||
concepts = json.loads((run_dir / "step1b_concepts.json").read_text(encoding="utf-8"))
|
||||
|
||||
concept_map = {c["id"]: c for c in concepts.get("concepts", [])}
|
||||
for topic in analysis.get("topics", []):
|
||||
tid = topic["id"]
|
||||
if tid in concept_map:
|
||||
topic["relation_type"] = concept_map[tid].get("relation_type", "none")
|
||||
|
||||
topics = analysis["topics"]
|
||||
preset_name = select_preset(analysis)
|
||||
preset = LAYOUT_PRESETS[preset_name]
|
||||
|
||||
out_dir = ROOT / "data" / "runs" / "ideal_simulation"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ═══════════════════════════════════════
|
||||
# 시뮬레이션 A: 전달 의도에 맞는 블록 선택
|
||||
# 컨테이너 비중도 콘텐츠에 맞게 조정
|
||||
# ═══════════════════════════════════════
|
||||
print("=== 시뮬레이션 A: 전달 의도 기반 블록 배치 ===")
|
||||
|
||||
# 비중 조정: 본심(핵심 비교)이 가장 크고, 배경은 간결하게
|
||||
adjusted_structure = copy.deepcopy(analysis["page_structure"])
|
||||
adjusted_structure["본심"]["weight"] = 0.55
|
||||
adjusted_structure["배경"]["weight"] = 0.25
|
||||
adjusted_structure["결론"]["weight"] = 0.10
|
||||
adjusted_structure["첨부"]["weight"] = 0.10
|
||||
|
||||
container_specs = calculate_container_specs(adjusted_structure, topics, preset)
|
||||
|
||||
blocks = []
|
||||
|
||||
# sidebar label
|
||||
blocks.append({
|
||||
"area": "sidebar", "type": "divider-text",
|
||||
"topic_id": None, "purpose": "_label",
|
||||
"data": {"text": "용어 정의"}, "size": "compact",
|
||||
})
|
||||
|
||||
# t1 (배경 - 문제제기): 짧은 인용 한 줄 — quote-big-mark
|
||||
blocks.append({
|
||||
"type": "quote-big-mark",
|
||||
"topic_id": 1,
|
||||
"area": "body",
|
||||
"purpose": "문제제기",
|
||||
"data": {
|
||||
"quote_text": "건설산업에서 DX와 BIM이 동일 개념으로 인식되고 있다",
|
||||
"source": ""
|
||||
}
|
||||
})
|
||||
|
||||
# t2 (배경 - 사례 비교): 가로 2열 비교 — comparison-2col
|
||||
blocks.append({
|
||||
"type": "comparison-2col",
|
||||
"topic_id": 2,
|
||||
"area": "body",
|
||||
"purpose": "근거사례",
|
||||
"data": {
|
||||
"left_title": "스마트건설 활성화 방안",
|
||||
"left_subtitle": "2022.07",
|
||||
"left_content": "• 추진과제: 건설산업 디지털화\n• 실행과제: BIM 전면 도입, BIM 전문인력 양성",
|
||||
"right_title": "제7차 건설기술진흥 기본계획",
|
||||
"right_subtitle": "2023.12",
|
||||
"right_content": "• 추진방향: 디지털 전환을 통한 스마트 건설 확산\n• 추진과제: BIM 도입으로 건설산업 디지털화"
|
||||
}
|
||||
})
|
||||
|
||||
# t3 (본심 - 핵심): DX vs BIM 차이 — comparison-2col (큰 비교)
|
||||
blocks.append({
|
||||
"type": "comparison-2col",
|
||||
"topic_id": 3,
|
||||
"area": "body",
|
||||
"purpose": "핵심전달",
|
||||
"data": {
|
||||
"left_title": "DX (상위개념)",
|
||||
"left_subtitle": "Digital Transformation",
|
||||
"left_content": "• BIM, GIS, 디지털 트윈 등 핵심기술의 융합을 통해서만 실현 가능한 상위개념\n• Engineering + Management 통합\n• 근본적 문제의식을 통한 개선\n• 전 생애주기 활용 시스템\n• 자체 수행 능력 — 지속가능성 확보",
|
||||
"right_title": "BIM (하위기술)",
|
||||
"right_subtitle": "Building Information Modeling",
|
||||
"right_content": "• 시설물의 생애주기 동안 발생한 모든 정보를 3차원 모델 기반으로 통합·관리하는 정보 관리 도구\n• Only 3D (형상 구현 중심)\n• 기존 2D 설계 방식 유지\n• (설계/시공/운영) 분야별 단절\n• S/W 제작사 판매 정책에 의존"
|
||||
}
|
||||
})
|
||||
|
||||
# t4 (sidebar - 용어 정의): 풀 정의 — card-numbered
|
||||
blocks.append({
|
||||
"type": "card-numbered",
|
||||
"topic_id": 4,
|
||||
"area": "sidebar",
|
||||
"purpose": "용어정의",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"title": "건설산업",
|
||||
"description": "부동산 개발, 설계, 시공, 유지보수를 포괄하는 종합산업으로, 광범위한 기술을 통합·융합하여 인프라를 만드는 산업"
|
||||
},
|
||||
{
|
||||
"title": "BIM",
|
||||
"description": "형상정보와 속성정보가 포함된 3D 모델로, 시설물의 생애주기 동안 발생한 모든 정보를 3차원 모델 기반으로 통합·관리하는 정보 관리 도구"
|
||||
},
|
||||
{
|
||||
"title": "DX",
|
||||
"description": "디지털 기술을 활용하여 업무방식과 가치 창출 구조를 전환하는 과정 및 결과. BIM, GIS, 디지털 트윈의 기술융합을 통해서만 실현 가능한 상위개념"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
# t5 (footer - 결론): 원문 그대로 — banner-gradient
|
||||
blocks.append({
|
||||
"type": "banner-gradient",
|
||||
"topic_id": 5,
|
||||
"area": "footer",
|
||||
"purpose": "결론강조",
|
||||
"data": {
|
||||
"text": "BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다",
|
||||
"sub_text": "각 용어의 정의, 역할, 상호관계에 대한 체계적 정립 필요"
|
||||
}
|
||||
})
|
||||
|
||||
layout = {
|
||||
"title": analysis.get("title", "슬라이드"),
|
||||
"_container_specs": container_specs,
|
||||
"pages": [{
|
||||
"grid_areas": preset["grid_areas"],
|
||||
"grid_columns": preset["grid_columns"],
|
||||
"grid_rows": preset["grid_rows"],
|
||||
"blocks": blocks,
|
||||
"area_styles": {
|
||||
"body": "--font-body: 0.85rem; --spacing-inner: 10px; --spacing-block: 10px;",
|
||||
"sidebar": "--font-body: 0.82rem; --spacing-inner: 8px; --spacing-block: 10px;",
|
||||
"footer": "",
|
||||
},
|
||||
}],
|
||||
}
|
||||
|
||||
html = render_slide(layout)
|
||||
m = await asyncio.to_thread(measure_rendered_heights, html)
|
||||
s = await asyncio.to_thread(capture_slide_screenshot, html)
|
||||
|
||||
_save(out_dir, "sim_a.html", html)
|
||||
if s:
|
||||
import base64 as b64
|
||||
(out_dir / "sim_a_screenshot.png").write_bytes(b64.b64decode(s))
|
||||
_save(out_dir, "sim_a_measurement.json", m)
|
||||
|
||||
print("컨테이너:")
|
||||
for name, data in m.get("containers", {}).items():
|
||||
status = "✅" if not data.get("overflowed") else f"❌ +{data.get('excess_px', 0)}px"
|
||||
print(f" {name}: {data.get('scrollHeight', 0)}px / {data.get('allocatedHeight', 0)}px {status}")
|
||||
slide = m.get("slide", {})
|
||||
print(f" slide: {slide.get('scrollHeight', 0)}px / 720px {'✅' if not slide.get('overflowed') else '❌'}")
|
||||
|
||||
print(f"\n결과: {out_dir}/sim_a_screenshot.png")
|
||||
|
||||
|
||||
def _save(out_dir, name, data):
|
||||
path = out_dir / name
|
||||
if isinstance(data, str):
|
||||
path.write_text(data, encoding="utf-8")
|
||||
else:
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s", datefmt="%H:%M:%S")
|
||||
logging.getLogger("selenium").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user