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,177 @@
|
||||
"""Phase 21b — ontology 기반 구조축 (tie-breaker 가중치)
|
||||
|
||||
공식: 점수 = 0.5 × 키워드 + 0.3 × 내용 + 0.1 × 구조(v3 ontology)
|
||||
(구조는 tie-breaker 수준)
|
||||
|
||||
Phase 21(원본, old structure) vs Phase 21b(new ontology) 비교 의도.
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from phase_common import (
|
||||
TARGET_UNITS, load_synonyms, load_32_frames, compute_df_idf_tier,
|
||||
extract_mdx_keywords, keyword_score, content_scores_batch,
|
||||
load_target_units, load_frame_index, normalize_with_synonyms,
|
||||
)
|
||||
from structure_v3 import load_ontology, detect_mdx_structure_v3, structural_match_v3
|
||||
|
||||
ROOT = Path(r"d:\ad-hoc\kei\design_agent")
|
||||
PREVIEW_DIR = ROOT / "data" / "figma_previews"
|
||||
|
||||
|
||||
def main():
|
||||
synonyms = load_synonyms()
|
||||
frames = load_32_frames()
|
||||
df, idf, tier, N = compute_df_idf_tier(frames)
|
||||
vocab = set()
|
||||
for v in frames.values(): vocab.update(v["keywords"])
|
||||
units_full, units_title = load_target_units()
|
||||
idx_data, frame_to_short = load_frame_index()
|
||||
ontology = load_ontology()
|
||||
|
||||
W_KW, W_CONTENT, W_STRUCT = 0.5, 0.3, 0.1
|
||||
|
||||
reports = []
|
||||
hits = 0
|
||||
for uid, display, correct_sid, *_ in TARGET_UNITS:
|
||||
text_orig = units_full[uid]
|
||||
text = normalize_with_synonyms(text_orig, synonyms) # synonym 정규화 공통 적용
|
||||
title = units_title[uid]
|
||||
mdx_kws = extract_mdx_keywords(text, vocab, synonyms=None) # 이미 정규화됨
|
||||
mdx_schema = detect_mdx_structure_v3(text, title) # 정규화된 text 사용
|
||||
c_scores = content_scores_batch(title, frames)
|
||||
|
||||
ranked = []
|
||||
for fid, v in frames.items():
|
||||
fig_kws = set(v["keywords"])
|
||||
k_s, inter = keyword_score(mdx_kws, fig_kws, idf, tier)
|
||||
c_s = c_scores.get(fid, 0.0)
|
||||
s_s, aligns = structural_match_v3(mdx_schema, ontology.get(fid, {}))
|
||||
final = W_KW * k_s + W_CONTENT * c_s + W_STRUCT * s_s
|
||||
ranked.append((fid, final, {
|
||||
"kw": k_s, "content": c_s, "struct": s_s,
|
||||
"aligns": aligns, "inter": sorted(inter),
|
||||
}))
|
||||
ranked.sort(key=lambda x: -x[1])
|
||||
top3 = ranked[:3]
|
||||
top_sid = frame_to_short.get(top3[0][0], "?")
|
||||
if top_sid == correct_sid:
|
||||
hits += 1
|
||||
reports.append({
|
||||
"uid": uid, "display": display, "correct_sid": correct_sid,
|
||||
"mdx_kws": mdx_kws, "mdx_title": title, "mdx_schema": mdx_schema,
|
||||
"top3": top3,
|
||||
})
|
||||
print(f"{uid}(정답={correct_sid}): "
|
||||
f"Top1={top_sid} ({top3[0][1]:.3f}) "
|
||||
f"[{'✓' if top_sid == correct_sid else '✗'}] "
|
||||
f"struct={top3[0][2]['struct']:.2f}")
|
||||
|
||||
print(f"\n=== Phase 21b 결과: {hits}/4 ===")
|
||||
|
||||
# MD 리포트
|
||||
png_rel = "../../data/figma_previews/"
|
||||
lines = []
|
||||
lines.append(f"# Phase 21b — Ontology 기반 구조축 (tie-breaker)")
|
||||
lines.append("")
|
||||
lines.append(f"**공식**: `점수 = {W_KW} × 키워드 + {W_CONTENT} × 내용 + {W_STRUCT} × 구조(ontology)`")
|
||||
lines.append(f"**결과: {hits}/4**")
|
||||
lines.append("")
|
||||
lines.append("**Phase 21 원본 대비 차이:**")
|
||||
lines.append("- 구조 매칭 로직: `structural_match_v2` (layout 문자열) → `structural_match_v3` (속성 교집합)")
|
||||
lines.append("- 구조 가중치: 0.2 → 0.1 (tie-breaker 수준)")
|
||||
lines.append("- 구조 ontology: layout 문자열 → `structure_ontology.yaml` 속성 schema")
|
||||
lines.append("- MDX 구조 감지: ### 서브섹션/표헤더/블릿 기반 schema emit")
|
||||
lines.append("- confidence 체크: low면 구조 점수 0")
|
||||
lines.append("")
|
||||
|
||||
# 통합 매트릭스
|
||||
lines.append("## 테스트 결과")
|
||||
lines.append("")
|
||||
lines.append("| 콘텐츠 | 1순위 | 2순위 | 3순위 |")
|
||||
lines.append("|--------|-------|-------|-------|")
|
||||
for r in reports:
|
||||
row = [f"**{r['display']}**<br>정답 Frame **{r['correct_sid']}**"]
|
||||
for rank_idx in range(3):
|
||||
fid, score, bd = r["top3"][rank_idx]
|
||||
sid = frame_to_short.get(fid, "?")
|
||||
info = idx_data.get(sid, {})
|
||||
png = info.get("png", "")
|
||||
title = info.get("title_text", "").strip().replace("\n", " ")[:15] + "…"
|
||||
inner = f"<br>**{sid}** ({score:.3f})<br>{title}"
|
||||
if sid == r["correct_sid"]:
|
||||
cell = f"<div style='background:#fff3cd;border:3px solid #dc2626;padding:8px;border-radius:6px'>🎯 <b>정답</b><br>{inner}</div>"
|
||||
else:
|
||||
cell = inner
|
||||
row.append(cell)
|
||||
lines.append("| " + " | ".join(row) + " |")
|
||||
lines.append("")
|
||||
|
||||
# 세부 결과
|
||||
lines.append("## 테스트 결과 세부")
|
||||
lines.append("")
|
||||
for r in reports:
|
||||
lines.append(f"### {r['display']} — 정답 Frame **{r['correct_sid']}**")
|
||||
lines.append("")
|
||||
lines.append(f"**MDX 구조 (v3 emit)**")
|
||||
lines.append("```yaml")
|
||||
for k, vv in r["mdx_schema"].items():
|
||||
lines.append(f"{k}: {vv}")
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
lines.append(f"**MDX 키워드 ({len(r['mdx_kws'])}개)**: {', '.join(sorted(r['mdx_kws']))}")
|
||||
lines.append("")
|
||||
for rank, (fid, score, bd) in enumerate(r["top3"], 1):
|
||||
sid = frame_to_short.get(fid, "?")
|
||||
fig_schema = ontology.get(fid, {})
|
||||
info = idx_data.get(sid, {})
|
||||
png = info.get("png", "")
|
||||
fig_content = frames[fid].get("content", "")
|
||||
common = ", ".join(bd["inter"]) if bd["inter"] else "(없음)"
|
||||
align_str = ", ".join(bd["aligns"]) if bd["aligns"] else "-"
|
||||
is_correct = sid == r["correct_sid"]
|
||||
if is_correct:
|
||||
lines.append(f"**<span style='background:#fff3cd;border:2px solid #dc2626;padding:2px 8px;border-radius:3px'>🎯 {rank}위 Frame {sid} (정답)</span>**")
|
||||
else:
|
||||
lines.append(f"**{rank}위 Frame {sid}**")
|
||||
lines.append("")
|
||||
lines.append(f"")
|
||||
lines.append("")
|
||||
lines.append(f"- **매칭 내용**: {fig_content}")
|
||||
lines.append(f"- **매칭 키워드**: {common}")
|
||||
lines.append(f"- **Figma schema**: family=`{fig_schema.get('family')}`, "
|
||||
f"columns={fig_schema.get('columns')}, "
|
||||
f"role=`{fig_schema.get('semantic_role')}`, "
|
||||
f"conf=`{fig_schema.get('confidence')}`")
|
||||
lines.append(f"- **구조 속성 정렬**: {align_str}")
|
||||
lines.append(f"- **축별 점수**: 키워드 {bd['kw']:.3f} / 내용 {bd['content']:.3f} / 구조 {bd['struct']:.2f}")
|
||||
lines.append(f"- **최종**: **{score:.3f}**")
|
||||
lines.append("")
|
||||
|
||||
out = Path(__file__).parent / "MATRIX_PHASE21b.md"
|
||||
out.write_text("\n".join(lines), encoding="utf-8")
|
||||
print(f"완료: {out}")
|
||||
|
||||
# 별도 결과 저장 (phase22용)
|
||||
import pickle
|
||||
serializable = {
|
||||
"phase": "21b",
|
||||
"desc": "synonym + 키워드 + 내용 + 구조(ontology v3)",
|
||||
"weights": (W_KW, W_CONTENT, W_STRUCT),
|
||||
"hits": hits,
|
||||
"reports": [{
|
||||
"uid": r["uid"], "display": r["display"], "correct_sid": r["correct_sid"],
|
||||
"mdx_kws_count": len(r["mdx_kws"]),
|
||||
"top3": [(fid, score, frame_to_short.get(fid, "?"), bd) for fid, score, bd in r["top3"]],
|
||||
} for r in reports]
|
||||
}
|
||||
with open(Path(__file__).parent / "_phase21b_results.pkl", "wb") as f:
|
||||
pickle.dump(serializable, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user