Add Type B slide pipeline and recipe rendering updates
This commit is contained in:
@@ -158,51 +158,8 @@ def validate_stage_1a(
|
||||
})
|
||||
return errors
|
||||
|
||||
# weight 합 검증 (0.9~1.1)
|
||||
total_weight = sum(
|
||||
info.get("weight", 0) for info in page_struct.values()
|
||||
if isinstance(info, dict)
|
||||
)
|
||||
if total_weight < 0.9 or total_weight > 1.1:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.weight",
|
||||
"localization": f"weight 합 {total_weight:.2f} (범위: 0.9~1.1)",
|
||||
"instruction": f"weight 합이 1.0에 가깝도록 조정하라. 현재 합: {total_weight:.2f}",
|
||||
})
|
||||
|
||||
# 유형에 따른 구조 검증
|
||||
layout_template = analysis.get("layout_template", "A")
|
||||
if layout_template == "A":
|
||||
# 유형 A: 본심 필수
|
||||
core_info = page_struct.get("본심", {})
|
||||
if not core_info or not isinstance(core_info, dict):
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심",
|
||||
"localization": "본심 역할이 page_structure에 없음",
|
||||
"instruction": "page_structure에 본심 역할을 추가하라. 본심은 슬라이드의 핵심 콘텐츠이다.",
|
||||
})
|
||||
elif core_info.get("weight", 0) < 0.3:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.본심.weight",
|
||||
"localization": f"본심 weight {core_info['weight']:.2f} < 0.3",
|
||||
"instruction": "본심은 슬라이드의 핵심. weight 0.3 이상 필요.",
|
||||
})
|
||||
elif layout_template == "B":
|
||||
# 유형 B: 결론(footer) 필수, 나머지 자유
|
||||
has_footer = any(
|
||||
isinstance(info, dict) and info.get("zone") == "footer"
|
||||
for info in page_struct.values()
|
||||
)
|
||||
if not has_footer and "결론" not in page_struct:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.footer",
|
||||
"localization": "결론(footer) 역할이 없음",
|
||||
"instruction": "유형 B에서도 결론 역할(zone: footer)은 필수이다.",
|
||||
})
|
||||
# Phase Y: page_structure 검증은 validate_page_structure()에서 별도 수행.
|
||||
# Stage 1A에서는 Kei 응답의 page_structure를 검증하지 않음.
|
||||
|
||||
# 필수 필드 검증
|
||||
for t in topics:
|
||||
@@ -243,7 +200,8 @@ def validate_stage_1a(
|
||||
# 원본 ## 섹션 수 vs topic 수 비교
|
||||
original_sections = re.findall(r"^## .+$", clean_text, re.MULTILINE)
|
||||
# 유형 B에서는 하나의 섹션을 여러 꼭지로 나눌 수 있으므로 허용 폭 확대
|
||||
max_diff = 4 if layout_template == "B" else 2
|
||||
_layout = analysis.get("layout_template", "A")
|
||||
max_diff = 4 if _layout == "B" else 2
|
||||
if len(original_sections) > 0 and abs(len(topics) - len(original_sections)) > max_diff:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
@@ -336,18 +294,29 @@ def validate_stage_1b(
|
||||
})
|
||||
|
||||
# ── 모순 탐지 (결정 테이블) ──
|
||||
# Phase Y: Type B에서는 purpose/relation_type이 블록 선택의 핵심 입력이 아님
|
||||
# (tag 매칭이 item_count + content_example로 동작)
|
||||
# → Type B: 경고만 (파이프라인 계속). Type A: hard fail 유지.
|
||||
|
||||
if purpose in CONTRADICTIONS:
|
||||
if relation_type in CONTRADICTIONS[purpose]:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": f"topics[{tid}].relation_type",
|
||||
"localization": f"topic {tid}: purpose '{purpose}' × relation_type '{relation_type}' 모순",
|
||||
"current_value": f"purpose={purpose}, relation_type={relation_type}",
|
||||
"evidence": f"'{purpose}'는 '{relation_type}'와 논리적으로 양립 불가",
|
||||
"instruction": f"relation_type을 재판단하라. '{purpose}'에 적합한 관계는 "
|
||||
f"{[r for r in VALID_RELATION_TYPES if r not in CONTRADICTIONS.get(purpose, [])]}",
|
||||
})
|
||||
if layout_template == "B":
|
||||
# Type B: 경고만
|
||||
logger.warning(
|
||||
f"[T-2 모순경고] topic {tid}: purpose '{purpose}' × relation_type '{relation_type}' "
|
||||
f"— Type B에서는 보조 힌트이므로 경고만"
|
||||
)
|
||||
else:
|
||||
# Type A: hard fail 유지
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": f"topics[{tid}].relation_type",
|
||||
"localization": f"topic {tid}: purpose '{purpose}' × relation_type '{relation_type}' 모순",
|
||||
"current_value": f"purpose={purpose}, relation_type={relation_type}",
|
||||
"evidence": f"'{purpose}'는 '{relation_type}'와 논리적으로 양립 불가",
|
||||
"instruction": f"relation_type을 재판단하라. '{purpose}'에 적합한 관계는 "
|
||||
f"{[r for r in VALID_RELATION_TYPES if r not in CONTRADICTIONS.get(purpose, [])]}",
|
||||
})
|
||||
|
||||
if purpose in SOFT_WARNINGS:
|
||||
if relation_type in SOFT_WARNINGS[purpose]:
|
||||
@@ -400,3 +369,35 @@ def validate_stage_1b(
|
||||
})
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_page_structure(page_struct: dict) -> list[dict]:
|
||||
"""Phase Y: section_parser가 생성한 page_structure 검증.
|
||||
|
||||
Stage 1A 후, section_parser + 블록 매칭으로 page_structure가 채워진 후 호출.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if not page_struct:
|
||||
errors.append({
|
||||
"severity": "FATAL",
|
||||
"field": "page_structure",
|
||||
"localization": "page_structure가 비어있음",
|
||||
"instruction": "section_parser가 영역을 생성하지 못함",
|
||||
})
|
||||
return errors
|
||||
|
||||
# weight 합 검증 (0.9~1.1)
|
||||
total_weight = sum(
|
||||
info.get("weight", 0) for info in page_struct.values()
|
||||
if isinstance(info, dict)
|
||||
)
|
||||
if total_weight < 0.9 or total_weight > 1.1:
|
||||
errors.append({
|
||||
"severity": "RETRYABLE",
|
||||
"field": "page_structure.weight",
|
||||
"localization": f"weight 합 {total_weight:.2f} (범위: 0.9~1.1)",
|
||||
"instruction": f"weight 합이 1.0에 가깝도록 조정하라. 현재 합: {total_weight:.2f}",
|
||||
})
|
||||
|
||||
return errors
|
||||
|
||||
Reference in New Issue
Block a user