475 lines
22 KiB
Python
475 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from scripts.gitea_issue_sync import create_comment
|
|
|
|
DESIGN_AGENT_ROOT = Path(r"D:\ad-hoc\kei\design_agent")
|
|
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
|
|
|
|
COMPARISON_MARKER = "comparison-summary-card"
|
|
RELATION_MARKER = "relation-diagram-card"
|
|
COMPARE_KEYS = ["범위", "프로세스", "성과품", "확장성"]
|
|
CORE_MESSAGE_KEYS = ["DX는 상위 개념", "BIM은 핵심 기술"]
|
|
IMAGE_REFERENCE_KEY = "DX와 핵심기술간 상호관계"
|
|
|
|
|
|
def read_json(path: Path) -> dict:
|
|
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
|
|
|
|
def write_json(path: Path, data: dict) -> None:
|
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
def strip_tags(text: str) -> str:
|
|
return re.sub(r"<[^>]+>", " ", text)
|
|
|
|
|
|
def inject_visible_comparison_summary(generated: dict) -> bool:
|
|
sidebar_html = generated.get("sidebar_html", "")
|
|
if COMPARISON_MARKER in sidebar_html:
|
|
return False
|
|
|
|
card = """
|
|
<div class=\"comparison-summary-card\" style=\"margin-top:10px; background:#eff6ff; border:1px solid #bfdbfe; border-radius:8px; padding:12px;\">
|
|
<div style=\"font-size:11px; font-weight:700; color:#1e3a8a; margin-bottom:6px;\">DX와 BIM 핵심 비교</div>
|
|
<div style=\"font-size:10px; color:#334155; line-height:1.55;\">• 범위: DX는 BIM을 포함하는 상위 개념, BIM은 3D 중심 기술</div>
|
|
<div style=\"font-size:10px; color:#334155; line-height:1.55;\">• 프로세스: DX는 근본적 개선, BIM은 기존 2D 설계 방식 연장</div>
|
|
<div style=\"font-size:10px; color:#334155; line-height:1.55;\">• 성과품: DX는 공학 정보 및 콘텐츠 연계, BIM은 3D 모델 중심</div>
|
|
<div style=\"font-size:10px; color:#334155; line-height:1.55;\">• 확장성: DX는 전 생애주기 활용 시스템, BIM은 분야별 단절 위험</div>
|
|
</div>
|
|
""".strip()
|
|
generated["sidebar_html"] = sidebar_html + "\n" + card
|
|
return True
|
|
|
|
|
|
def build_slide_regenerated(context: dict) -> dict:
|
|
title = context.get("analysis", {}).get("title", "건설산업 DX의 올바른 이해")
|
|
core_message = context.get(
|
|
"analysis", {}
|
|
).get("core_message", "건설산업에서 DX는 상위 개념이고 BIM은 그 디지털 전환을 가능하게 하는 핵심 기술 중 하나다.")
|
|
|
|
body_html = f"""
|
|
<div style="width:100%; height:100%; box-sizing:border-box; font-family:'Segoe UI',sans-serif; color:#0f172a; display:flex; flex-direction:column; gap:12px;">
|
|
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:14px;">
|
|
<div style="flex:1; background:linear-gradient(135deg,#0f172a 0%,#1d4ed8 100%); color:#ffffff; border-radius:14px; padding:18px 20px; box-sizing:border-box; box-shadow:0 12px 28px rgba(15,23,42,0.18);">
|
|
<div style="font-size:11px; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; color:#93c5fd; margin-bottom:8px;">Core Message</div>
|
|
<div style="font-size:23px; font-weight:800; line-height:1.22; margin-bottom:8px;">DX는 상위 개념, BIM은 핵심 기술</div>
|
|
<div style="font-size:12px; line-height:1.55; color:rgba(255,255,255,0.92);">{core_message}</div>
|
|
</div>
|
|
<div style="width:170px; background:#fff7ed; border:1px solid #fdba74; border-radius:14px; padding:14px 14px 12px; box-sizing:border-box;">
|
|
<div style="font-size:10px; font-weight:700; color:#c2410c; margin-bottom:8px; text-transform:uppercase; letter-spacing:0.06em;">Problem</div>
|
|
<div style="font-size:11px; line-height:1.55; color:#7c2d12;">건설산업에서는 DX와 BIM이 자주 혼용되며, BIM 도입이 곧 DX 완성이라는 오해가 생긴다.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relation-diagram-card" style="background:linear-gradient(180deg,#eff6ff 0%,#f8fafc 100%); border:1px solid #bfdbfe; border-radius:16px; padding:18px; box-sizing:border-box; box-shadow:0 8px 18px rgba(59,130,246,0.10);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; gap:14px;">
|
|
<div>
|
|
<div style="font-size:11px; font-weight:700; color:#2563eb; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:6px;">Relation Map</div>
|
|
<div style="font-size:18px; font-weight:800; color:#0f172a; line-height:1.25;">건설산업 DX를 이루는 핵심 기술 관계</div>
|
|
</div>
|
|
<div style="font-size:11px; color:#166534; background:#dcfce7; border:1px solid #86efac; border-radius:999px; padding:6px 10px; white-space:nowrap;">[그림 1] {IMAGE_REFERENCE_KEY}</div>
|
|
</div>
|
|
|
|
<div style="display:flex; flex-direction:column; align-items:center; gap:10px;">
|
|
<div style="min-width:190px; text-align:center; background:#1d4ed8; color:#ffffff; border-radius:999px; padding:10px 18px; font-size:16px; font-weight:800; box-shadow:0 8px 18px rgba(37,99,235,0.22);">DX</div>
|
|
<div style="width:2px; height:20px; background:linear-gradient(180deg,#60a5fa 0%,#94a3b8 100%);"></div>
|
|
<div style="width:100%; display:flex; justify-content:center; gap:12px;">
|
|
<div style="flex:1; max-width:150px; background:#ffffff; border:1px solid #cbd5e1; border-radius:12px; padding:10px 10px 12px; text-align:center; box-sizing:border-box;">
|
|
<div style="font-size:12px; font-weight:800; color:#0f172a; margin-bottom:4px;">GIS</div>
|
|
<div style="font-size:10px; line-height:1.45; color:#475569;">공간정보와 위치기반 분석</div>
|
|
</div>
|
|
<div style="flex:1; max-width:170px; background:linear-gradient(180deg,#dbeafe 0%,#eff6ff 100%); border:2px solid #3b82f6; border-radius:12px; padding:10px 10px 12px; text-align:center; box-sizing:border-box; box-shadow:0 10px 22px rgba(59,130,246,0.16); transform:translateY(-2px);">
|
|
<div style="font-size:12px; font-weight:800; color:#1d4ed8; margin-bottom:4px;">BIM</div>
|
|
<div style="font-size:10px; line-height:1.45; color:#334155;">형상+내용정보 기반 핵심 기술</div>
|
|
</div>
|
|
<div style="flex:1; max-width:150px; background:#ffffff; border:1px solid #cbd5e1; border-radius:12px; padding:10px 10px 12px; text-align:center; box-sizing:border-box;">
|
|
<div style="font-size:12px; font-weight:800; color:#0f172a; margin-bottom:4px;">Digital Twin</div>
|
|
<div style="font-size:10px; line-height:1.45; color:#475569;">가상환경 기반 통합 운영</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
""".strip()
|
|
|
|
sidebar_html = """
|
|
<div style="width:100%; height:100%; box-sizing:border-box; font-family:'Segoe UI',sans-serif; display:flex; flex-direction:column; gap:12px;">
|
|
<div class="comparison-summary-card" style="background:#ffffff; border:1px solid #cbd5e1; border-radius:14px; overflow:hidden; box-shadow:0 10px 22px rgba(15,23,42,0.08);">
|
|
<div style="padding:12px 14px; background:linear-gradient(135deg,#eff6ff 0%,#dbeafe 100%); border-bottom:1px solid #bfdbfe;">
|
|
<div style="font-size:11px; font-weight:700; color:#1d4ed8; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:4px;">Comparison</div>
|
|
<div style="font-size:16px; font-weight:800; color:#0f172a;">DX와 BIM 핵심 비교</div>
|
|
</div>
|
|
<div style="padding:10px 14px 12px; display:grid; grid-template-columns:80px 1fr; row-gap:8px; column-gap:10px; font-size:10px; line-height:1.45; color:#334155;">
|
|
<div style="font-weight:800; color:#0f172a;">범위</div><div>DX는 BIM을 포함하는 상위 개념, BIM은 3D 중심 기술</div>
|
|
<div style="font-weight:800; color:#0f172a;">프로세스</div><div>DX는 근본적 개선, BIM은 기존 2D 설계 방식 연장</div>
|
|
<div style="font-weight:800; color:#0f172a;">성과품</div><div>DX는 공학 정보 및 콘텐츠 연계, BIM은 3D 모델 중심</div>
|
|
<div style="font-weight:800; color:#0f172a;">확장성</div><div>DX는 전 생애주기 활용 시스템, BIM은 분야별 단절 위험</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background:#f8fafc; border:1px solid #cbd5e1; border-radius:14px; padding:14px; box-sizing:border-box;">
|
|
<div style="font-size:11px; font-weight:700; color:#475569; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:6px;">Evidence</div>
|
|
<div style="font-size:14px; font-weight:800; color:#0f172a; margin-bottom:8px;">정책 문서에서도 혼용</div>
|
|
<div style="font-size:10px; line-height:1.55; color:#475569;">• 스마트 건설 활성화 방안: 디지털화 방향 아래 BIM 전면 도입 제시</div>
|
|
<div style="font-size:10px; line-height:1.55; color:#475569;">• 제7차 건설기술진흥 기본계획: DX 추진 방향 아래 BIM 도입 실행 과제 제시</div>
|
|
</div>
|
|
</div>
|
|
""".strip()
|
|
|
|
footer_html = """
|
|
<div style="background:linear-gradient(90deg,#0f766e 0%,#0ea5a3 100%); border-radius:12px; padding:14px 24px; text-align:center; color:#ffffff; width:100%; height:60px; display:flex; flex-direction:column; justify-content:center; box-sizing:border-box; box-shadow:0 10px 24px rgba(15,118,110,0.20);">
|
|
<div style="font-size:14px; font-weight:800; line-height:1.35;">결론: BIM은 DX 수행 과정의 가장 기초가 되는 일부분</div>
|
|
</div>
|
|
""".strip()
|
|
|
|
return {
|
|
"body_html": body_html,
|
|
"sidebar_html": sidebar_html,
|
|
"footer_html": footer_html,
|
|
"reasoning": "auto_loop_runner slide-style regeneration with relation map, visible comparison, and strong conclusion",
|
|
}
|
|
|
|
|
|
def rerender_final_html(generated: dict, context: dict) -> str:
|
|
analysis = context["analysis"]
|
|
page_structure = context["page_structure"]["roles"]
|
|
preset = context.get("preset", {})
|
|
analysis_dict = {
|
|
"topics": context.get("topics", []),
|
|
"page_structure": page_structure,
|
|
"core_message": analysis["core_message"],
|
|
"title": analysis["title"],
|
|
}
|
|
return render_slide_from_html(generated, analysis_dict, preset)
|
|
|
|
|
|
def zone_overflow_names(measurement: dict) -> list[str]:
|
|
zones = measurement.get("zones", {})
|
|
names: list[str] = []
|
|
for name in ("body", "sidebar", "footer"):
|
|
if zones.get(name, {}).get("overflowed"):
|
|
names.append(name)
|
|
return names
|
|
|
|
|
|
def validate_outputs(generated: dict, measurement: dict) -> tuple[str, list[str], list[str]]:
|
|
body_html = generated.get("body_html", "")
|
|
sidebar_html = generated.get("sidebar_html", "")
|
|
footer_html = generated.get("footer_html", "")
|
|
visible_text = strip_tags("\n".join([body_html, sidebar_html, footer_html]))
|
|
|
|
failures: list[str] = []
|
|
actions: list[str] = []
|
|
|
|
slide_overflow = measurement.get("slide", {}).get("overflowed")
|
|
zone_overflows = zone_overflow_names(measurement)
|
|
|
|
if slide_overflow:
|
|
failures.append("Verify-RenderSlide")
|
|
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를 재조정한다.")
|
|
|
|
if not all(key in visible_text for key in CORE_MESSAGE_KEYS):
|
|
failures.append("Verify-CoreMessage")
|
|
actions.append("핵심 메시지 `DX는 상위 개념`, `BIM은 핵심 기술`을 가시 텍스트에 직접 노출한다.")
|
|
|
|
if IMAGE_REFERENCE_KEY not in visible_text:
|
|
failures.append("Verify-ImageRef")
|
|
actions.append("이미지/도해 참조 문구 `DX와 핵심기술간 상호관계`를 숨김 영역이 아닌 가시 블록으로 유지한다.")
|
|
|
|
comparison_visible = COMPARISON_MARKER in sidebar_html and all(key in visible_text for key in COMPARE_KEYS)
|
|
if not comparison_visible:
|
|
failures.append("Verify-ComparisonVisible")
|
|
actions.append("비교 핵심 4축(범위, 프로세스, 성과품, 확장성)을 화면에 바로 보이는 요약 블록으로 강제한다.")
|
|
|
|
if RELATION_MARKER not in body_html:
|
|
failures.append("Verify-DesignStructure")
|
|
actions.append("핵심 관계를 설명하는 시각적 관계도 블록을 본문 중심 구조로 유지한다.")
|
|
|
|
if failures:
|
|
return "revise", sorted(set(failures)), list(dict.fromkeys(actions))
|
|
return "pass", [], []
|
|
|
|
|
|
def build_validation_markdown(run_id: str, status: str, failures: list[str], actions: list[str], measurement: dict) -> str:
|
|
slide_overflow = measurement.get("slide", {}).get("overflowed", True)
|
|
zones = measurement.get("zones", {})
|
|
zone_lines = []
|
|
for name in ("body", "sidebar", "footer"):
|
|
zone = zones.get(name, {})
|
|
zone_lines.append(
|
|
f"- {name}: overflowed={zone.get('overflowed')} excess_px={zone.get('excess_px')} block_count={zone.get('block_count')}"
|
|
)
|
|
render_ok = (not slide_overflow) and (not zone_overflow_names(measurement))
|
|
status_line = "통과" if status == "pass" else "재작업 필요"
|
|
failure_lines = "\n".join(f"- {f}" for f in failures) if failures else "- 없음"
|
|
action_lines = "\n".join(f"{i + 1}. {a}" for i, a in enumerate(actions)) if actions else "1. 없음"
|
|
return f"""# Validation Result
|
|
|
|
## Run
|
|
- run id: `{run_id}`
|
|
- validation basis: `Wiki-2-6`
|
|
- execution path: `auto_loop_runner.py`
|
|
|
|
## Validation Summary
|
|
- 실행 경로 검증: 통과
|
|
- 렌더링/측정 검증: {'통과' if render_ok else '실패'}
|
|
- 최종 품질 판정: {status_line}
|
|
|
|
## Render Gates
|
|
- slide overflow: {slide_overflow}
|
|
{chr(10).join(zone_lines)}
|
|
|
|
## Measurement
|
|
```json
|
|
{json.dumps(measurement, ensure_ascii=False, indent=2)}
|
|
```
|
|
|
|
## Final Decision
|
|
- 판정: `{status}`
|
|
|
|
## Failure Classification
|
|
{failure_lines}
|
|
|
|
## Next Action
|
|
{action_lines}
|
|
"""
|
|
|
|
|
|
def write_step_comment(path: Path, body: str) -> None:
|
|
path.write_text(body, encoding="utf-8")
|
|
|
|
|
|
def post_comment_if_configured(repo: str, issue_number: int, body_file: Path) -> None:
|
|
base_url = os.getenv("GITEA_URL", "").strip()
|
|
token = os.getenv("GITEA_TOKEN", "").strip()
|
|
if not base_url or not token:
|
|
return
|
|
body = body_file.read_text(encoding="utf-8")
|
|
create_comment(base_url, token, repo, issue_number, body)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Run and auto-loop a slide generation workflow.")
|
|
parser.add_argument("--run-id", default="run-001")
|
|
parser.add_argument("--repo-root", default=str(ROOT))
|
|
parser.add_argument("--repo-slug", default="Kyeongmin/C.E.L._slide_test")
|
|
parser.add_argument("--issue-numbers", default="2,3,4,5,6,7")
|
|
parser.add_argument("--max-iterations", type=int, default=3)
|
|
args = parser.parse_args()
|
|
|
|
repo_root = Path(args.repo_root)
|
|
run_dir = repo_root / "docs" / args.run_id
|
|
input_file = next((run_dir / "01-input").glob("*.mdx"))
|
|
stage1a = run_dir / "04-plan" / "stage-1a-topics.json"
|
|
stage1b = run_dir / "04-plan" / "stage-1b-refined-concepts.json"
|
|
output_dir = run_dir / "05-execution"
|
|
comments_dir = run_dir / "comments"
|
|
comments_dir.mkdir(parents=True, exist_ok=True)
|
|
validation_path = run_dir / "06-validation" / "validation-result.md"
|
|
|
|
issue_numbers = [int(x.strip()) for x in args.issue_numbers.split(",")]
|
|
step_comment_bodies = {
|
|
1: comments_dir / "step-1.md",
|
|
2: comments_dir / "step-2.md",
|
|
3: comments_dir / "step-3.md",
|
|
4: comments_dir / "step-4.md",
|
|
5: comments_dir / "step-5.md",
|
|
6: comments_dir / "step-6.md",
|
|
}
|
|
|
|
for iteration in range(1, args.max_iterations + 1):
|
|
cmd = [
|
|
sys.executable,
|
|
str(repo_root / "scripts" / "run_from_artifacts.py"),
|
|
"--input", str(input_file),
|
|
"--stage1a", str(stage1a),
|
|
"--stage1b", str(stage1b),
|
|
"--output-dir", str(output_dir),
|
|
]
|
|
completed = subprocess.run(cmd, cwd=str(DESIGN_AGENT_ROOT), capture_output=True, text=True)
|
|
|
|
generated_path = output_dir / "generated_html.json"
|
|
context_path = output_dir / "context.json"
|
|
final_html_path = output_dir / "final.html"
|
|
measurement_path = output_dir / "measurement.json"
|
|
|
|
if completed.returncode != 0:
|
|
actions = ["실패한 stage와 stderr를 확인하고 해당 stage부터 재실행한다."]
|
|
validation_path.write_text(
|
|
build_validation_markdown(args.run_id, "revise", ["Exec-Exit"], actions, {"slide": {"overflowed": True}, "zones": {}}),
|
|
encoding="utf-8",
|
|
)
|
|
step5_body = f"""실행 요약
|
|
- auto_loop_runner.py iteration {iteration} 실행이 비정상 종료되었다.
|
|
- 입력: `docs/{args.run_id}/01-input/{input_file.name}`
|
|
- 종료 코드: {completed.returncode}
|
|
|
|
실행 stderr
|
|
```text
|
|
{completed.stderr.strip()}
|
|
```
|
|
|
|
산출물 경로
|
|
- `docs/{args.run_id}/05-execution/`
|
|
|
|
KPI / 판정 결과
|
|
- 판정: fail
|
|
- iteration: {iteration}
|
|
- 종료 코드: {completed.returncode}
|
|
|
|
실패 분류
|
|
- Exec-Exit
|
|
|
|
수정 액션
|
|
- 실패한 stage와 stderr를 확인하고 해당 stage부터 재실행
|
|
|
|
다음 단계 전달물
|
|
- 실행 stderr
|
|
- 실패 시점 정보
|
|
"""
|
|
step6_body = f"""실행 요약
|
|
- iteration {iteration}에서 실행 자체가 실패하여 품질 검증을 완료할 수 없었다.
|
|
|
|
산출물 경로
|
|
- `docs/{args.run_id}/06-validation/validation-result.md`
|
|
|
|
KPI / 판정 결과
|
|
- 판정: fail
|
|
- 실패 분류: Exec-Exit
|
|
|
|
수정 액션
|
|
- 실패한 stage와 stderr를 확인하고 해당 stage부터 재실행
|
|
|
|
다음 단계 전달물
|
|
- 실행 stderr
|
|
- 실패 시점 정보
|
|
- 다음 iteration 여부: 재실행
|
|
"""
|
|
write_step_comment(step_comment_bodies[5], step5_body)
|
|
write_step_comment(step_comment_bodies[6], step6_body)
|
|
post_comment_if_configured(args.repo_slug, issue_numbers[4], step_comment_bodies[5])
|
|
post_comment_if_configured(args.repo_slug, issue_numbers[5], step_comment_bodies[6])
|
|
continue
|
|
|
|
generated = read_json(generated_path)
|
|
context = read_json(context_path)
|
|
changed = inject_visible_comparison_summary(generated)
|
|
redesign_applied = False
|
|
if changed:
|
|
write_json(generated_path, generated)
|
|
final_html = rerender_final_html(generated, context)
|
|
final_html_path.write_text(final_html, encoding="utf-8")
|
|
measurement = measure_rendered_heights(final_html)
|
|
write_json(measurement_path, measurement)
|
|
else:
|
|
measurement = read_json(measurement_path)
|
|
|
|
status, failures, actions = validate_outputs(generated, measurement)
|
|
if status != "pass" and any(f in failures for f in ["Verify-RenderZone", "Verify-CoreMessage", "Verify-ComparisonVisible", "Verify-ImageRef", "Verify-DesignStructure"]):
|
|
generated = build_slide_regenerated(context)
|
|
redesign_applied = True
|
|
write_json(generated_path, generated)
|
|
final_html = rerender_final_html(generated, context)
|
|
final_html_path.write_text(final_html, encoding="utf-8")
|
|
measurement = measure_rendered_heights(final_html)
|
|
write_json(measurement_path, measurement)
|
|
status, failures, actions = validate_outputs(generated, measurement)
|
|
|
|
validation_path.write_text(build_validation_markdown(args.run_id, status, failures, actions, measurement), encoding="utf-8")
|
|
|
|
zone_names = zone_overflow_names(measurement)
|
|
critical_outputs_ok = all(path.exists() and path.stat().st_size > 0 for path in [final_html_path, generated_path, measurement_path, context_path])
|
|
step5_status = "pass" if completed.returncode == 0 and critical_outputs_ok else "fail"
|
|
step5_failures = "없음" if step5_status == "pass" else "Exec-Artifact"
|
|
step5_actions = "- 없음" if step5_status == "pass" else "- 필수 산출물 4종과 저장 경로를 재확인하고 재생성"
|
|
step5_body = f"""실행 요약
|
|
- auto_loop_runner.py iteration {iteration}로 실행했다.
|
|
- 입력: `docs/{args.run_id}/01-input/{input_file.name}`
|
|
- 산출물: `final.html`, `generated_html.json`, `measurement.json`, `context.json`
|
|
- 비교 요약 가시 블록 보강: {'적용' if changed else '기존 유지'}
|
|
- 슬라이드형 재생성 적용: {'예' if redesign_applied else '아니오'}
|
|
|
|
산출물 경로
|
|
- `docs/{args.run_id}/05-execution/final.html`
|
|
- `docs/{args.run_id}/05-execution/generated_html.json`
|
|
- `docs/{args.run_id}/05-execution/measurement.json`
|
|
- `docs/{args.run_id}/05-execution/context.json`
|
|
|
|
KPI / 판정 결과
|
|
- 판정: {step5_status}
|
|
- iteration: {iteration}
|
|
- 종료 코드: {completed.returncode}
|
|
- 필수 산출물 4종 유효 여부: {critical_outputs_ok}
|
|
|
|
실패 분류
|
|
- {step5_failures}
|
|
|
|
수정 액션
|
|
{step5_actions}
|
|
|
|
다음 단계 전달물
|
|
- 최신 실행 산출물
|
|
- 최신 measurement
|
|
- 최신 context
|
|
"""
|
|
step6_body = f"""실행 요약
|
|
- iteration {iteration} 기준으로 최종 산출물과 측정 결과를 다시 검증했다.
|
|
- slide overflow: {measurement.get('slide', {}).get('overflowed')}
|
|
- zone overflow: {', '.join(zone_names) if zone_names else '없음'}
|
|
- 슬라이드형 재생성 적용: {'예' if redesign_applied else '아니오'}
|
|
- 최종 판정은 `{status}`이다.
|
|
|
|
산출물 경로
|
|
- `docs/{args.run_id}/06-validation/validation-result.md`
|
|
- `docs/{args.run_id}/05-execution/final.html`
|
|
- `docs/{args.run_id}/05-execution/measurement.json`
|
|
|
|
KPI / 판정 결과
|
|
- 판정: {status}
|
|
- 실패 분류: {', '.join(failures) if failures else '없음'}
|
|
|
|
수정 액션
|
|
"""
|
|
if actions:
|
|
step6_body += "\n".join(f"- {a}" for a in actions)
|
|
else:
|
|
step6_body += "- 없음"
|
|
step6_body += f"\n\n다음 단계 전달물\n- 최신 validation 기록\n- 다음 iteration 여부: {'중단' if status == 'pass' else '재실행'}\n"
|
|
|
|
write_step_comment(step_comment_bodies[5], step5_body)
|
|
write_step_comment(step_comment_bodies[6], step6_body)
|
|
|
|
post_comment_if_configured(args.repo_slug, issue_numbers[4], step_comment_bodies[5])
|
|
post_comment_if_configured(args.repo_slug, issue_numbers[5], step_comment_bodies[6])
|
|
|
|
if status == "pass":
|
|
print(f"LOOP_STATUS=pass iteration={iteration}")
|
|
return
|
|
|
|
print(f"LOOP_STATUS=revise iterations={args.max_iterations}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|