"""Phase 23 — 정제 anchor + content summary 공식: 점수 = 0.5 × 키워드 + 0.5 × 내용 - 키워드 축: keyword_base 기반 (Phase 22 과 동일) - 내용 축: MDX summary ↔ frame.content 의 ko-sroberta cosine 유사도 MDX summary = title + 첫 일반 문단 + slot labels (detect_mdx.build_summary 사용) (Phase 25 template-fit-v1 과 같은 summary 방식 — axis 간 consistency 확보) """ import sys import pickle from pathlib import Path import numpy as np 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, ) from embeddings import embed_texts, cosine from detect_mdx import detect_mdx_analysis HERE = Path(__file__).parent PNG_REL = "../../data/figma_previews/" W_KW = 0.5 W_CONTENT = 0.5 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() # Pre-embed all frame contents 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 = units_full[uid] title = units_title[uid] mdx_kws = extract_mdx_keywords(text, vocab, keyword_base=kb) # content 축: detect_mdx.build_summary 기반 (title + 첫 문단 + slot labels) analysis = detect_mdx_analysis(text, 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))} 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] final = W_KW * k_s + W_CONTENT * c_s ranked.append((fid, final, { "kw": k_s, "content": c_s, "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, "top3": ranked[:3], "margin": margin, }) return { "phase": 23, "desc": "정제 anchor + content summary", "formula": f"{W_KW} × 키워드 + {W_CONTENT} × 내용(summary)", "weights": (W_KW, W_CONTENT, 0.0), "hits": hits, "reports": reports, "frame_to_short": frame_to_short, "idx_data": idx_data, "frames": frames, } def write_md(r): lines = [] lines.append("# Phase 23 — 정제 anchor + content summary") lines.append("") lines.append(f"**공식**: `{r['formula']}`") lines.append(f"**결과: {r['hits']}/4 정답**") lines.append("") lines.append("**Phase 22 대비**:") lines.append("- 내용(의미 유사도) 축 추가") lines.append("- 내용: **MDX summary** ↔ frame.content 의 ko-sroberta cosine") lines.append(" - summary = title + 첫 일반 문단 + slot labels (detect_mdx.build_summary 사용)") lines.append(" - Phase 25 template-fit-v1 과 동일한 summary 방식 — axis consistency") lines.append("") # 요약 lines.append("## 1. TARGET별 결과") lines.append("") lines.append("| MDX | 정답 | 1위 (kw / content / 최종) | 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}/{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']}**
정답 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})
**{sid}** ({score:.3f})
{title}" if sid == rep["correct_sid"]: cell = f"
🎯 정답
{inner}
" 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 제목**: {rep['mdx_title']}") 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", "") common = ", ".join(bd["inter"]) if bd["inter"] 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"- **축별 점수**: 키워드 {bd['kw']:.3f} / 내용 {bd['content']:.3f}") lines.append(f"- **최종**: **{score:.3f}** = {W_KW}×{bd['kw']:.3f} + {W_CONTENT}×{bd['content']:.3f}") lines.append("") # 한계 lines.append("## 4. 한계") lines.append("") lines.append("- **의미 유사 ≠ 구조 적합**: ko-sroberta는 '같은 도메인 주제'를 높게 본다. 하지만 3열 병렬과 2열 비교는 구조가 다름에도 cosine이 비슷할 수 있음.") lines.append("- **구조 축 부재**: MDX 2개 병렬 vs Figma 3열 템플릿이 점수로 구분되지 않음.") lines.append("- **재구성 불가 판정 없음**: 적합도가 낮아도 여전히 1위가 될 수 있음.") lines.append("- **다음 단계**: Phase 25에서 legacy 구조 축을 추가.") lines.append("") out = HERE / "MATRIX_PHASE23.md" out.write_text("\n".join(lines), encoding="utf-8") print(f"완료: {out}") def main(): result = run() with open(HERE / "_phase23_results.pkl", "wb") as f: pickle.dump(result, f) write_md(result) print(f"Phase 23: {result['hits']}/4 정답") if __name__ == "__main__": main()