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>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user