"""Phase 12 — 키워드 수준 매칭 진단 (왜 이 Figma가 1위/2위가 됐나) 각 MDX 유닛에 대해: 1. MDX 측 추출 키워드 (Kiwi 형태소 + IDF 가중 상위 15개) 2. Ensemble Top-3 후보 각각에 대해: - Figma 측 키워드 (공통 + 고유) - 공통 키워드 × IDF 기여도 - BM25 점수 / Cross 점수 / Ensemble 점수 """ import sys import json import math import collections from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from common import load_figma_texts from extract_units import extract_units from methods import ( _get_kiwi, _extract_content_tokens, method_kiwi_bm25, method_cross_encoder, ) from phase10 import extract_titles_only_mdx, extract_titles_only_figma, TARGET_UNITS from phase11 import method_ensemble ROOT = Path(r"d:\ad-hoc\kei\design_agent") PREVIEW_DIR = ROOT / "data" / "figma_previews" def build_idf(figma_texts, kiwi): """32개 Figma 프레임 전체에서 IDF 계산""" doc_tokens = {} df = collections.Counter() for fid, text in figma_texts.items(): toks = _extract_content_tokens(text, kiwi) doc_tokens[fid] = collections.Counter(toks) for w in set(toks): df[w] += 1 N = len(figma_texts) idf = {w: math.log(N / c) for w, c in df.items() if c > 0} return doc_tokens, idf, N def main(): units_full = extract_units() figma_full = load_figma_texts() figma_titles = extract_titles_only_figma() mdx_titles = {} for uid, _, _, fname, mid, sub in TARGET_UNITS: mdx_titles[uid] = extract_titles_only_mdx(fname, mid, sub) with open(PREVIEW_DIR / "index.json", encoding="utf-8") as f: idx_data = json.load(f) frame_to_short = {info["frame_id"]: sid for sid, info in idx_data.items()} kiwi = _get_kiwi() doc_tokens, idf, N = build_idf(figma_full, kiwi) lines = [] lines.append("# Phase 12 — 키워드 수준 매칭 진단") lines.append("") lines.append(f"**IDF 베이스**: {N}개 Figma 프레임 전체 corpus") lines.append(f"**키워드 추출**: Kiwi 형태소 분석기, 내용어(명사/동사/형용사/영문/숫자)만, 2글자 이상") lines.append("") lines.append("각 유닛에 대해 Ensemble(α=0.7) Top-3 후보의 키워드 수준 매칭을 보여줍니다.") lines.append("") for uid, display, correct_id, _, _, _ in TARGET_UNITS: lines.append("---") lines.append("") lines.append(f"## {display}") lines.append(f"**정답**: Frame **{correct_id}**") lines.append("") # MDX 측 키워드 mdx_text = units_full[uid] mdx_title_text = mdx_titles[uid] mdx_tokens = _extract_content_tokens(mdx_text, kiwi) mdx_tf = collections.Counter(mdx_tokens) mdx_weighted = [(w, c, idf.get(w, 0), c * idf.get(w, 0)) for w, c in mdx_tf.items() if idf.get(w, 0) > 0] mdx_weighted.sort(key=lambda x: -x[3]) lines.append(f"### MDX 측 키워드 (Kiwi + IDF 상위 15개)") lines.append("") lines.append("| 키워드 | TF | IDF | TF×IDF |") lines.append("|-------|----|----|-------|") for w, tf_, widf, score in mdx_weighted[:15]: lines.append(f"| {w} | {tf_} | {widf:.2f} | {score:.2f} |") lines.append("") # Ensemble Top-3 ensemble_ranked = method_ensemble(mdx_text, mdx_title_text, figma_full, figma_titles, alpha=0.7) bm25_dict = dict(method_kiwi_bm25(mdx_text, figma_full)) cross_dict = dict(method_cross_encoder(mdx_title_text, figma_titles)) lines.append(f"### Ensemble Top-3 후보 (α=0.7: 70% BM25본문×전체 + 30% Cross제목×제목)") lines.append("") for rank_idx, (fid, ens_score) in enumerate(ensemble_ranked[:3], 1): short = frame_to_short.get(str(fid), "?") mark = " ⭐ 정답" if short == correct_id else "" info = idx_data.get(short, {}) figma_title_text = info.get("title_text", "").replace("\n", " ").strip() or "(타이틀 없음)" figma_cleaned_title = figma_titles.get(fid, "(타이틀 섹션 비어있음)") lines.append(f"#### {rank_idx}위 Frame **{short}**{mark}") lines.append(f"- Figma 파일 타이틀: `{figma_title_text[:50]}`") lines.append(f"- 추출된 타이틀: `{figma_cleaned_title[:80]}`") lines.append(f"- **BM25** = {bm25_dict.get(fid, 0):.1f} | " f"**Cross** = {cross_dict.get(fid, 0):.3f} | " f"**Ensemble(0.7)** = {ens_score:.3f}") lines.append("") # 공통 키워드 분석 figma_tf = doc_tokens.get(fid, {}) common = [] for w, mdx_c, widf, _ in mdx_weighted: if w in figma_tf: contribution = mdx_c * figma_tf[w] * widf common.append((w, mdx_c, figma_tf[w], widf, contribution)) common.sort(key=lambda x: -x[4]) if common: lines.append("**공통 키워드 (IDF 기여도 순 상위 12개):**") lines.append("") lines.append("| 키워드 | MDX TF | Figma TF | IDF | 기여도 |") lines.append("|-------|--------|----------|-----|--------|") for w, mc, fc, widf, contrib in common[:12]: lines.append(f"| {w} | {mc} | {fc} | {widf:.2f} | {contrib:.2f} |") lines.append("") lines.append(f"**공통 키워드 총 {len(common)}개, 기여도 합계 = " f"{sum(c[4] for c in common):.2f}**") else: lines.append("**공통 키워드 없음.**") lines.append("") # 해석 도움말 lines.append("### 해석") lines.append("") top1 = ensemble_ranked[0] top1_short = frame_to_short.get(str(top1[0]), "?") if top1_short == correct_id: lines.append(f"✓ Ensemble이 정답({correct_id})을 1위로 선택.") else: lines.append(f"✗ Ensemble이 정답({correct_id})을 놓침. 1위 = {top1_short}.") # 정답이 Top-3에 있는지 for i, (fid, _) in enumerate(ensemble_ranked[:3], 1): if frame_to_short.get(str(fid), "?") == correct_id: lines.append(f" (정답은 {i}위에 있음)") break lines.append("") out_path = Path(__file__).parent / "MATRIX_PHASE12.md" out_path.write_text("\n".join(lines), encoding="utf-8") print(f"완료: {out_path}") if __name__ == "__main__": main()