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

194 lines
7.8 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 23 — 정제 anchor + content summary
공식: 점수 = 0.5 × 키워드 + 0.5 × 내용
- 키워드 축: keyword_base 기반 (Phase 22 과 동일)
- 내용 축: MDX summary ↔ frame.content 의 ko-sroberta cosine 유사도
MDX summary = title + 첫 일반 문단 + slot labels (detect_mdx.build_summary 사용)
(Phase 25 template-fit-v1 과 같은 summary 방식 — axis 간 consistency 확보)
"""
import sys
import pickle
from pathlib import Path
import numpy as np
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,
)
from embeddings import embed_texts, cosine
from detect_mdx import detect_mdx_analysis
HERE = Path(__file__).parent
PNG_REL = "../../data/figma_previews/"
W_KW = 0.5
W_CONTENT = 0.5
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()
# Pre-embed all frame contents
fids = list(frames.keys())
frame_texts = [frames[fid].get("content", "") for fid in fids]
frame_vecs = embed_texts(frame_texts)
reports = []
hits = 0
for uid, display, correct_sid, *_ in TARGET_UNITS:
text = units_full[uid]
title = units_title[uid]
mdx_kws = extract_mdx_keywords(text, vocab, keyword_base=kb)
# content 축: detect_mdx.build_summary 기반 (title + 첫 문단 + slot labels)
analysis = detect_mdx_analysis(text, title, anchor_vocab=None)
mdx_summary = analysis['summary']
mdx_vec = embed_texts([mdx_summary])[0]
c_scores = {fids[i]: max(0.0, min(1.0, cosine(mdx_vec, frame_vecs[i])))
for i in range(len(fids))}
ranked = []
for fid, v in frames.items():
fig_kws = set(v["keywords"])
k_s, inter = keyword_score(mdx_kws, fig_kws, idf, tier)
c_s = c_scores[fid]
final = W_KW * k_s + W_CONTENT * c_s
ranked.append((fid, final, {
"kw": k_s, "content": c_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
margin = ranked[0][1] - ranked[1][1]
reports.append({
"uid": uid, "display": display, "correct_sid": correct_sid,
"mdx_kws": sorted(mdx_kws), "mdx_title": title,
"mdx_summary": mdx_summary,
"top3": ranked[:3], "margin": margin,
})
return {
"phase": 23, "desc": "정제 anchor + content summary",
"formula": f"{W_KW} × 키워드 + {W_CONTENT} × 내용(summary)",
"weights": (W_KW, W_CONTENT, 0.0),
"hits": hits, "reports": reports,
"frame_to_short": frame_to_short, "idx_data": idx_data, "frames": frames,
}
def write_md(r):
lines = []
lines.append("# Phase 23 — 정제 anchor + content summary")
lines.append("")
lines.append(f"**공식**: `{r['formula']}`")
lines.append(f"**결과: {r['hits']}/4 정답**")
lines.append("")
lines.append("**Phase 22 대비**:")
lines.append("- 내용(의미 유사도) 축 추가")
lines.append("- 내용: **MDX summary** ↔ frame.content 의 ko-sroberta cosine")
lines.append(" - summary = title + 첫 일반 문단 + slot labels (detect_mdx.build_summary 사용)")
lines.append(" - Phase 25 template-fit-v1 과 동일한 summary 방식 — axis consistency")
lines.append("")
# 요약
lines.append("## 1. TARGET별 결과")
lines.append("")
lines.append("| MDX | 정답 | 1위 (kw / content / 최종) | 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 "✗"
bd1 = rep["top3"][0][2]
lines.append(
f"| {rep['display']} | {rep['correct_sid']} | "
f"{top1} ({bd1['kw']:.2f}/{bd1['content']:.2f}/{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"![{sid}]({PNG_REL}{png})<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 제목**: {rep['mdx_title']}")
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"- **축별 점수**: 키워드 {bd['kw']:.3f} / 내용 {bd['content']:.3f}")
lines.append(f"- **최종**: **{score:.3f}** = {W_KW}×{bd['kw']:.3f} + {W_CONTENT}×{bd['content']:.3f}")
lines.append("")
# 한계
lines.append("## 4. 한계")
lines.append("")
lines.append("- **의미 유사 ≠ 구조 적합**: ko-sroberta는 '같은 도메인 주제'를 높게 본다. 하지만 3열 병렬과 2열 비교는 구조가 다름에도 cosine이 비슷할 수 있음.")
lines.append("- **구조 축 부재**: MDX 2개 병렬 vs Figma 3열 템플릿이 점수로 구분되지 않음.")
lines.append("- **재구성 불가 판정 없음**: 적합도가 낮아도 여전히 1위가 될 수 있음.")
lines.append("- **다음 단계**: Phase 25에서 legacy 구조 축을 추가.")
lines.append("")
out = HERE / "MATRIX_PHASE23.md"
out.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out}")
def main():
result = run()
with open(HERE / "_phase23_results.pkl", "wb") as f:
pickle.dump(result, f)
write_md(result)
print(f"Phase 23: {result['hits']}/4 정답")
if __name__ == "__main__":
main()