"""Phase 22 — 정제 anchor keywords only (baseline) 공식: 점수 = 1.0 × 키워드 (IDF-weighted Jaccard) 데이터: - keyword_base.yaml (Milestone 1.6 결과, 268 canonical + variants) - analysis.md mirror (templates_v1.anchor_sets 기반, Milestone 2 결과) - MDX 추출: keyword_base variants → canonical 정규화 → direct canonical hit + Kiwi token hit union 목적: 정제 anchor keyword 만으로 4 TARGET 매칭이 어디까지 되는지 baseline. """ 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, ) HERE = Path(__file__).parent PNG_REL = "../../data/figma_previews/" 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() reports = [] hits = 0 for uid, display, correct_sid, *_ in TARGET_UNITS: text = units_full[uid] mdx_kws = extract_mdx_keywords(text, vocab, keyword_base=kb) ranked = [] for fid, v in frames.items(): fig_kws = set(v["keywords"]) k_s, inter = keyword_score(mdx_kws, fig_kws, idf, tier) ranked.append((fid, k_s, {"kw": k_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 top1_score = ranked[0][1] top2_score = ranked[1][1] if len(ranked) > 1 else 0 margin = top1_score - top2_score reports.append({ "uid": uid, "display": display, "correct_sid": correct_sid, "mdx_kws": sorted(mdx_kws), "top3": ranked[:3], "margin": margin, }) return { "phase": 22, "desc": "키워드만 (baseline)", "formula": "1.0 × 키워드", "hits": hits, "reports": reports, "frame_to_short": frame_to_short, "idx_data": idx_data, "frames": frames, } def write_md(r): lines = [] lines.append(f"# Phase 22 — 정제 anchor keywords only (baseline)") lines.append("") lines.append(f"**공식**: `점수 = 1.0 × 키워드` (IDF-weighted Jaccard)") lines.append(f"**결과: {r['hits']}/4 정답**") lines.append("") lines.append("**데이터 기반**:") lines.append("- `keyword_base.yaml` (Milestone 1.6 결과, 268 canonical + variants)") lines.append("- 32 Figma frames 의 anchor keywords (analysis.md mirror — templates_v1.anchor_sets 기반)") lines.append("") lines.append("**MDX 키워드 추출**:") lines.append("1. keyword_base variants → canonical 정규화") lines.append("2. direct canonical substring hit (compound 보호: 설계Data, 공사비절감 등)") lines.append("3. Kiwi 형태소 분석 → Figma vocab 교집합") lines.append("4. direct hit ∪ Kiwi hit") lines.append("") # 요약 표 lines.append("## 1. TARGET별 결과") lines.append("") lines.append("| MDX | 정답 | 1위 | 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 "✗" lines.append( f"| {rep['display']} | {rep['correct_sid']} | {top1} ({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 키워드 ({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"- **점수**: **{score:.3f}**") lines.append("") # 한계 lines.append("## 4. 한계") lines.append("") lines.append("- **정제 anchor 만으론 recall 한계**: Milestone 1.6 에서 구조어·weak·AI 요약어 49개 제외 후 anchor pool 이 좁아짐.") lines.append(" - 원래 analysis.md 후보 키워드 (수동 큐레이션, messy) 대비 recall 낮을 수 있음") lines.append(" - 대신 precision ↑ + evidence-based 방어 가능") lines.append("- **IDF Jaccard 만으론 의미 관계 포착 못함**: 같은 주제를 다른 canonical 로 표현 시 매칭 실패 가능") lines.append("- **구조적 호환성 무시**: 3열 병렬과 2열 비교를 구분 못 함") lines.append("- **다음 단계**: Phase 23 에서 MDX summary embedding 추가") lines.append("") out = HERE / "MATRIX_PHASE22.md" out.write_text("\n".join(lines), encoding="utf-8") print(f"완료: {out}") def main(): result = run() with open(HERE / "_phase22_results.pkl", "wb") as f: pickle.dump(result, f) write_md(result) print(f"Phase 22: {result['hits']}/4 정답") if __name__ == "__main__": main()