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,195 @@
|
||||
"""Phase 21b용 구조 ontology 기반 매칭
|
||||
- detect_mdx_structure_v3(): MDX 본문에서 같은 schema 속성 emit
|
||||
- structural_match_v3(): 속성 교집합 기반 score (0~1)
|
||||
- 가중치는 tie-breaker 수준 (0.05~0.10)
|
||||
- confidence high 일 때만 점수 부여
|
||||
"""
|
||||
import re
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
|
||||
|
||||
def load_ontology():
|
||||
p = HERE / "structure_ontology.yaml"
|
||||
with open(p, encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)["frames"]
|
||||
|
||||
|
||||
# ═══ MDX 구조 감지 v3 ═══
|
||||
def detect_mdx_structure_v3(text, mdx_title=""):
|
||||
"""schema emit: {family, surface, columns, items, has_table, has_cards, semantic_role, confidence}"""
|
||||
lines = text.split("\n")
|
||||
result = {
|
||||
"family": None, "surface": None, "semantic_role": None,
|
||||
"columns": None, "items": None,
|
||||
"has_table": False, "has_cards": False, "has_diagram": False,
|
||||
"confidence": "low",
|
||||
}
|
||||
|
||||
# 1. ### X.Y 서브섹션 감지
|
||||
subs = [ln for ln in lines if re.match(r"^###\s+\d+\.\d+", ln)]
|
||||
subsection_texts = [ln for ln in subs]
|
||||
n_subs = len(subs)
|
||||
|
||||
# 2. 표 감지
|
||||
table_header = None
|
||||
for ln in lines:
|
||||
stripped = ln.strip()
|
||||
if re.match(r"^\|.*\|.*\|", stripped) and not re.match(r"^\|[\s\-:]+\|", stripped):
|
||||
table_header = stripped
|
||||
break
|
||||
|
||||
# 3. 최상위 볼드 블릿 수
|
||||
top_bullets = [ln for ln in lines if re.match(r"^[-*]\s+\*\*", ln)]
|
||||
n_bullets = len(top_bullets)
|
||||
|
||||
full_text = (text + " " + mdx_title).lower()
|
||||
|
||||
# ═══ semantic_role 감지 (figma_audit과 동일 — best-match + priority) ═══
|
||||
from figma_audit import _SEMANTIC_HINT_PATTERNS, _ROLE_PRIORITY
|
||||
candidates = []
|
||||
for role_name, req_kws, min_req in _SEMANTIC_HINT_PATTERNS:
|
||||
hits = sum(1 for k in req_kws if k in full_text)
|
||||
if hits >= min_req:
|
||||
ratio = hits / len(req_kws)
|
||||
candidates.append((hits, ratio, role_name))
|
||||
if candidates:
|
||||
# 1) hits 많음 2) ratio 높음 3) priority 높음
|
||||
candidates.sort(key=lambda x: (-x[0], -x[1], -_ROLE_PRIORITY.get(x[2], 0)))
|
||||
result["semantic_role"] = candidates[0][2]
|
||||
else:
|
||||
result["semantic_role"] = None
|
||||
|
||||
# ═══ family / surface / columns 분기 ═══
|
||||
# 우선순위: ### 서브섹션 > 표 > 블릿
|
||||
if n_subs >= 2:
|
||||
# 과정/결과 비교 → compare family
|
||||
sub_lower = " ".join(subsection_texts).lower()
|
||||
if any(kw in sub_lower for kw in ["과정", "결과", "process", "product"]):
|
||||
result["family"] = "compare"
|
||||
result["surface"] = "banner-plus-2col"
|
||||
result["columns"] = 2
|
||||
result["items"] = 2
|
||||
result["confidence"] = "high"
|
||||
elif n_subs == 2:
|
||||
result["family"] = "compare"
|
||||
result["surface"] = "2col-paired"
|
||||
result["columns"] = 2
|
||||
result["items"] = 2
|
||||
result["confidence"] = "high"
|
||||
else:
|
||||
result["family"] = "list"
|
||||
result["surface"] = "cards-3-horizontal"
|
||||
result["columns"] = n_subs
|
||||
result["items"] = n_subs
|
||||
result["confidence"] = "medium"
|
||||
elif table_header:
|
||||
result["has_table"] = True
|
||||
cols = [c.strip().replace("*", "").lower() for c in table_header.strip("|").split("|") if c.strip()]
|
||||
n_cols = len(cols) - 1 # 첫 열 = label
|
||||
col_text = " ".join(cols)
|
||||
if any(kw in col_text for kw in ["발주자", "시공자", "설계자"]):
|
||||
result["family"] = "cards"
|
||||
result["surface"] = "table-persona-3col"
|
||||
result["semantic_role"] = result.get("semantic_role") or "persona-benefits"
|
||||
result["columns"] = 3
|
||||
result["items"] = 3
|
||||
result["has_cards"] = True
|
||||
result["confidence"] = "high"
|
||||
elif any(kw in col_text for kw in ["제조업", "건축", "토목"]) and "토목" in col_text:
|
||||
result["family"] = "table"
|
||||
result["surface"] = "table-3col"
|
||||
result["columns"] = 3
|
||||
result["items"] = 3
|
||||
result["confidence"] = "high"
|
||||
elif ("bim" in col_text) and ("dx" in col_text):
|
||||
result["family"] = "compare"
|
||||
result["surface"] = "table-multi-row"
|
||||
result["columns"] = 2
|
||||
result["confidence"] = "high"
|
||||
else:
|
||||
result["family"] = "table"
|
||||
result["surface"] = f"table-{n_cols}col"
|
||||
result["columns"] = n_cols
|
||||
result["confidence"] = "medium"
|
||||
elif n_bullets >= 1:
|
||||
result["has_cards"] = True
|
||||
if n_bullets == 3:
|
||||
result["family"] = "list"
|
||||
result["surface"] = "bullets-3"
|
||||
result["columns"] = 3
|
||||
result["items"] = 3
|
||||
result["confidence"] = "high"
|
||||
elif n_bullets == 2:
|
||||
result["family"] = "compare"
|
||||
result["surface"] = "bullets-2col"
|
||||
result["columns"] = 2
|
||||
result["items"] = 2
|
||||
result["confidence"] = "medium"
|
||||
elif n_bullets == 4:
|
||||
result["family"] = "cards"
|
||||
result["surface"] = "cards-4-grid"
|
||||
result["columns"] = 4
|
||||
result["items"] = 4
|
||||
result["confidence"] = "medium"
|
||||
else:
|
||||
result["family"] = "list"
|
||||
result["surface"] = "mixed-bullets"
|
||||
result["items"] = n_bullets
|
||||
result["confidence"] = "low"
|
||||
else:
|
||||
result["family"] = None
|
||||
result["surface"] = "single-column"
|
||||
result["confidence"] = "low"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ═══ 구조 매칭 v3 ═══
|
||||
def structural_match_v3(mdx_schema, fig_schema):
|
||||
"""속성 교집합 기반 score (0~1). confidence high + 주요 속성 일치시에만 점수 부여.
|
||||
Tie-breaker 용도라 가중치는 호출 측에서 낮게 적용 (0.05~0.10 권장)."""
|
||||
if not mdx_schema or not fig_schema:
|
||||
return 0.0, []
|
||||
# confidence 체크: low면 신뢰 안 함
|
||||
if mdx_schema.get("confidence") == "low":
|
||||
return 0.0, ["mdx confidence low → 0"]
|
||||
# 속성 5개 비교
|
||||
aligns = []
|
||||
scores = []
|
||||
# family (가장 중요)
|
||||
m_fam = mdx_schema.get("family")
|
||||
f_fam = fig_schema.get("family")
|
||||
if m_fam and f_fam and m_fam == f_fam:
|
||||
scores.append(0.40)
|
||||
aligns.append(f"family:{m_fam}✓")
|
||||
elif m_fam and f_fam:
|
||||
aligns.append(f"family:{m_fam}≠{f_fam}")
|
||||
# columns / items
|
||||
m_cols = mdx_schema.get("columns") or mdx_schema.get("items")
|
||||
f_cols = fig_schema.get("columns") or fig_schema.get("items")
|
||||
if m_cols and f_cols and m_cols == f_cols:
|
||||
scores.append(0.25)
|
||||
aligns.append(f"columns:{m_cols}✓")
|
||||
elif m_cols and f_cols:
|
||||
aligns.append(f"columns:{m_cols}≠{f_cols}")
|
||||
# has_table
|
||||
if mdx_schema.get("has_table") is not None and fig_schema.get("has_table") is not None:
|
||||
if mdx_schema["has_table"] == fig_schema["has_table"]:
|
||||
scores.append(0.10)
|
||||
aligns.append(f"has_table:{mdx_schema['has_table']}✓")
|
||||
# has_cards
|
||||
if mdx_schema.get("has_cards") is not None and fig_schema.get("has_cards") is not None:
|
||||
if mdx_schema["has_cards"] == fig_schema["has_cards"]:
|
||||
scores.append(0.10)
|
||||
aligns.append(f"has_cards:{mdx_schema['has_cards']}✓")
|
||||
# semantic_role (bonus)
|
||||
m_role = mdx_schema.get("semantic_role")
|
||||
f_role = fig_schema.get("semantic_role")
|
||||
if m_role and f_role and m_role == f_role and m_role != "(검수 필요)":
|
||||
scores.append(0.15)
|
||||
aligns.append(f"role:{m_role}✓")
|
||||
total = sum(scores)
|
||||
return total, aligns
|
||||
Reference in New Issue
Block a user