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,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"")
|
||||
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()
|
||||
Reference in New Issue
Block a user