Add stage-based retry loop regeneration
This commit is contained in:
@@ -7,6 +7,7 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
@@ -40,126 +41,6 @@ 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] = []
|
||||
@@ -211,7 +92,7 @@ def validate_outputs(generated: dict, measurement: dict) -> tuple[str, list[str]
|
||||
return "pass", [], []
|
||||
|
||||
|
||||
def build_validation_markdown(run_id: str, status: str, failures: list[str], actions: list[str], measurement: dict) -> str:
|
||||
def build_validation_markdown(run_id: str, status: str, failures: list[str], actions: list[str], measurement: dict, retry_plan: dict | None = None) -> str:
|
||||
slide_overflow = measurement.get("slide", {}).get("overflowed", True)
|
||||
zones = measurement.get("zones", {})
|
||||
zone_lines = []
|
||||
@@ -224,6 +105,14 @@ def build_validation_markdown(run_id: str, status: str, failures: list[str], act
|
||||
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. 없음"
|
||||
retry_section = ""
|
||||
if retry_plan:
|
||||
retry_section = f"""
|
||||
## Retry Plan
|
||||
```json
|
||||
{json.dumps(retry_plan, ensure_ascii=False, indent=2)}
|
||||
```
|
||||
"""
|
||||
return f"""# Validation Result
|
||||
|
||||
## Run
|
||||
@@ -244,7 +133,7 @@ def build_validation_markdown(run_id: str, status: str, failures: list[str], act
|
||||
```json
|
||||
{json.dumps(measurement, ensure_ascii=False, indent=2)}
|
||||
```
|
||||
|
||||
{retry_section}
|
||||
## Final Decision
|
||||
- 판정: `{status}`
|
||||
|
||||
@@ -269,6 +158,121 @@ def post_comment_if_configured(repo: str, issue_number: int, body_file: Path) ->
|
||||
create_comment(base_url, token, repo, issue_number, body)
|
||||
|
||||
|
||||
def compact_text(text: str, max_len: int) -> str:
|
||||
normalized = re.sub(r"\s+", " ", text).strip()
|
||||
if len(normalized) <= max_len:
|
||||
return normalized
|
||||
cut = normalized[:max_len].rsplit(" ", 1)[0].strip()
|
||||
return (cut or normalized[:max_len]).rstrip(" ,.;:") + "..."
|
||||
|
||||
|
||||
def ensure_phrase(base: str, phrase: str) -> str:
|
||||
if phrase in base:
|
||||
return base
|
||||
return f"{base} {phrase}".strip()
|
||||
|
||||
|
||||
def load_stage_artifacts(output_dir: Path) -> dict[str, Any]:
|
||||
artifacts: dict[str, Any] = {}
|
||||
for name in [
|
||||
"stage_1b_context.json",
|
||||
"stage_1_5b_context.json",
|
||||
"stage_2_context.json",
|
||||
"stage_2_verification.json",
|
||||
]:
|
||||
path = output_dir / name
|
||||
if path.exists():
|
||||
artifacts[name] = read_json(path)
|
||||
return artifacts
|
||||
|
||||
|
||||
def derive_retry_plan(failures: list[str], artifacts: dict[str, Any]) -> dict[str, Any]:
|
||||
stage_1_5b = artifacts.get("stage_1_5b_context.json", {})
|
||||
stage_2v = artifacts.get("stage_2_verification.json", {})
|
||||
|
||||
rollback_stage = "stage_2"
|
||||
reasons: list[str] = []
|
||||
mutations: list[dict[str, Any]] = []
|
||||
|
||||
if any(f in failures for f in ["Verify-CoreMessage", "Verify-ImageRef", "Verify-ComparisonVisible", "Verify-DesignStructure"]):
|
||||
rollback_stage = "stage_1b"
|
||||
reasons.append("가시 메시지/관계도/비교 요약이 부족하여 topic 표현 지시를 다시 강화해야 함")
|
||||
mutations.extend([
|
||||
{"topic_id": 2, "change": "summary", "strategy": "core_message_strengthen"},
|
||||
{"topic_id": 3, "change": "expression_hint", "strategy": "force_relation_diagram_visible"},
|
||||
{"topic_id": 5, "change": "expression_hint", "strategy": "force_visible_comparison_summary"},
|
||||
{"topic_id": 6, "change": "summary", "strategy": "strong_footer_conclusion"},
|
||||
])
|
||||
|
||||
if any(f in failures for f in ["Verify-RenderZone", "Verify-RenderSlide"]):
|
||||
if rollback_stage != "stage_1b":
|
||||
rollback_stage = "stage_1_5b"
|
||||
reasons.append("overflow가 발생하여 budget/문장 길이/보조 정보 밀도를 재조정해야 함")
|
||||
for role, container in stage_1_5b.get("containers", {}).items():
|
||||
db = container.get("design_budget", {})
|
||||
if db and not db.get("fits", True):
|
||||
mutations.append({"role": role, "change": "budget", "strategy": "compress_visible_copy"})
|
||||
for area, result in stage_2v.items():
|
||||
if not result.get("passed"):
|
||||
mutations.append({"area": area, "change": "verification", "strategy": "reduce_density_and_split_visibility"})
|
||||
|
||||
return {
|
||||
"rollback_stage": rollback_stage,
|
||||
"failures": failures,
|
||||
"reasons": reasons,
|
||||
"mutations": mutations,
|
||||
}
|
||||
|
||||
|
||||
def apply_retry_plan_to_stage1b(stage1b_path: Path, retry_plan: dict[str, Any], iteration: int) -> dict[str, Any]:
|
||||
data = read_json(stage1b_path)
|
||||
backup_dir = stage1b_path.parent / "history"
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
backup_path = backup_dir / f"stage-1b-refined-concepts.iteration-{iteration}.json"
|
||||
write_json(backup_path, data)
|
||||
|
||||
concept_map = {item["topic_id"]: item for item in data.get("concepts", [])}
|
||||
|
||||
for mutation in retry_plan.get("mutations", []):
|
||||
topic_id = mutation.get("topic_id")
|
||||
strategy = mutation.get("strategy")
|
||||
if topic_id not in concept_map:
|
||||
continue
|
||||
concept = concept_map[topic_id]
|
||||
summary = concept.get("summary", "")
|
||||
hint = concept.get("expression_hint", "")
|
||||
|
||||
if strategy == "core_message_strengthen":
|
||||
concept["summary"] = compact_text(
|
||||
ensure_phrase(summary, "DX는 상위 개념이고 BIM은 핵심 기술이다."),
|
||||
80,
|
||||
)
|
||||
concept["expression_hint"] = ensure_phrase(hint, "본문 첫 블록에서 DX는 상위 개념, BIM은 핵심 기술이라는 문구를 그대로 가시 텍스트로 노출한다.")
|
||||
elif strategy == "force_relation_diagram_visible":
|
||||
concept["expression_hint"] = ensure_phrase(hint, "관계도는 팝업이나 숨김영역이 아니라 본문 중앙의 가시 다이어그램으로 렌더링한다.")
|
||||
concept["summary"] = compact_text(ensure_phrase(summary, "DX와 GIS, BIM, Digital Twin의 관계를 시각적으로 드러낸다."), 90)
|
||||
elif strategy == "force_visible_comparison_summary":
|
||||
concept["expression_hint"] = ensure_phrase(hint, "범위, 프로세스, 성과품, 확장성 4개 비교축을 sidebar의 가시 요약 카드로 직접 노출한다.")
|
||||
concept["summary"] = compact_text(
|
||||
"범위·프로세스·성과품·확장성의 4개 비교축으로 DX와 BIM 차이를 짧고 직접적으로 보여준다.",
|
||||
90,
|
||||
)
|
||||
elif strategy == "strong_footer_conclusion":
|
||||
concept["summary"] = "결론: BIM은 건설산업 DX를 수행하는 과정의 가장 기초가 되는 일부분이다."
|
||||
concept["expression_hint"] = ensure_phrase(hint, "footer 또는 결론 배너에서 문장을 축약하지 말고 그대로 강하게 노출한다.")
|
||||
elif strategy == "compress_visible_copy":
|
||||
concept["summary"] = compact_text(summary, 60)
|
||||
concept["expression_hint"] = ensure_phrase(hint, "문장 수를 줄이고 핵심 명사구 위주로 압축하되, 핵심 메시지는 유지한다.")
|
||||
elif strategy == "reduce_density_and_split_visibility":
|
||||
concept["summary"] = compact_text(summary, 70)
|
||||
concept["expression_hint"] = ensure_phrase(hint, "표현 밀도를 낮추고, 장문 설명 대신 짧은 bullet/card 구조로 나눈다.")
|
||||
|
||||
write_json(stage1b_path, data)
|
||||
retry_plan_path = stage1b_path.parent / "retry-plan.json"
|
||||
write_json(retry_plan_path, retry_plan)
|
||||
return data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Run and auto-loop a slide generation workflow.")
|
||||
parser.add_argument("--run-id", default="run-001")
|
||||
@@ -373,30 +377,16 @@ KPI / 판정 결과
|
||||
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)
|
||||
|
||||
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)
|
||||
retry_plan = None
|
||||
|
||||
validation_path.write_text(build_validation_markdown(args.run_id, status, failures, actions, measurement), encoding="utf-8")
|
||||
if status != "pass" and iteration < args.max_iterations:
|
||||
artifacts = load_stage_artifacts(output_dir)
|
||||
retry_plan = derive_retry_plan(failures, artifacts)
|
||||
apply_retry_plan_to_stage1b(stage1b, retry_plan, iteration)
|
||||
|
||||
validation_path.write_text(build_validation_markdown(args.run_id, status, failures, actions, measurement, retry_plan), 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])
|
||||
@@ -407,8 +397,7 @@ KPI / 판정 결과
|
||||
- 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 '아니오'}
|
||||
- stage snapshot: `stage_0_context.json` ~ `final_context.json`
|
||||
|
||||
산출물 경로
|
||||
- `docs/{args.run_id}/05-execution/final.html`
|
||||
@@ -432,18 +421,19 @@ KPI / 판정 결과
|
||||
- 최신 실행 산출물
|
||||
- 최신 measurement
|
||||
- 최신 context
|
||||
- stage snapshot 묶음
|
||||
"""
|
||||
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`
|
||||
- `docs/{args.run_id}/05-execution/stage_2_verification.json`
|
||||
|
||||
KPI / 판정 결과
|
||||
- 판정: {status}
|
||||
@@ -455,6 +445,8 @@ KPI / 판정 결과
|
||||
step6_body += "\n".join(f"- {a}" for a in actions)
|
||||
else:
|
||||
step6_body += "- 없음"
|
||||
if retry_plan:
|
||||
step6_body += f"\n\nRetry Plan\n- rollback stage: {retry_plan.get('rollback_stage')}\n- reason: {'; '.join(retry_plan.get('reasons', [])) or '없음'}\n- mutation count: {len(retry_plan.get('mutations', []))}"
|
||||
step6_body += f"\n\n다음 단계 전달물\n- 최신 validation 기록\n- 다음 iteration 여부: {'중단' if status == 'pass' else '재실행'}\n"
|
||||
|
||||
write_step_comment(step_comment_bodies[5], step5_body)
|
||||
|
||||
Reference in New Issue
Block a user