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>
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user