Files
KyeongminandClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- 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>
2026-07-02 17:03:42 +09:00

162 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()