"""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})
**Frame {correct_sid}** — 필수조건 | " f"![{wrong_sid}]({png_rel}{w_png})
**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()