Files
C.E.L_Slide_test2/tests/matching/phase15.py
T
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

246 lines
11 KiB
Python

"""Phase 15 — 스토리텔링: "TF-IDF(키워드)는 왜 실패하고 AI-Meta(구조+의미)는 왜 성공하는가"
MDX03-1 케이스로 Before/After 대조.
"""
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 (
_load_metadata_db, _build_tfidf, method_tfidf,
method_ai_meta_for_uid,
)
ROOT = Path(r"d:\ad-hoc\kei\design_agent")
PREVIEW_DIR = ROOT / "data" / "figma_previews"
def main():
units = extract_units()
figma = load_figma_texts()
meta = _load_metadata_db()
mdx_sections = meta["mdx_sections"]
figma_frames = meta["figma_frames"]
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()}
png_rel = "../../data/figma_previews/"
# ═══ IDF 계산 (AI-Meta 용, Figma 개념 집합 기준) ═══
df_concept = collections.Counter()
N_concept = 0
for fid, v in figma_frames.items():
if isinstance(v, dict):
for c in v.get("concepts", []):
df_concept[c] += 1
N_concept += 1
idf_concept = {c: math.log(N_concept / cnt) for c, cnt in df_concept.items() if cnt > 0}
# ═══ 주인공: MDX03-1 ═══
uid = "MDX03-1"
correct_sid = "13"
wrong_sid = "15" # TF-IDF가 1위로 고르는 오답
correct_fid = "1171281190"
wrong_fid = "1171281192"
lines = []
lines.append("# Phase 15 — 왜 키워드만으론 실패하고, AI 구조+의미가 붙으니 성공하는가")
lines.append("")
lines.append("## 🎬 시나리오")
lines.append("")
lines.append("**MDX03-1** \"1. DX 시행을 위한 필수 요건\" (기술·사람·자연 3요소)")
lines.append("")
lines.append("**정답**: Frame 13 (3열 구조 \"필수조건\" — 기술/사람/자연 카드)")
lines.append(f"**오답으로 자주 잡히는 후보**: Frame 15 (\"정책목표\" — 성장·비용절감·안전 4개 카드)")
lines.append("")
# 썸네일
lines.append("| 정답 | 오답(TF-IDF 1위) |")
lines.append("|------|------------------|")
c_png = idx_data[correct_sid]["png"]
w_png = idx_data[wrong_sid]["png"]
lines.append(f"| ![{correct_sid}]({png_rel}{c_png})<br>**Frame {correct_sid}** — 필수조건 | "
f"![{wrong_sid}]({png_rel}{w_png})<br>**Frame {wrong_sid}** — 정책목표 |")
lines.append("")
lines.append("두 프레임 모두 **3~4열 카드 배치** + 건설·DX 관련 용어가 등장 → 단어만 보면 구분이 어려움.")
lines.append("")
# ═══ Part 1: TF-IDF 시도 ═══
lines.append("---")
lines.append("")
lines.append("# Part 1. 기존 방식 — TF-IDF (키워드 표면만 본다)")
lines.append("")
# TF-IDF 실제 돌려서 top 3 얻기
mdx_text = units[uid]
ranked = method_tfidf(mdx_text, figma)[:3]
# TF-IDF 벡터 재계산해서 공통 단어 추출용
all_docs = {"__mdx__": mdx_text, **figma}
vecs, df, N = _build_tfidf(all_docs)
mdx_vec = vecs["__mdx__"]
lines.append("### TF-IDF Top-3 결과")
lines.append("")
lines.append("| 순위 | Frame | 점수 | 결과 |")
lines.append("|------|-------|------|------|")
for rank, (fid, score) in enumerate(ranked, 1):
sid = frame_to_short.get(str(fid), "?")
mark = "✗ 오답(1위)" if rank == 1 and sid != correct_sid else ("⭐ 정답" if sid == correct_sid else "")
lines.append(f"| {rank}위 | **{sid}** | {score * 100:.0f}% | {mark} |")
lines.append("")
# 왜 Frame 15가 1위인지 — 공통 단어 분석
lines.append("### 왜 Frame 15가 1위로 잘못 뽑혔나 — 공통 단어 분해")
lines.append("")
wrong_vec = vecs[wrong_fid]
correct_vec = vecs[correct_fid]
def common_words(vec_a, vec_b, top=8):
common = [(w, vec_a[w], vec_b[w], vec_a[w] * vec_b[w])
for w in vec_a if w in vec_b]
common.sort(key=lambda x: -x[3])
return common[:top]
lines.append("**MDX03-1 ↔ Frame 15 (정책목표) — 공통 단어 상위 8개**")
lines.append("")
lines.append("| 단어 | MDX TF-IDF | Frame 15 TF-IDF | 기여도 |")
lines.append("|------|------------|-----------------|--------|")
for w, a, b, contrib in common_words(mdx_vec, wrong_vec):
lines.append(f"| {w} | {a:.3f} | {b:.3f} | {contrib:.4f} |")
lines.append("")
lines.append("**MDX03-1 ↔ Frame 13 (필수조건, 정답) — 공통 단어 상위 8개**")
lines.append("")
lines.append("| 단어 | MDX TF-IDF | Frame 13 TF-IDF | 기여도 |")
lines.append("|------|------------|-----------------|--------|")
for w, a, b, contrib in common_words(mdx_vec, correct_vec):
lines.append(f"| {w} | {a:.3f} | {b:.3f} | {contrib:.4f} |")
lines.append("")
lines.append("### TF-IDF가 실패하는 이유")
lines.append("")
lines.append("1. **단어 표면만 본다**: \"요건\"이라는 단어가 MDX(필수 **요건**)에도 있고 Frame 15(실행**요건**)에도 있음. 문맥·의미는 완전 다른데 TF-IDF는 그냥 같은 단어로 취급")
lines.append("2. **Frame 13의 핵심 단어 \"필수조건\"이 깨진다**: Kiwi 같은 형태소 분석기는 \"필수조건\"\"필수\" + \"조건\"으로 분해. MDX는 \"필수 **요건**\"을 쓰는데 Frame 13은 \"필수**조건**\". \"요건\"\"조건\"은 동의어인데 글자가 달라 매칭이 안 됨")
lines.append("3. **구조를 못 본다**: \"이 MDX는 3요소 병렬 설명이고 Frame 13도 3열 병렬 카드\"라는 구조 정보를 TF-IDF는 인지 불가. \"몇 열\", \"어떤 패턴\" 정보가 통째로 빠짐")
lines.append("")
lines.append("→ 결과: 표면 단어 겹침이 많은 Frame 15가 1위로, 진짜 같은 구조·의미인 Frame 13이 2위로 밀림")
lines.append("")
# ═══ Part 2: AI-Meta로 해결 ═══
lines.append("---")
lines.append("")
lines.append("# Part 2. AI-Meta — AI가 구조와 의미를 개념 집합으로 정규화")
lines.append("")
lines.append("### AI가 사전에 뽑아둔 개념 집합 (metadata_db.yaml)")
lines.append("")
mdx_concepts = mdx_sections[uid]["concepts"]
mdx_layout = mdx_sections[uid].get("layout_hint", "")
correct_concepts = figma_frames[correct_fid]["concepts"]
wrong_concepts = figma_frames[wrong_fid]["concepts"]
lines.append(f"**MDX03-1** (layout_hint: `{mdx_layout}`)")
lines.append("")
lines.append(f"```yaml")
lines.append(f"concepts: {mdx_concepts}")
lines.append(f"```")
lines.append("")
lines.append(f"**Frame 13** (정답)")
lines.append("")
lines.append(f"```yaml")
lines.append(f"concepts: {correct_concepts}")
lines.append(f"```")
lines.append("")
lines.append(f"**Frame 15** (TF-IDF가 1위로 찍었던 오답)")
lines.append("")
lines.append(f"```yaml")
lines.append(f"concepts: {wrong_concepts}")
lines.append(f"```")
lines.append("")
# AI-Meta 점수 계산
ai_ranked = method_ai_meta_for_uid(uid, figma)[:3]
lines.append("### AI-Meta Top-3 결과")
lines.append("")
lines.append("| 순위 | Frame | 점수 | 결과 |")
lines.append("|------|-------|------|------|")
for rank, (fid, score) in enumerate(ai_ranked, 1):
sid = frame_to_short.get(str(fid), "?")
mark = "⭐ 정답" if sid == correct_sid else ""
lines.append(f"| {rank}위 | **{sid}** | {score * 100:.1f}% | {mark} |")
lines.append("")
# 교집합 상세
lines.append("### 왜 Frame 13이 1위가 됐나 — 개념 교집합")
lines.append("")
mdx_set = set(mdx_concepts)
correct_set = set(correct_concepts)
wrong_set = set(wrong_concepts)
inter_c = mdx_set & correct_set
inter_w = mdx_set & wrong_set
lines.append("**MDX03-1 ∩ Frame 13 (정답)**")
lines.append("")
lines.append("| 공통 개념 | Figma 출현 빈도 | IDF (희귀도) |")
lines.append("|-----------|----------------|--------------|")
inter_c_sorted = sorted(inter_c, key=lambda c: -idf_concept.get(c, 0))
for c in inter_c_sorted:
lines.append(f"| **{c}** | {df_concept.get(c, 0)}/32 | {idf_concept.get(c, 0):.2f} |")
lines.append("")
lines.append(f"**공통 {len(inter_c)}개 (IDF 합 = {sum(idf_concept.get(c, 0) for c in inter_c):.2f})**")
lines.append("")
lines.append(f"**MDX03-1 ∩ Frame 15 (오답)**")
lines.append("")
if inter_w:
lines.append(", ".join(sorted(inter_w)))
else:
lines.append("**공통 개념 0개** — \"요건\" 같은 표면 단어는 TF-IDF 단계에서 걸리지만, AI는 \"요건\"이 Frame 15에서는 \"정책 실행요건\"을 의미하기에 MDX03-1의 \"필수 요건\" 개념과 다르게 추출함")
lines.append("")
lines.append("### AI-Meta가 성공하는 이유")
lines.append("")
lines.append("1. **동의어 정규화**: AI가 MDX03-1 본문을 읽고 \"필수 요건\"을 직접 단어로 쓰지 않고, 그 내용인 `[기술, 사람, 자연, 여건, ...]`로 개념화. Frame 13도 같은 핵심 개념으로 태그. **표면 단어가 다르더라도 의미 일치**")
lines.append("2. **구조 힌트 포함**: `layout_hint: 3col-parallel`이라는 구조 태그가 있어 \"3열 병렬 카드\"끼리 매칭 선호 가능 (현재 구현은 concepts만 쓰지만 layout_hint도 활용 가능)")
lines.append("3. **희귀 개념 우대**: IDF로 \"여건, 사람, 자연, 역량\" 같은 이 MDX에만 특징적인 개념은 3.47이라는 큰 가중치. \"디지털\"처럼 많은 프레임에 나오는 일반어는 1.86으로 낮은 가중치")
lines.append("4. **근거가 투명**: 어떤 개념이 겹쳐서 몇 점이 됐는지 한눈에 확인 가능. Cross-encoder 같은 블랙박스와 달리 사람이 metadata_db를 직접 편집해서 튜닝 가능")
lines.append("")
# ═══ 요약 비교 ═══
lines.append("---")
lines.append("")
lines.append("# 최종 Before/After 비교")
lines.append("")
lines.append("| | TF-IDF (키워드만) | AI-Meta (키워드+구조+의미) |")
lines.append("|---|---|---|")
lines.append("| 1위 선택 | Frame 15 ❌ | Frame 13 ⭐ |")
lines.append("| 기준 | 단어 표면 겹침 | 개념 집합 일치 (구조 포함) |")
lines.append("| 동의어 | 못 다룸 (\"요건\"\"조건\" 실패) | AI가 사전에 같은 개념으로 정규화 |")
lines.append("| 구조 | 무시 | layout_hint로 명시적 태깅 |")
lines.append("| 설명 가능성 | 공통 단어 나열 → 설명 제한적 | 개념 교집합 + IDF → 명확 |")
lines.append("| 튜닝 | corpus 변경하면 IDF 전체 재계산 | metadata_db에 개념 추가/수정으로 조정 |")
lines.append("| 비용 | AI 불필요, 가벼움 | 사전 AI 1회 호출 필요 (이후 저장된 데이터만 사용) |")
lines.append("")
lines.append("**결론**: 키워드 기반은 표면 단어 한계에 막힌다. "
"AI가 구조·의미를 미리 개념 집합으로 정규화하는 순간, "
"동의어·구조 불일치 문제가 자연스럽게 해결된다.")
lines.append("")
out_path = Path(__file__).parent / "MATRIX_PHASE15.md"
out_path.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out_path}")
if __name__ == "__main__":
main()