- 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>
175 lines
7.1 KiB
Python
175 lines
7.1 KiB
Python
"""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']}**<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 키워드 ({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"")
|
||
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()
|