Files
C.E.L_Slide_test2/scripts/check_mdx_section_tree_contract.py
T
KyeongminandClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:03:42 +09:00

151 lines
5.4 KiB
Python

from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from src.phase_z2_mapper import _extract_bold_or_plain, split_source
from src.phase_z2_pipeline import parse_mdx
MDX_ROOT = ROOT / "samples" / "mdx"
CONTRACT: dict[str, dict[str, Any]] = {
"01. 건설산업 DX의 올바른 이해(0127).mdx": {
"top_sections": ["1. 용어 정의", "2. 용어간 상호관계"],
"internal_labels": {
"01-1": ["건설산업", "BIM(Building Information Modeling) : 디지털 전환을 위한 핵심 기술", "DX(Digital Transformation) : 산업 패러다임의 변화"],
"01-2": ["DX는 BIM과 같은 디지털기술을 기반으로 산업 전반의 프로세스를 혁신하는 상위개념", "DX와 BIM의 구분"],
},
},
"02. DX의 시행 목표 및 기대효과.mdx": {
"top_sections": ["1. DX의 궁극적 목표", "2. DX 기반 Process 혁신에 따른 주체별 기대효과"],
"h3": {
"02-2": ["2.1 업무 수행 과정(Process)의 변화", "2.2 DX 시행 주체별 기대효과"],
},
"internal_labels": {
"02-1": ["안전과 품질", "생산성 향상", "소통과 신뢰"],
},
},
"03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx": {
"top_sections": ["1. DX 시행을 위한 필수 요건", "2. Process의 혁신과 Product의 변화"],
"h3": {
"03-2": ["2.1 과정(Process)의 혁신", "2.2 결과(Product)의 변화"],
},
"internal_labels": {
"03-1": ["기술(디지털)", "사람(역량)", "자연(여건)"],
},
},
"04. DX 지연 요인.mdx": {
"top_sections": ["1. DX에 대한 인식", "2. DX 추진의 실태"],
"h3": {
"04-1": ["기술 및 소프트웨어 이해도", "효과와 효율성", "인력 및 교육", "경제적 부담", "실무 및 적용성"],
"04-2": ["2.1 정책 및 발주 체계", "2.2 조직 및 수행 역량"],
},
},
"05. 설계 방식의 왜곡.mdx": {
"top_sections": ["1. 설계의 자동화", "2. S/W 중심 설계 방식"],
"h3": {
"05-2": ["1 기능·구조적 한계", "2 기술 역량의 왜곡"],
},
"internal_labels": {
"05-1": ["설계의 개념", "설계의 특성", "'설계 자동화'라는 용어의 오용과 모순"],
},
},
}
def _h3_titles(raw_content: str) -> list[str]:
return [
m.group(1).strip()
for m in re.finditer(r"(?m)^###\s+(.+?)\s*$", raw_content or "")
]
def _top_bullet_labels(raw_content: str) -> list[str]:
return [
_extract_bold_or_plain(top_line)
for top_line, _nested in split_source("top_bullets", raw_content or "")
]
def _contains_label(labels: list[str], expected: str) -> bool:
return any(expected == label or expected in label for label in labels)
def check_one(filename: str, spec: dict[str, Any]) -> dict[str, Any]:
path = MDX_ROOT / filename
title, sections, footer = parse_mdx(path)
section_titles = [s.title for s in sections]
errors: list[str] = []
expected_top = spec.get("top_sections", [])
if section_titles != expected_top:
errors.append(
f"top_sections mismatch: expected={expected_top!r}, actual={section_titles!r}"
)
section_reports = []
for section in sections:
h3_titles = _h3_titles(section.raw_content)
bullet_labels = _top_bullet_labels(section.raw_content)
expected_h3 = (spec.get("h3") or {}).get(section.section_id)
if expected_h3 is not None and h3_titles != expected_h3:
errors.append(
f"{section.section_id} h3 mismatch: expected={expected_h3!r}, actual={h3_titles!r}"
)
expected_labels = (spec.get("internal_labels") or {}).get(section.section_id, [])
missing_labels = [
label for label in expected_labels if not _contains_label(bullet_labels, label)
]
if missing_labels:
errors.append(
f"{section.section_id} internal labels missing: {missing_labels!r}"
)
section_reports.append({
"section_id": section.section_id,
"title": section.title,
"h3_titles": h3_titles,
"top_bullet_labels_count": len(bullet_labels),
"top_bullet_labels_sample": bullet_labels[:12],
})
return {
"filename": filename,
"title": title,
"has_footer": bool(footer),
"passed": not errors,
"errors": errors,
"sections": section_reports,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--write-report", type=Path)
args = parser.parse_args()
reports = [check_one(filename, spec) for filename, spec in CONTRACT.items()]
result = {
"passed": all(report["passed"] for report in reports),
"reports": reports,
}
text = json.dumps(result, ensure_ascii=False, indent=2)
print(text)
if args.write_report:
args.write_report.parent.mkdir(parents=True, exist_ok=True)
args.write_report.write_text(text + "\n", encoding="utf-8")
return 0 if result["passed"] else 1
if __name__ == "__main__":
raise SystemExit(main())