Files
C.E.L_Slide_test2/tests/matching/phase24.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

247 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Phase 24 — 정제 anchor + content summary + legacy structure ontology
공식: 점수 = 0.5 × 키워드 + 0.3 × 내용 + 0.2 × 구조(legacy ontology)
- 키워드 축: keyword_base 기반 (Phase 22 과 동일)
- 내용 축: MDX summary ↔ frame.content (Phase 23 와 동일)
- 구조 축: **legacy structure ontology** (structure_ontology.yaml 의 `frames:` 블록)
- detect_mdx_structure_v3 로 MDX 구조 추출
- structural_match_v3 로 family/surface/semantic_role 속성 교집합 매칭
- Phase 21b 방식 유지 (tie-breaker 가중치)
**주의 — Phase 25 (template-fit-v1) 과 차이**:
- Phase 23 구조 = family/semantic_role (legacy frames: 블록)
- Phase 25 구조 = visual_pattern (cardinality + relation_type + slot_coverage) + adaptation_cost + not_suits
- 즉 Phase 23 는 "구조가 얼마나 도움 되는지" retrieval 확인,
Phase 25 은 "이 디자인을 실제로 쓸 수 있는지" application 판단.
"""
import sys
import pickle
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from phase_common import (
TARGET_UNITS, load_keyword_base, load_32_frames, compute_df_idf_tier,
extract_mdx_keywords, keyword_score,
load_target_units, load_frame_index, normalize_with_keyword_base,
)
from structure_v3 import load_ontology, detect_mdx_structure_v3, structural_match_v3
from embeddings import embed_texts, cosine
from detect_mdx import detect_mdx_analysis
from template_fit import load_templates_v1, intent_compat_with_source
HERE = Path(__file__).parent
PNG_REL = "../../data/figma_previews/"
W_KW, W_CONTENT, W_STRUCT = 0.5, 0.3, 0.2
def run():
kb = load_keyword_base()
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()
templates_v1 = load_templates_v1() # structure_intent 조회용
fids = list(frames.keys())
frame_texts = [frames[fid].get("content", "") for fid in fids]
frame_vecs = embed_texts(frame_texts)
reports = []
hits = 0
for uid, display, correct_sid, *_ in TARGET_UNITS:
text_orig = units_full[uid]
text = normalize_with_keyword_base(text_orig, kb)
title = units_title[uid]
mdx_kws = extract_mdx_keywords(text_orig, vocab, keyword_base=kb)
mdx_schema = detect_mdx_structure_v3(text, title)
# content 축: summary 기반 (Phase 23 와 동일)
analysis = detect_mdx_analysis(text_orig, title, anchor_vocab=None)
mdx_summary = analysis['summary']
mdx_vec = embed_texts([mdx_summary])[0]
c_scores = {fids[i]: max(0.0, min(1.0, cosine(mdx_vec, frame_vecs[i])))
for i in range(len(fids))}
mdx_intents = analysis.get('structure_intent', [])
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[fid]
legacy_s, aligns = structural_match_v3(mdx_schema, ontology.get(fid, {}))
# structure_intent 호환도 (templates_v1 에서 frame intent 조회)
tpl_v1 = templates_v1.get(fid, {})
frame_intents = tpl_v1.get('visual_pattern', {}).get('structure_intent', [])
ic, ic_source = intent_compat_with_source(mdx_intents, frame_intents)
# 구조 점수 = legacy 50% + intent 50%
struct_combined = 0.5 * legacy_s + 0.5 * ic
final = W_KW * k_s + W_CONTENT * c_s + W_STRUCT * struct_combined
ranked.append((fid, final, {
"kw": k_s, "content": c_s,
"struct": struct_combined,
"legacy_struct": legacy_s,
"intent_compat": ic,
"intent_source": ic_source,
"frame_intents": frame_intents,
"aligns": aligns, "inter": sorted(inter),
}))
ranked.sort(key=lambda x: -x[1])
top_sid = frame_to_short.get(ranked[0][0], "?")
if top_sid == correct_sid:
hits += 1
margin = ranked[0][1] - ranked[1][1]
reports.append({
"uid": uid, "display": display, "correct_sid": correct_sid,
"mdx_kws": sorted(mdx_kws), "mdx_title": title,
"mdx_summary": mdx_summary,
"mdx_schema": mdx_schema,
"mdx_intents": mdx_intents,
"top3": ranked[:3], "margin": margin,
})
return {
"phase": 24, "desc": "정제 anchor + content summary + (legacy structure + structure_intent)",
"formula": f"{W_KW} × 키워드 + {W_CONTENT} × 내용(summary) + {W_STRUCT} × (0.5 legacy + 0.5 intent)",
"weights": (W_KW, W_CONTENT, W_STRUCT),
"hits": hits, "reports": reports,
"frame_to_short": frame_to_short, "idx_data": idx_data,
"frames": frames, "ontology": ontology,
}
def write_md(r):
lines = []
lines.append("# Phase 24 — 정제 anchor + content summary + legacy structure ontology")
lines.append("")
lines.append(f"**공식**: `{r['formula']}`")
lines.append(f"**결과: {r['hits']}/4 정답**")
lines.append("")
lines.append("**Phase 23 대비**:")
lines.append("- **legacy structure ontology** 축 추가 (family/semantic_role/columns 교집합)")
lines.append("- MDX 측: `detect_mdx_structure_v3()` 로 `### 서브섹션`, 표 헤더, 볼드 블릿 기반 schema emit")
lines.append("- Figma 측: `structure_ontology.yaml` 의 legacy `frames:` 블록 (32 프레임 family/semantic_role)")
lines.append("- confidence low 인 MDX 구조는 점수 0")
lines.append("")
lines.append("**Phase 25 (template-fit-v1) 과 차이**:")
lines.append("- Phase 23 구조 = family/semantic_role **legacy ontology** (retrieval 용)")
lines.append("- Phase 25 구조 = **visual_pattern** (cardinality + relation + slot_coverage) + adaptation_cost + not_suits (application 용)")
lines.append("- Phase 23 는 '구조가 매칭에 기여하는가' 확인,")
lines.append(" Phase 25 은 '이 디자인을 실제로 쓸 수 있는가' 판단")
lines.append("")
# 요약
lines.append("## 1. TARGET별 결과")
lines.append("")
lines.append("| MDX | 정답 | 1위 (kw / content / struct / 최종) | 2위 | margin |")
lines.append("|-----|------|-----------------------------------|-----|--------|")
for rep in r["reports"]:
top1 = r["frame_to_short"].get(rep["top3"][0][0], "?")
top2 = r["frame_to_short"].get(rep["top3"][1][0], "?")
mark = "✓" if top1 == rep["correct_sid"] else "✗"
bd1 = rep["top3"][0][2]
lines.append(
f"| {rep['display']} | {rep['correct_sid']} | "
f"{top1} ({bd1['kw']:.2f}/{bd1['content']:.2f}/{bd1['struct']:.2f}/{rep['top3'][0][1]:.3f}) {mark} | "
f"{top2} ({rep['top3'][1][1]:.3f}) | {rep['margin']:.3f} |"
)
lines.append("")
# Top-3
lines.append("## 2. Top-3 매트릭스")
lines.append("")
lines.append("| 콘텐츠 | 1순위 | 2순위 | 3순위 |")
lines.append("|--------|-------|-------|-------|")
for rep in r["reports"]:
row = [f"**{rep['display']}**<br>정답 Frame **{rep['correct_sid']}**"]
for rank_idx in range(3):
fid, score, _ = rep["top3"][rank_idx]
sid = r["frame_to_short"].get(fid, "?")
info = r["idx_data"].get(sid, {})
png = info.get("png", "")
title = info.get("title_text", "").strip().replace("\n", " ")[:15] + "…"
inner = f"![{sid}]({PNG_REL}{png})<br>**{sid}** ({score:.3f})<br>{title}"
if sid == rep["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("## 3. 유닛별 세부")
lines.append("")
for rep in r["reports"]:
lines.append(f"### {rep['display']} — 정답 Frame **{rep['correct_sid']}**")
lines.append("")
lines.append(f"**MDX 구조 (v3 emit)**")
lines.append("```yaml")
for k, vv in rep["mdx_schema"].items():
lines.append(f"{k}: {vv}")
lines.append("```")
lines.append("")
lines.append(f"**MDX 키워드 ({len(rep['mdx_kws'])}개)**: {', '.join(rep['mdx_kws'])}")
lines.append("")
for rank, (fid, score, bd) in enumerate(rep["top3"], 1):
sid = r["frame_to_short"].get(fid, "?")
info = r["idx_data"].get(sid, {})
png = info.get("png", "")
fig_content = r["frames"][fid].get("content", "")
fig_schema = r["ontology"].get(fid, {})
common = ", ".join(bd["inter"]) if bd["inter"] else "(없음)"
align_str = ", ".join(bd["aligns"]) if bd["aligns"] else "-"
is_correct = sid == rep["correct_sid"]
prefix = "🎯 " if is_correct else ""
lines.append(f"**{prefix}{rank}위 Frame {sid}** {'(정답)' if is_correct else ''}")
lines.append("")
lines.append(f"![{sid}]({PNG_REL}{png})")
lines.append("")
lines.append(f"- **매칭 내용**: {fig_content}")
lines.append(f"- **매칭 키워드**: {common}")
lines.append(f"- **Figma schema**: family=`{fig_schema.get('family')}`, "
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("")
# 한계
lines.append("## 4. 한계")
lines.append("")
lines.append("- **구조 = 구조만 기록**: family/semantic_role 은 '디자인이 어떻게 생겼나'를 말할 뿐 '이 콘텐츠가 들어갈 수 있나'를 판정하지 못함.")
lines.append("- **slot 개념 부재**: 4열 디자인에 3개 MDX를 넣을 수 있는지 여부가 드러나지 않음.")
lines.append("- **adaptation 비용 없음**: 재구성이 필요한 경우와 원본 그대로 쓰는 경우의 구분 없음.")
lines.append("- **라우팅 없음**: 점수만 제공, use_as_is / light_edit / restructure / reject 같은 운영 판단 불가.")
lines.append("- **다음 단계**: Phase 25 template-fit-v1 — 매칭을 '얼마나 닮았나'가 아니라 '얼마나 끼워 넣을 수 있나'로 재정의.")
lines.append("")
out = HERE / "MATRIX_PHASE24.md"
out.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out}")
def main():
result = run()
serializable = dict(result)
serializable.pop("frames", None)
serializable.pop("ontology", None)
serializable.pop("idx_data", None)
with open(HERE / "_phase24_results.pkl", "wb") as f:
pickle.dump(serializable, f)
write_md(result)
print(f"Phase 24: {result['hits']}/4 정답")
if __name__ == "__main__":
main()