"""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}** ({score:.3f})
{title}"
if sid == rep["correct_sid"]:
cell = f"