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,246 @@
|
||||
"""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"<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"")
|
||||
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()
|
||||
Reference in New Issue
Block a user