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

295 lines
16 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 26 — 매칭 방법 비교 종합 (Phase 22~25) + Figma DB 설계 결론
Phase 22: 정제 anchor keywords only (baseline)
Phase 23: anchor + content summary
Phase 24: anchor + content + legacy structure ontology
Phase 25: Template-fit-v1 (32 템플릿 + 조건부 cap + 감점 + 라우팅)
결론: 4 TARGET 에서는 모두 4/4 정답이지만,
단순 점수 매칭(22~24) vs 운영 체계(25)의 차이 드러냄.
Figma DB 는 templates_v1 스키마로 가야 함.
"""
import sys
import pickle
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from phase_common import load_frame_index
HERE = Path(__file__).parent
PNG_REL = "../../data/figma_previews/"
def load_all_results():
results = {}
for ph in [22, 23, 24, 25]:
p = HERE / f"_phase{ph}_results.pkl"
with open(p, "rb") as f:
results[ph] = pickle.load(f)
return results
def phase_top(result, uid_or_mdx_id):
"""Phase 22/23/24 은 'uid', Phase 25 (template-fit) 는 'mdx_id'."""
if result["phase"] == 25:
for rep in result["reports"]:
if rep["mdx_id"] == uid_or_mdx_id:
return rep
return None
else:
for rep in result["reports"]:
if rep["uid"] == uid_or_mdx_id:
return rep
return None
def main():
results = load_all_results()
idx_data, frame_to_short = load_frame_index()
TARGET_IDS = ['MDX01-2-details', 'MDX02-2.2-table', 'MDX03-1', 'MDX03-2']
TARGET_NAMES = {
'MDX01-2-details': '1. (MDX 1) 팝업 — DX와 BIM의 구분',
'MDX02-2.2-table': '2. (MDX 2) 2.2 DX 시행 주체별 기대효과',
'MDX03-1': '3. (MDX 03) 1. DX 시행을 위한 필수요건',
'MDX03-2': '4. (MDX 03) 2. Process 혁신과 Product 변화',
}
GT = {
'MDX01-2-details': '18',
'MDX02-2.2-table': '14',
'MDX03-1': '13',
'MDX03-2': '29',
}
lines = []
lines.append("# Phase 26 — 매칭 방법 비교 결과와 Figma DB 설계 결론")
lines.append("")
lines.append("AI 를 활용한 MDX ↔ Figma 매칭을 **네 가지 방법**으로 비교하고, "
"그 결과로부터 **Figma DB 를 어떤 구조로 쌓아야 하는지** 도출한다.")
lines.append("")
# 1. 방법 구성
lines.append("## 1. 네 가지 방법")
lines.append("")
lines.append("| Phase | 방법 | 공식 | 데이터 |")
lines.append("|-------|------|------|--------|")
lines.append("| **22** | 정제 anchor keywords only (baseline) | `1.0 × 키워드` | keyword_base.yaml + anchor_sets mirror |")
lines.append("| **23** | anchor + content summary | `0.5 × 키워드 + 0.5 × 내용` | + ko-sroberta cosine (MDX summary ↔ frame.content) |")
lines.append("| **24** | anchor + content + legacy structure | `0.5 × 키워드 + 0.3 × 내용 + 0.2 × 구조` | + legacy structure ontology (family/semantic_role) |")
lines.append("| **25** | Template-fit-v1 (최종 운영) | `0.25 anchor + 0.20 card + 0.20 rel + 0.15 slot + 0.20 content penalties` | templates_v1 (32개: slots + anchor_sets + fit_notes + adaptation_allowed) |")
lines.append("")
# 2. 정답률
lines.append("## 2. 정답률")
lines.append("")
header_cells = ["Phase"] + [TARGET_NAMES[uid] for uid in TARGET_IDS] + ["합계"]
lines.append("| " + " | ".join(header_cells) + " |")
lines.append("|" + "|".join(["---"] * len(header_cells)) + "|")
for ph in [22, 23, 24, 25]:
row = [f"**{ph}**"]
for uid in TARGET_IDS:
rep = phase_top(results[ph], uid)
if ph == 25:
top_tpl = rep["results"][0][0]
sid = results[ph]["tplid_to_sid"].get(top_tpl, "?")
else:
top_fid = rep["top3"][0][0]
sid = frame_to_short.get(top_fid, "?")
mark = "✓" if sid == GT[uid] else "✗"
row.append(mark)
row.append(f"**{results[ph]['hits']}/{results[ph].get('total', 4)}**")
lines.append("| " + " | ".join(row) + " |")
lines.append("")
lines.append("→ **네 방법 모두 4 TARGET 에서는 4/4**. 정답률만으로는 차이 없음.")
lines.append("")
# 3. Margin 비교
lines.append("## 3. 1-2위 margin 비교 (안정성)")
lines.append("")
lines.append("| Phase | MDX01-2 | MDX02 | MDX03-1 | MDX03-2 | 평균 |")
lines.append("|-------|---------|-------|---------|---------|------|")
for ph in [22, 23, 24, 25]:
row = [f"**{ph}**"]
margins = []
for uid in TARGET_IDS:
rep = phase_top(results[ph], uid)
m = rep["margin"]
margins.append(m)
row.append(f"{m:.3f}")
avg = sum(margins) / len(margins)
row.append(f"**{avg:.3f}**")
lines.append("| " + " | ".join(row) + " |")
lines.append("")
# 4. 유닛별 상세
lines.append("## 4. 유닛별 Phase 점수 상세")
lines.append("")
for uid in TARGET_IDS:
lines.append(f"### {TARGET_NAMES[uid]} — 정답 Frame **{GT[uid]}**")
lines.append("")
lines.append("| Phase | 1위 Frame | 1위 점수 | 2위 Frame | 2위 점수 | margin | 라우팅 |")
lines.append("|-------|-----------|----------|-----------|----------|--------|--------|")
for ph in [22, 23, 24, 25]:
rep = phase_top(results[ph], uid)
if ph == 25:
top1_tpl, top1_r = rep["results"][0]
top2_tpl, top2_r = rep["results"][1]
top1_sid = results[ph]["tplid_to_sid"].get(top1_tpl, "?")
top2_sid = results[ph]["tplid_to_sid"].get(top2_tpl, "?")
top1_score = top1_r["confidence"]
top2_score = top2_r["confidence"]
route_lbl = rep["top1_route"]
else:
top1_fid, top1_score, _ = rep["top3"][0]
top2_fid, top2_score, _ = rep["top3"][1]
top1_sid = frame_to_short.get(top1_fid, "?")
top2_sid = frame_to_short.get(top2_fid, "?")
route_lbl = "-"
highlight_open = ""
highlight_close = ""
if top1_sid == GT[uid]:
highlight_open = ("<span style='background:#fff3cd;padding:2px 6px;"
"border:2px solid #dc2626;border-radius:3px;font-weight:bold'>🎯 ")
highlight_close = "</span>"
lines.append(
f"| {ph} | {highlight_open}{top1_sid}{highlight_close} | {top1_score:.3f} | "
f"{top2_sid} | {top2_score:.3f} | {rep['margin']:.3f} | {route_lbl} |"
)
lines.append("")
# 5. 관찰
lines.append("## 5. 관찰")
lines.append("")
lines.append("### 5-1. 정답률로는 차이 없음")
lines.append("")
lines.append("Phase 22~25 네 방법 모두 4 TARGET 에서 4/4 — 이 4개 유닛만 보면 어느 방법이 더 나은지 결정 불가.")
lines.append("")
lines.append("### 5-2. Margin 으로 본 안정성 차이")
lines.append("")
lines.append("- **Phase 22 (키워드만)**: IDF Jaccard 만으로는 의미 관계 포착 부족. 추상 주제에서 margin 약함.")
lines.append("- **Phase 23 (+내용)**: 의미 유사도가 정답 margin 을 확대하지만, "
"\"의미 비슷한데 구조 다른\" 프레임도 함께 상승.")
lines.append("- **Phase 24 (+기존 구조)**: family/semantic_role 매칭이 정답 확정에 기여. "
"단, 구조 감지 confidence low 일 땐 점수 0 이라 불안정.")
lines.append("- **Phase 25 (template-fit-v1)**: margin 절대값은 Phase 24 대비 일부 케이스에서 좁아지지만, "
"**multi-gate (intent + anchor/content/adaptation/not_suits) 로 정답 후보만 use_as_is** — 운영 관점에선 가장 안전.")
lines.append("")
lines.append("### 5-3. Phase 24 기존 구조축의 한계")
lines.append("")
lines.append("기존 구조축은 `family=list`, `semantic_role=prerequisites` 같은 "
"**디자인이 어떻게 생겼나**만 기록한다. 실제 운영에서는 '이 콘텐츠가 이 디자인에 "
"들어갈 수 있나'를 알아야 하는데, 기존 구조축은 **slot 개수·재구성 비용·금지 사항**을 다루지 못한다.")
lines.append("")
lines.append("### 5-4. Phase 25 (template-fit-v1) 이 제공하는 추가 가치")
lines.append("")
lines.append("- **라우팅 (multi-gate)**: 점수만 제공하는 Phase 22~24 와 달리, "
"**structure_intent gate + anchor/content/adaptation/not_suits multi-constraint** "
"로 `use_as_is` / `light_edit` / `restructure` / `reject` 4단계 운영 판단 제공.")
lines.append("- **structure_intent 축**: 11 frame 에 태깅 (concept_comparison, persona_benefit, "
"requirement_list, problem_diagnosis, process_product_split 등). Tagged mismatch 시 강한 차단 — "
"예: MDX03-1 × Frame 28 (requirement vs problem polarity 반대) = reject.")
lines.append("- **축별 breakdown**: anchor · cardinality · relation · slot · content 가 "
"각각 몇 점인지 설명 가능 → 왜 이 점수인지 투명.")
lines.append("- **조건부 cap**: 짧은 generic anchor (`[BIM, DX]` 2-term) 가 "
"우연히 매칭되어 점수를 부풀리는 편향 방어. "
"MDX01-2 (진짜 BIM/DX 비교) 는 방증 세트(comparison_dimensions 0.71)로 cap 면제, "
"MDX03-2 (BIM/DX 배경 언급) 는 방증 0.29 로 cap 적용 — 의도대로 작동.")
lines.append("- **not_suits 구조 신호**: `주체별 나열`, `필수요건 나열`, `BIM vs DX 직접 대조` 등 "
"잘못된 매칭을 구조 신호로 감점.")
lines.append("- **adaptation_cost**: cardinality 불일치 시 split/merge/infer 비용으로 자연 감쇠.")
lines.append("- **32 템플릿 스키마화**: Phase 22~24 의 legacy frames 는 analysis.md 키워드 + family 로만 "
"기록된 반면, Phase 25 은 각 프레임마다 slot / anchor / fit_notes / adaptation 을 "
"명시한 templates_v1 스키마로 32개 전부 작성.")
lines.append("")
# 6. 결론
lines.append("## 6. 결론 — Figma DB 는 어떻게 쌓아야 하나")
lines.append("")
lines.append("**네 방법 모두 4 TARGET 정답률은 같지만, 제공하는 정보량이 다르다:**")
lines.append("")
lines.append("| 제공 정보 | Phase 22 | Phase 23 | Phase 24 | Phase 25 |")
lines.append("|-----------|:-:|:-:|:-:|:-:|")
lines.append("| 1위 Frame | ✓ | ✓ | ✓ | ✓ |")
lines.append("| 단일 점수 | ✓ | ✓ | ✓ | ✓ |")
lines.append("| 축별 분해 | ✗ | 2축 | 3축 | **5축 + 2감점** |")
lines.append("| 원본 그대로 쓸 수 있나 (use_as_is) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 편집이 필요한가 (light_edit) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 재구성이 필요한가 (restructure) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 아예 쓰면 안 되는가 (reject) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 짧은 anchor 편향 방어 (조건부 cap) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 구조 신호로 감점 (not_suits) | ✗ | ✗ | ✗ | ✓ |")
lines.append("| 재구성 비용 산정 (adaptation_cost) | ✗ | ✗ | ✗ | ✓ |")
lines.append("")
lines.append("**따라서 Figma DB 는 Phase 25 의 templates_v1 스키마로 쌓아야 한다**:")
lines.append("")
lines.append("```yaml")
lines.append("templates_v1:")
lines.append(" <frame_id>:")
lines.append(" template_id: <snake_case_name>")
lines.append(" source: {title, original_layout}")
lines.append(" description: |")
lines.append(" <embedding-friendly 자연어 설명 (suits 맥락 포함)>")
lines.append(" visual_pattern:")
lines.append(" family: list | cards | table | compare | diagram | map | composite")
lines.append(" layout: <구체 layout>")
lines.append(" axis: horizontal | vertical")
lines.append(" relation_type: parallel | sequence | compare | hierarchy")
lines.append(" cardinality: {ideal, min, max}")
lines.append(" slots:")
lines.append(" - {id, type, required, max_chars}")
lines.append(" anchor_sets:")
lines.append(" - id: <set_name>")
lines.append(" terms: [...]")
lines.append(" min_hits: <int, optional>")
lines.append(" confidence_cap: <float, 짧은 generic 세트에만>")
lines.append(" cap_exempt_if_corroborated_by: <방증 임계>")
lines.append(" fit_notes:")
lines.append(" suits: [검수용 bullet, description 에 반영]")
lines.append(" not_suits: [점수 감점 발화 조건]")
lines.append(" adaptation_allowed:")
lines.append(" split, merge, infer_missing_slot, rewrite_label, rewrite_body")
lines.append(" legacy: {family, surface, semantic_role} # Phase 24 호환용 보존")
lines.append("```")
lines.append("")
lines.append("32개 전체 템플릿이 이 스키마로 [`structure_ontology.yaml`](structure_ontology.yaml) 에 정의됨. "
"스펙: [`TEMPLATE_FIT_V1.md`](TEMPLATE_FIT_V1.md). 엔진: [`template_fit.py`](template_fit.py).")
lines.append("")
# 7. 운영 권고
lines.append("## 7. 운영 권고")
lines.append("")
lines.append("- **매칭 엔진**: `template_fit.py` (Phase 25)")
lines.append("- **DB 파일**: `structure_ontology.yaml` → `templates_v1` 블록 "
"(legacy `frames:` 는 Phase 24 호환용으로 보존)")
lines.append("- **MDX 분석**: `detect_mdx.py` — 매칭 단계 LLM 호출 0회")
lines.append("- **임베딩**: `embeddings.py` — ko-sroberta + numpy (32 프레임 규모는 벡터 DB 불필요)")
lines.append("- **라우팅 판정 (multi-gate)**: 단순 threshold 아님 — 여러 조건 AND")
lines.append(" - 1차: **structure_intent gate** (tagged mismatch 강한 차단)")
lines.append(" - `intent_source == 'tagged'` + `intent_compat < 0.4` → **reject**")
lines.append(" - `intent_source == 'tagged'` + `intent_compat < 0.7` → use_as_is/light_edit 금지, restructure 이하만")
lines.append(" - 2차: **route_v2 multi-constraint**")
lines.append(" - `use_as_is`: confidence ≥ 0.90 AND anchor ≥ 0.70 AND content ≥ 0.55 AND not_suits = 0")
lines.append(" - `light_edit`: confidence ≥ 0.75 AND anchor ≥ 0.50 AND content ≥ 0.45 AND adapt_pen < 0.10")
lines.append(" - `restructure`: confidence ≥ 0.60 AND (anchor ≥ 0.35 OR (content ≥ 0.55 AND adapt_pen < 0.20)) AND not_suits ≤ 1")
lines.append(" - 나머지: `reject`")
lines.append("- **운영 의미**:")
lines.append(" - `use_as_is`: 코드로 slot fill, AI 0회")
lines.append(" - `light_edit`: max_chars 초과 시만 AI 1회")
lines.append(" - `restructure`: adaptation_allowed 조작 AI 1회")
lines.append(" - `reject`: 다음 후보 또는 '적합 디자인 없음' 반환")
lines.append("")
lines.append("**추가 검증 필요**: 현재 4 TARGET 으로는 엔진 프로토타입 검증까지 완료. "
"실제 확장 MDX 세트 (17~30개 추정) 회귀 테스트는 별도 진행.")
out = HERE / "MATRIX_PHASE26.md"
out.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out}")
if __name__ == "__main__":
main()