"""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()