Checkpoint Type B pipeline refinement for run-002 and run-003
This commit is contained in:
@@ -21,7 +21,10 @@ if str(DESIGN_AGENT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(DESIGN_AGENT_ROOT))
|
||||
|
||||
from src.renderer import render_slide_from_html # type: ignore
|
||||
from src.slide_measurer import measure_rendered_heights # type: ignore
|
||||
from src.slide_measurer import measure_rendered_heights, capture_slide_screenshot, settings # type: ignore
|
||||
from selenium import webdriver # type: ignore
|
||||
from selenium.webdriver.chrome.options import Options # type: ignore
|
||||
from selenium.webdriver.common.by import By # type: ignore
|
||||
|
||||
COMPARISON_MARKER = "comparison-summary-card"
|
||||
RELATION_MARKER = "relation-diagram-card"
|
||||
@@ -52,7 +55,13 @@ def zone_overflow_names(measurement: dict) -> list[str]:
|
||||
return names
|
||||
|
||||
|
||||
def validate_outputs(generated: dict, measurement: dict, required_titles: list[str], run_mode: str) -> tuple[str, list[str], list[str]]:
|
||||
def validate_outputs(
|
||||
generated: dict,
|
||||
measurement: dict,
|
||||
required_titles: list[str],
|
||||
run_mode: str,
|
||||
layout_template: str = "",
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
body_html = generated.get("body_html", "")
|
||||
sidebar_html = generated.get("sidebar_html", "")
|
||||
footer_html = generated.get("footer_html", "")
|
||||
@@ -69,50 +78,74 @@ def validate_outputs(generated: dict, measurement: dict, required_titles: list[s
|
||||
|
||||
if measurement_missing:
|
||||
failures.append("Verify-Measurement")
|
||||
actions.append("?? ??? ?? ???? stage 3/4 ?? ? ?? ??? ?? ???? ?? ??? ?? ????.")
|
||||
actions.append("?? ??? ?? ?? stage 3/4 ???? ?? ???? ??.")
|
||||
|
||||
if slide_overflow:
|
||||
failures.append("Verify-RenderSlide")
|
||||
actions.append("slide 전체 overflow를 해소하도록 layout budget 또는 전체 레이아웃 구조를 조정한다.")
|
||||
actions.append("slide ?? overflow? ????? layout budget ?? ?? ???? ??? ????.")
|
||||
|
||||
if zone_overflows:
|
||||
failures.append("Verify-RenderZone")
|
||||
actions.append(f"overflow가 발생한 zone({', '.join(zone_overflows)})의 content budget, block 수, typography를 재조정한다.")
|
||||
actions.append(f"overflow? ??? zone({', '.join(zone_overflows)})? content budget, block ?, typography? ?????.")
|
||||
|
||||
if '???' in visible_text or '?? ??' in visible_text:
|
||||
failures.append("Verify-Placeholder")
|
||||
actions.append("placeholder나 깨진 라벨을 제거하고, 원문 제목/문장으로 다시 채운다.")
|
||||
actions.append("placeholder? ?? ??? ????, ?? ??/???? ?? ???.")
|
||||
|
||||
matched_titles = sum(1 for title in required_titles if title and title in visible_text)
|
||||
visible_len = len(re.sub(r'\s+', ' ', visible_text).strip())
|
||||
if matched_titles < max(2, min(len(required_titles), 3)):
|
||||
failures.append("Verify-SectionTitles")
|
||||
actions.append("원문 섹션 제목을 가시 텍스트에 더 직접적으로 유지한다.")
|
||||
actions.append("?? ?? ??? ?? ???? ? ????? ????.")
|
||||
|
||||
if run_mode == 'run001':
|
||||
core_message_ok = all(any(marker in visible_text for marker in variants) for variants in CORE_MESSAGE_MARKERS)
|
||||
if not core_message_ok:
|
||||
failures.append("Verify-CoreMessage")
|
||||
actions.append("원문 표현을 유지하되 `상위 개념`과 `핵심 기술/핵심 인프라 기술` 판단이 가시 텍스트에 분명히 드러나도록 정리한다.")
|
||||
actions.append("?? ??? ???? `?? ??`? `?? ??/?? ??? ??` ??? ?? ???? ??? ????? ????.")
|
||||
|
||||
if IMAGE_REFERENCE_KEY not in visible_text:
|
||||
failures.append("Verify-ImageRef")
|
||||
actions.append("이미지/도해 참조 문구 `DX와 핵심기술간 상호관계`를 숨김 영역이 아닌 가시 블록으로 유지한다.")
|
||||
actions.append("???/?? ?? ?? `DX? ????? ????`? ?? ??? ?? ?? ???? ????.")
|
||||
|
||||
comparison_visible = (COMPARISON_MARKER in body_html) and all(key in visible_text for key in COMPARE_KEYS)
|
||||
if not comparison_visible:
|
||||
failures.append("Verify-ComparisonVisible")
|
||||
actions.append("비교 핵심 4축(범위, 프로세스, 성과품, 확장성)을 화면에 바로 보이는 요약 블록으로 강제한다.")
|
||||
actions.append("?? ?? 4?(??, ????, ???, ???)? ??? ?? ??? ?? ???? ????.")
|
||||
|
||||
if RELATION_MARKER not in body_html:
|
||||
failures.append("Verify-DesignStructure")
|
||||
actions.append("핵심 관계를 설명하는 시각적 관계도 블록을 본문 중심 구조로 유지한다.")
|
||||
actions.append("?? ??? ???? ??? ??? ??? ?? ?? ??? ????.")
|
||||
else:
|
||||
if len(re.sub(r'\s+', ' ', visible_text).strip()) < 260:
|
||||
if visible_len < 420:
|
||||
failures.append("Verify-ContentDensity")
|
||||
actions.append("본문과 보조 영역의 원문 문장 보존량을 높여 내용 밀도를 보강한다.")
|
||||
if not body_html or not sidebar_html:
|
||||
actions.append("??? ?? ??? ?? ?? ???? ?? ?? ??? ????.")
|
||||
if not body_html:
|
||||
failures.append("Verify-DesignStructure")
|
||||
actions.append("body와 sidebar의 역할을 분리하여 섹션별 배치를 다시 잡는다.")
|
||||
actions.append("body ??? ?? ??? ?? ?? ?? ??? ???.")
|
||||
if matched_titles < max(3, len([title for title in required_titles if title]) - 1):
|
||||
failures.append("Verify-SectionTitles")
|
||||
actions.append("?? ?? ??? ? ?? ?? ?? ???? ????.")
|
||||
if layout_template == "B_GOAL":
|
||||
for marker, reason in [
|
||||
("Goal details", "?? ?? ?? ?? ?? ???? ????."),
|
||||
("Process details", "?? ?? ?? ?? ???? ????."),
|
||||
("Stakeholder details", "??? ???? ?? ?? ???? ????."),
|
||||
]:
|
||||
if marker not in body_html:
|
||||
failures.append("Verify-DesignStructure")
|
||||
actions.append(reason)
|
||||
if body_html.count("<li") < 10:
|
||||
failures.append("Verify-ContentDensity")
|
||||
actions.append("??/??/?? ??? ?? bullet ?? ?? ?? ??? ???.")
|
||||
elif layout_template == "B_RPP":
|
||||
for title in required_titles[:3]:
|
||||
if title and title not in body_html:
|
||||
failures.append("Verify-SectionTitles")
|
||||
actions.append("??/??/??? ?? ?? ??? ??? ?? ????.")
|
||||
if body_html.count("<li") < 14:
|
||||
failures.append("Verify-ContentDensity")
|
||||
actions.append("??/??/?? ??? ?? bullet ?? ??? ????? ??? ???.")
|
||||
|
||||
if failures:
|
||||
return "revise", sorted(set(failures)), list(dict.fromkeys(actions))
|
||||
@@ -224,6 +257,42 @@ def post_comment_if_configured(repo: str, issue_number: int, body_file: Path) ->
|
||||
create_comment(base_url, token, repo, issue_number, body)
|
||||
|
||||
|
||||
def refresh_final_screenshot(final_html_path: Path, output_dir: Path) -> None:
|
||||
if not final_html_path.exists():
|
||||
return
|
||||
|
||||
driver = None
|
||||
try:
|
||||
options = Options()
|
||||
options.add_argument("--headless=new")
|
||||
options.add_argument("--disable-gpu")
|
||||
options.add_argument("--no-sandbox")
|
||||
options.add_argument("--disable-dev-shm-usage")
|
||||
options.add_argument("--force-device-scale-factor=1")
|
||||
options.add_argument(f"--window-size={settings.slide_width},{settings.slide_height + 200}")
|
||||
driver = webdriver.Chrome(options=options)
|
||||
driver.get(final_html_path.resolve().as_uri())
|
||||
slide = driver.find_element(By.CSS_SELECTOR, ".slide")
|
||||
screenshot_bytes = slide.screenshot_as_png
|
||||
(output_dir / "final-screenshot-current.png").write_bytes(screenshot_bytes)
|
||||
(output_dir / "final-screenshot.png").write_bytes(screenshot_bytes)
|
||||
except Exception:
|
||||
html = final_html_path.read_text(encoding="utf-8")
|
||||
screenshot_b64 = capture_slide_screenshot(html)
|
||||
if not screenshot_b64:
|
||||
return
|
||||
import base64
|
||||
screenshot_bytes = base64.b64decode(screenshot_b64)
|
||||
(output_dir / "final-screenshot-current.png").write_bytes(screenshot_bytes)
|
||||
(output_dir / "final-screenshot.png").write_bytes(screenshot_bytes)
|
||||
finally:
|
||||
if driver:
|
||||
try:
|
||||
driver.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def compact_text(text: str, max_len: int) -> str:
|
||||
normalized = re.sub(r"\s+", " ", text).strip()
|
||||
if len(normalized) <= max_len:
|
||||
@@ -496,13 +565,18 @@ KPI / 판정 결과
|
||||
post_comment_if_configured(args.repo_slug, issue_numbers[5], step_comment_bodies[6])
|
||||
continue
|
||||
|
||||
screenshot_path = output_dir / "final-screenshot-current.png"
|
||||
if (not screenshot_path.exists()) or (screenshot_path.stat().st_mtime < final_html_path.stat().st_mtime):
|
||||
refresh_final_screenshot(final_html_path, output_dir)
|
||||
|
||||
generated = read_json(generated_path)
|
||||
measurement = read_json(measurement_path)
|
||||
stage1a_data = read_json(stage1a)
|
||||
required_titles = [item.get("title", "") for item in stage1a_data.get("topics", [])]
|
||||
topic_count = len(required_titles)
|
||||
run_mode = "run001" if topic_count >= 5 else "generic"
|
||||
status, failures, actions = validate_outputs(generated, measurement, required_titles, run_mode)
|
||||
layout_template = str(stage1a_data.get("analysis", {}).get("layout_template", "") or "")
|
||||
status, failures, actions = validate_outputs(generated, measurement, required_titles, run_mode, layout_template)
|
||||
final_html_text = final_html_path.read_text(encoding="utf-8")
|
||||
if 'width:100%; height:28px' in final_html_text:
|
||||
status = "revise"
|
||||
|
||||
Reference in New Issue
Block a user