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:
2026-07-02 17:03:42 +09:00
co-authored by Claude Opus 4.8
parent 97b7833a1b
commit b836e79ee1
527 changed files with 673036 additions and 717 deletions
+251
View File
@@ -0,0 +1,251 @@
"""Figma 32개 프레임 구조 검수 리포트 v2
family 단순화 + semantic_role 분리.
"""
import sys
import json
import collections
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from phase_common import load_32_frames
ROOT = Path(r"d:\ad-hoc\kei\design_agent")
PREVIEW_DIR = ROOT / "data" / "figma_previews"
HERE = Path(__file__).parent
# ═══ family 단순화 (7개만) ═══
# compare / table / cards / list / diagram / map / composite
_FAMILY = {
"compare": {"compare-rows", "compare-2col", "compare-2banner-top-2col-bottom",
"2col-paired", "2-boxes", "paired-rows", "central-split", "3col-compare"},
"table": {"table-2col", "table-3col"},
"cards": {"3col-cards", "persona-3col", "cards-4", "cards-4plus5",
"policy-4card-plus-list", "quadrant-issues", "bullet-cards", "side-card"},
"list": {"3col-parallel", "3-emphasis", "3-category", "3-section",
"list-numbered", "list-stacked"},
"diagram": {"cycle-3way", "circular-nodes", "diagram-labels", "diagram-5",
"split-panel-diagram", "split-panel-numbered"},
"map": {"full-page-map"},
"composite": {"central-5-goals"},
}
def get_family(layout):
for fam, layouts in _FAMILY.items():
if layout in layouts:
return fam
return "(미분류)"
# shape / items / has_* 매핑 (검수용 힌트)
_PROPS = {
"3col-parallel": {"shape": "three-col", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"3col-cards": {"shape": "three-col", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"3col-compare": {"shape": "three-col", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"persona-3col": {"shape": "three-col", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"compare-rows": {"shape": "multi-row-table", "items": None, "has_table": True, "has_cards": False, "has_diagram": False},
"compare-2col": {"shape": "two-col", "items": 2, "has_table": False, "has_cards": False, "has_diagram": False},
"compare-2banner-top-2col-bottom": {"shape": "banner-plus-two-col", "items": 2, "has_table": False, "has_cards": False, "has_diagram": False},
"table-2col": {"shape": "two-col-table", "items": 2, "has_table": True, "has_cards": False, "has_diagram": False},
"table-3col": {"shape": "three-col-table", "items": 3, "has_table": True, "has_cards": False, "has_diagram": False},
"cycle-3way": {"shape": "circular", "items": 3, "has_table": False, "has_cards": False, "has_diagram": True},
"circular-nodes": {"shape": "circular", "items": None, "has_table": False, "has_cards": False, "has_diagram": True},
"diagram-labels": {"shape": "radial", "items": None, "has_table": False, "has_cards": False, "has_diagram": True},
"diagram-5": {"shape": "radial", "items": 5, "has_table": False, "has_cards": False, "has_diagram": True},
"cards-4": {"shape": "four-grid", "items": 4, "has_table": False, "has_cards": True, "has_diagram": False},
"cards-4plus5": {"shape": "four-plus-five", "items": 9, "has_table": False, "has_cards": True, "has_diagram": False},
"policy-4card-plus-list": {"shape": "four-card-plus-list", "items": 4, "has_table": False, "has_cards": True, "has_diagram": False},
"quadrant-issues": {"shape": "2x2", "items": 4, "has_table": False, "has_cards": True, "has_diagram": False},
"paired-rows": {"shape": "paired", "items": 4, "has_table": False, "has_cards": True, "has_diagram": False},
"central-split": {"shape": "center-split", "items": None, "has_table": False, "has_cards": False, "has_diagram": False},
"central-5-goals": {"shape": "center-radial", "items": 5, "has_table": False, "has_cards": False, "has_diagram": True},
"3-emphasis": {"shape": "three-horizontal", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"3-category": {"shape": "three-horizontal", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"3-section": {"shape": "three-horizontal", "items": 3, "has_table": False, "has_cards": True, "has_diagram": False},
"bullet-cards": {"shape": "mixed", "items": None, "has_table": False, "has_cards": True, "has_diagram": False},
"list-numbered": {"shape": "numbered", "items": None, "has_table": False, "has_cards": False, "has_diagram": False},
"list-stacked": {"shape": "stacked", "items": None, "has_table": False, "has_cards": False, "has_diagram": False},
"side-card": {"shape": "side-column", "items": None, "has_table": False, "has_cards": True, "has_diagram": False},
"full-page-map": {"shape": "full-map", "items": None, "has_table": False, "has_cards": False, "has_diagram": True},
"split-panel-diagram": {"shape": "split", "items": 2, "has_table": False, "has_cards": False, "has_diagram": True},
"split-panel-numbered": {"shape": "split", "items": None, "has_table": False, "has_cards": False, "has_diagram": False},
"2col-paired": {"shape": "two-col", "items": 2, "has_table": False, "has_cards": True, "has_diagram": False},
"2-boxes": {"shape": "two-box", "items": 2, "has_table": False, "has_cards": True, "has_diagram": False},
}
# ═══ semantic_role 분리 — 별도 필드 (family와 무관) ═══
# 코드 기반 heuristic 추측 (자동 완벽 불가 → 디자이너 검수 전제)
_SEMANTIC_HINT_PATTERNS = [
# (role, keywords, min_hits_required)
("prerequisites", ["필수", "조건", "요건"], 2),
("persona-benefits", ["발주자", "시공자", "설계자"], 3),
("industry-compare", ["제조업", "건축", "토목"], 3),
("safety-quality-productivity", ["안전", "품질", "생산성"], 3),
("process-vs-product", ["과정", "결과", "process", "product"], 2),
("policy-goals", ["정책", "목표"], 2),
("sw-ecosystem", ["상용", "3rdparty", "package", "solution", "engineering", "전문성", "비효율"], 2),
("risk-problems", ["독과점", "기술예속", "사용료", "존폐"], 2),
("design-distortion", ["설계", "왜곡", "엔지니어", "기능인"], 3),
("term-definition", ["용어", "정의", "혼용"], 2),
("innovation-stages", ["혁신", "단계"], 2),
("bim-vs-dx", ["bim", "dx", "비교"], 2),
]
# 명시적 role priority — 동률 tie-breaker (높을수록 우선)
_ROLE_PRIORITY = {
"persona-benefits": 10, # 구조 정의적 (표 헤더)
"industry-compare": 10, # 구조 정의적 (산업 구분)
"prerequisites": 9, # 구체적 도메인
"safety-quality-productivity": 8, # 목표 정의적
"process-vs-product": 8,
"policy-goals": 7,
"sw-ecosystem": 6,
"risk-problems": 6,
"design-distortion": 6,
"term-definition": 5,
"innovation-stages": 4, # 일반적
"bim-vs-dx": 1, # 가장 일반적 (last-resort)
}
def guess_semantic_role(content_summary, keywords):
"""Best-match: hits 많음 → ratio 높음 → priority 높음 순서로 선택."""
text = (content_summary + " " + ",".join(keywords)).lower()
candidates = []
for role, req_kws, min_req in _SEMANTIC_HINT_PATTERNS:
hits = sum(1 for kw in req_kws if kw in text)
if hits >= min_req:
ratio = hits / len(req_kws)
candidates.append((hits, ratio, role))
if candidates:
# 1) hits 많음 2) ratio 높음 3) priority 높음
candidates.sort(key=lambda x: (-x[0], -x[1], -_ROLE_PRIORITY.get(x[2], 0)))
return candidates[0][2]
return "(검수 필요)"
def main():
frames = load_32_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/"
lines = []
lines.append("# Figma 32개 구조 검수 리포트 v2")
lines.append("")
lines.append("> **개선판**: family를 7개로 단순화, semantic_role 별도 필드로 분리.")
lines.append("> 현재 매핑은 **Claude가 만든 1차 초안** — 디자이너 검수 대상.")
lines.append("")
# 0. Ontology 소개
lines.append("## 0. Ontology (검수용 초안)")
lines.append("")
lines.append("**family (7개 — 구조 성격):**")
lines.append("")
lines.append("| family | 의미 | 포함 layout 예시 |")
lines.append("|--------|------|----------------|")
lines.append("| `compare` | 관점·항목 대비 | compare-rows, compare-2col, paired-rows |")
lines.append("| `table` | 표 형식 비교 | table-2col, table-3col |")
lines.append("| `cards` | 카드 묶음 | 3col-cards, persona-3col, cards-4, quadrant-issues |")
lines.append("| `list` | 나열 | 3col-parallel, list-numbered, 3-section |")
lines.append("| `diagram` | 다이어그램 | cycle-3way, diagram-labels, diagram-5 |")
lines.append("| `map` | 지도 | full-page-map |")
lines.append("| `composite` | 혼합 | central-5-goals |")
lines.append("")
lines.append("**semantic_role (의미 구조 — family와 독립):**")
lines.append("")
sr_items = [r[0] for r in _SEMANTIC_HINT_PATTERNS]
lines.append("- 후보: `" + "`, `".join(sr_items) + "`")
lines.append("- 자동 감지가 불명확하면 `(검수 필요)`로 표시")
lines.append("")
# family 분포
lines.append("## 1. family 분포")
lines.append("")
fam_counter = collections.Counter()
for v in frames.values():
fam_counter[get_family(v["layout"])] += 1
lines.append("| family | 개수 |")
lines.append("|--------|------|")
for fam, cnt in sorted(fam_counter.items(), key=lambda x: -x[1]):
lines.append(f"| `{fam}` | {cnt} |")
lines.append("")
# role 분포
role_counter = collections.Counter()
for v in frames.values():
role = guess_semantic_role(v["content"], v["keywords"])
role_counter[role] += 1
lines.append("## 2. semantic_role 분포 (자동 추측)")
lines.append("")
lines.append("| semantic_role | 개수 | 비고 |")
lines.append("|---------------|------|------|")
for role, cnt in sorted(role_counter.items(), key=lambda x: -x[1]):
note = "검수 필요 (힌트 부족)" if role == "(검수 필요)" else ""
lines.append(f"| `{role}` | {cnt} | {note} |")
lines.append("")
# 프레임 상세
lines.append("## 3. 프레임별 검수 상세")
lines.append("")
for fid in sorted(frames.keys()):
v = frames[fid]
sid = frame_to_short.get(fid, "?")
info = idx_data.get(sid, {})
png = info.get("png", "")
title = info.get("title_text", "").strip().replace("\n", " ") or "(타이틀 없음)"
layout = v.get("layout", "(미지정)")
family = get_family(layout)
props = _PROPS.get(layout, {})
content = v.get("content", "")
keywords = v.get("keywords", [])
role = guess_semantic_role(content, keywords)
lines.append(f"### Frame {sid} (`{fid}`)")
lines.append("")
lines.append(f"![{sid}]({png_rel}{png})")
lines.append("")
lines.append(f"**내용 요약**: {content}")
lines.append("")
lines.append("| 속성 | 현재 값 | 검수 |")
lines.append("|------|--------|------|")
lines.append(f"| title_text | {title} | ⬜ |")
lines.append(f"| **layout** | `{layout}` | ⬜ |")
lines.append(f"| **family** | `{family}` | ⬜ |")
lines.append(f"| shape | `{props.get('shape', '-')}` | ⬜ |")
lines.append(f"| items | {props.get('items', '-')} | ⬜ |")
lines.append(f"| has_table | {props.get('has_table', '-')} | ⬜ |")
lines.append(f"| has_cards | {props.get('has_cards', '-')} | ⬜ |")
lines.append(f"| has_diagram | {props.get('has_diagram', '-')} | ⬜ |")
lines.append(f"| **semantic_role** (자동 추측) | `{role}` | ⬜ |")
lines.append("")
lines.append(f"**후보 키워드 ({len(keywords)}개)**: "
f"{', '.join(keywords[:15])}"
+ (" ..." if len(keywords) > 15 else ""))
lines.append("")
lines.append("---")
lines.append("")
lines.append("## 4. 검수 사용 가이드")
lines.append("")
lines.append("1. 프레임 썸네일을 **실제로 보면서** 속성 값이 맞는지 체크")
lines.append("2. 틀린 것: `⬜ → ✗` 로 표시 + 올바른 값을 옆에 기입 (예: `layout: 3col-parallel → cards-4`)")
lines.append("3. 검수 완료: `⬜ → ✓`")
lines.append("4. 작업 완료 후:")
lines.append(" - `analysis.md`의 layout 수정")
lines.append(" - `figma_audit.py`의 `_FAMILY` / `_PROPS` / `_SEMANTIC_HINT_PATTERNS` 수정")
lines.append("5. 검수 완료된 분포로 Phase 21b/c/d 재실험")
lines.append("")
out = HERE / "FIGMA_AUDIT.md"
out.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out}")
if __name__ == "__main__":
main()