Phase W + V' 완료: before→filled→after 파이프라인 + 조립 로직 수정

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>
This commit is contained in:
2026-04-06 05:00:52 +09:00
parent 24eb1bc5ad
commit 1f7579cf64
64 changed files with 13955 additions and 696 deletions

View File

@@ -376,14 +376,15 @@ def verify_no_forbidden_content(
# Layer 3: 구조 검증
# ═══════════════════════════════════════════════════════════
# Phase T: overflow:hidden 필수 요구 제거.
# Phase T 프롬프트가 "overflow:hidden 금지"를 지시하므로 L3에서 요구하면 모순.
# 텍스트 잘림은 L4(Selenium 실측)에서 감지.
REQUIRED_PATTERNS: dict[str, list[str]] = {
"body_bg": ["overflow:hidden|overflow: hidden"],
"body_bg": [],
"body_core": [
"overflow:hidden|overflow: hidden",
"key-msg",
],
"sidebar": [
"overflow:hidden|overflow: hidden",
"padding-left",
"text-indent",
],
@@ -395,8 +396,12 @@ def verify_structure(
generated_html: str,
area_name: str,
has_image: bool = False,
font_hierarchy: dict | None = None,
) -> VerificationResult:
"""필수 CSS/HTML 패턴이 존재하는지 검증."""
"""필수 CSS/HTML 패턴이 존재하는지 검증.
Phase T-8: font_hierarchy가 제공되면 폰트 위계 위반도 검사.
"""
patterns = REQUIRED_PATTERNS.get(area_name, [])
missing = []
@@ -410,13 +415,36 @@ def verify_structure(
if "slide-img-" not in generated_html:
missing.append("slide-img-* (이미지 태그)")
# Phase T-8: 폰트 위계 검사
font_warnings = []
if font_hierarchy:
role_font_map = {
"body_bg": font_hierarchy.get("bg", 11),
"body_core": font_hierarchy.get("core", 12),
"sidebar": font_hierarchy.get("sidebar", 10),
"footer": font_hierarchy.get("core", 12),
}
max_font = role_font_map.get(area_name)
if max_font:
# HTML에서 font-size 값 추출
font_sizes = re.findall(r"font-size:\s*(\d+(?:\.\d+)?)\s*px", generated_html)
for fs_str in font_sizes:
fs = float(fs_str)
if fs > max_font + 1: # 1px 허용 오차
font_warnings.append(
f"폰트 위계 위반: {area_name}에서 {fs}px 사용 (최대 {max_font}px)"
)
passed = len(missing) == 0
all_errors = [f"필수 패턴 누락: {p}" for p in missing]
return VerificationResult(
passed=passed,
area_name=area_name,
checks={"structure": passed},
score=1.0 if passed else (1.0 - len(missing) / max(1, len(patterns))),
errors=[f"필수 패턴 누락: {p}" for p in missing],
errors=all_errors,
warnings=font_warnings,
)
@@ -551,18 +579,35 @@ async def generate_with_retry(
return [topic_map[tid] for tid in info.get("topic_ids", []) if tid in topic_map]
area_texts = {}
def _get_role_text(role_topics):
"""structured_text 우선, 없으면 source_hint 키워드로 sections 매칭."""
texts = []
for t in role_topics:
st = t.get("structured_text", "")
if st:
texts.append(st)
else:
# fallback: source_hint에서 키워드 추출하여 매칭
hint = t.get("source_hint", "")
keywords = [w for w in hint.split() if len(w) >= 2][:3]
matched = _map_sections_for_role(sections, [t], keywords) if keywords else ""
if matched:
texts.append(matched)
return "\n\n".join(texts) if texts else ""
bg_topics = get_topics_for_role("배경")
if bg_topics:
area_texts["body_bg"] = _map_sections_for_role(sections, bg_topics, ["혼용", "사례"])
area_texts["body_bg"] = _get_role_text(bg_topics)
core_topics = get_topics_for_role("본심")
if core_topics:
area_texts["body_core"] = _map_sections_for_role(sections, core_topics, ["관계", "핵심기술", "DX"])
area_texts["body_core"] = _get_role_text(core_topics)
ref_topics = get_topics_for_role("첨부")
if ref_topics:
area_texts["sidebar"] = _get_definitions(content)
area_texts["sidebar"] = _get_role_text(ref_topics)
conclusion_topics = get_topics_for_role("결론")
if conclusion_topics:
area_texts["footer"] = _get_conclusion(content)
area_texts["footer"] = _get_role_text(conclusion_topics)
has_image_areas = set()
if images: