"""BEPS 기반 통합 정리 리포트 생성. 출력: - BEPS_SUMMARY_REPORT.md (검수용 통합 리포트) - BEPS_SUMMARY_REPORT.html (md_to_html.py 로 생성) 구조: 1. 키워드 (BEPS 기준 정리) Step 01. 키워드 추출 Step 02. 띄어쓰기 및 유사 단어 정리 2. 구조 기준 (Figma 프레임별 AI 분석) 3. 프레임별 정리 내용 (32 frame 상세) """ import sys from pathlib import Path import yaml 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_keyword_base(): with open(HERE / "keyword_base.yaml", encoding='utf-8') as f: return yaml.safe_load(f) def load_ontology(): with open(HERE / "structure_ontology.yaml", encoding='utf-8') as f: return yaml.safe_load(f) def main(): kb = load_keyword_base() onto = load_ontology() idx_data, frame_to_short = load_frame_index() keywords = kb['keywords'] excluded = kb['excluded'] templates = onto.get('templates_v1', {}) lines = [] # ═══ Header ═══ lines.append("# BEPS 기반 통합 정리 리포트") lines.append("") lines.append("**대상 corpus**: BEPS(1171281171) + 32 Figma frames(texts + analysis + flat) + 3 MDX") lines.append("") lines.append("**구성**:") lines.append("1. 키워드 (BEPS 기준 정리) — 추출 / 정규화 / canonical 확정") lines.append("2. 구조 기준 (Figma 프레임별 AI 분석) — visual_pattern / structure_intent") lines.append("3. 프레임별 정리 내용 — 32 frame 상세 (구조 + 적용 가능성)") lines.append("") # ═══ 1. 키워드 ═══ lines.append("---") lines.append("") lines.append("## 1. 키워드 (BEPS 기준 정리)") lines.append("") # ─── Step 01 ─── lines.append("### Step 01. BEPS 및 MDX에서 키워드 추출") lines.append("") total_terms = len(keywords) + len(excluded) lines.append(f"- **입력 corpus**: BEPS(1171281171) master + 32 Figma frames × 3 파일 + 3 MDX 샘플 = **36 sources**") lines.append(f"- **1차 추출 대상**: templates_v1 의 anchor_sets 고유 term **{total_terms}개** (32 frame 에 걸친 anchor pool)") lines.append(f"- **전처리 삭제 (총 49개)**:") # excluded breakdown by bucket from collections import Counter bucket_counts = Counter(e['bucket'] for e in excluded) bucket_labels = { 'REMOVE_structure': '구조·레이아웃 서술어 (3열비교, 표, 다이어그램, split-panel 등)', 'REMOVE_weak': 'row label / weak polarity (정의, 특징, 개요, 적극, 구체, 소극 등)', 'REMOVE_summary_label': 'AI 요약 라벨 (3산업, 4대문제, BIM목적, 산업비교 등)', 'REMOVE_group_c_empty': 'evidence 없는 summary keep 후보 (필수성, 프레임워크 등)', 'REMOVE_no_evidence': '어떤 corpus 에도 exact substring 없음', 'REVIEW_remove_candidate':'generic/약어 제거 후보 (Speed, When, 관점별)', } for bk in ['REMOVE_structure', 'REMOVE_weak', 'REMOVE_summary_label', 'REMOVE_group_c_empty', 'REMOVE_no_evidence', 'REVIEW_remove_candidate']: if bucket_counts.get(bk, 0): lines.append(f" - {bucket_labels[bk]}: **{bucket_counts[bk]}개**") lines.append(f"- **최종 canonical**: **{len(keywords)}개** (precision-focused, evidence-based)") lines.append("") # 예시 키워드 lines.append("**예시 키워드 (type 별)**:") lines.append("") by_type = {} for c, entry in keywords.items(): by_type.setdefault(entry['type'], []).append(c) for t in ['canonical_only', 'formatting', 'compound_curator', 'compound_curator_anchor_only', 'synonym_registered', 'abbreviation', 'curator_summary_keep']: if t in by_type: sample = sorted(by_type[t])[:10] lines.append(f"- `{t}` ({len(by_type[t])}개): {', '.join(sample)}{'...' if len(by_type[t]) > 10 else ''}") lines.append("") # ─── Step 02 ─── lines.append("### Step 02. 띄어쓰기 및 유사 단어 정리 (정규화)") lines.append("") normalizable = [k for k, v in keywords.items() if v.get('variants')] lines.append(f"- **variants 등록 canonical**: **{len(normalizable)}개** " f"(total canonical 의 {len(normalizable)/len(keywords)*100:.0f}%)") total_variants = sum(len(v.get('variants', [])) for v in keywords.values()) lines.append(f"- **등록 variants 총수**: {total_variants}개") lines.append(f"- **정규화 로직**: MDX 원문에 variants 발견 시 canonical 로 치환 후 매칭") lines.append("") lines.append("**주요 정규화 예시**:") lines.append("") lines.append("| Canonical | Variants |") lines.append("|-----------|----------|") # 관심있는 핵심 canonical 부터 priority_canonicals = [ 'DX', '필수조건', '과정혁신', '결과혁신', '3D모델', '2D도면', 'BIM', 'GIS', '의사소통', '시행착오', '공사비절감', '토지현황', '시공전모델', 'Model특화', 'PC활용', '코딩기반SW', ] for c in priority_canonicals: if c in keywords: variants = keywords[c].get('variants', []) vstr = ', '.join(variants) if variants else '_(없음)_' lines.append(f"| `{c}` | {vstr} |") lines.append("") # ═══ 2. 구조 기준 ═══ lines.append("---") lines.append("") lines.append("## 2. 구조 기준 (Figma 프레임별 AI 분석)") lines.append("") # 2-1. 속성 체계 lines.append("### 2-1. 구조 속성 체계") lines.append("") lines.append("**visual_pattern** (32 frame 전부 보유):") lines.append("") lines.append("| 속성 | 값 종류 |") lines.append("|------|---------|") lines.append("| `family` | list / cards / table / compare / diagram / map / composite |") lines.append("| `layout` | 구체 layout (cards-3-horizontal, 2col-paired, split-panel-diagram, ...) |") lines.append("| `axis` | horizontal / vertical |") lines.append("| `relation_type` | parallel / sequence / compare / hierarchy |") lines.append("| `cardinality` | {ideal, min, max} — 수치 범위 |") lines.append("") lines.append("**structure_intent** (11 frame 태깅, 나머지 21 = neutral):") lines.append("") lines.append("| Intent | 의미 | 해당 frame |") lines.append("|--------|------|----------|") intent_map = { 'concept_comparison': ('2개 개념 대조', [18, 23]), 'multi_attribute_comparison': ('여러 관점으로 다면 비교', [18, 23, 24, 30, 31]), 'transformation_story': ('AS-IS → TO-BE 변환', [29]), 'process_product_split': ('과정/결과 2분할', [29]), 'category_comparison': ('S/W 유형 분류 비교', [24]), 'industry_comparison': ('산업별 비교', [30, 31]), 'persona_benefit': ('3주체 기대효과', [14]), 'requirement_list': ('필수요건 나열 (긍정형)', [13]), 'problem_diagnosis': ('문제·한계 진단 (부정형)', [17, 28]), 'requirement_or_pillar': ('일반 N-parallel', [20]), } for intent, (desc, frames) in intent_map.items(): lines.append(f"| `{intent}` | {desc} | Frame {', '.join(str(f).zfill(2) for f in frames)} |") lines.append("") # 2-2. 프레임별 intent 요약 lines.append("### 2-2. 프레임별 structure_intent 태깅 (11개)") lines.append("") lines.append("| Frame | 주제 | structure_intent |") lines.append("|-------|------|------------------|") ordered = sorted(templates.items(), key=lambda kv: kv[1].get('short_id', '99')) for fid, tpl in ordered: sid = tpl.get('short_id', '?') intents = tpl.get('visual_pattern', {}).get('structure_intent', []) if intents: title = tpl.get('source', {}).get('title', '?') lines.append(f"| **{sid}** | {title} | {', '.join(intents)} |") lines.append("") # ═══ 3. 프레임별 상세 ═══ lines.append("---") lines.append("") lines.append("## 3. 프레임별 정리 내용 (32 frame 상세)") lines.append("") lines.append("각 프레임의 visual_pattern + anchor_sets + fit_notes + adaptation_allowed 전수.") lines.append("") # 한 표로 요약 lines.append("### 3-1. 요약 표") lines.append("") lines.append("| # | Frame | 제목 | family | relation | card | slots | anchor_sets | intent |") lines.append("|---|-------|------|--------|----------|------|-------|-------------|--------|") for fid, tpl in ordered: sid = tpl.get('short_id', '?') title = tpl.get('source', {}).get('title', '?')[:24] vp = tpl.get('visual_pattern', {}) fam = vp.get('family', '?') rel = vp.get('relation_type', '?') card = vp.get('cardinality', {}) card_str = f"{card.get('ideal', '?')}" if card.get('min') != card.get('max'): card_str = f"{card.get('ideal')} [{card.get('min')}-{card.get('max')}]" slots = len(tpl.get('slots', [])) n_anchors = len(tpl.get('anchor_sets', [])) intents = vp.get('structure_intent', []) intent_str = ', '.join(intents) if intents else '_(미태깅)_' lines.append(f"| {sid} | {fid} | {title} | {fam} | {rel} | {card_str} | {slots} | {n_anchors} sets | {intent_str} |") lines.append("") # 3-2. 프레임별 세부 (이미지 포함) lines.append("### 3-2. 프레임별 세부") lines.append("") for fid, tpl in ordered: sid = tpl.get('short_id', '?') title = tpl.get('source', {}).get('title', '?') tpl_id = tpl.get('template_id', '?') info = idx_data.get(sid, {}) png = info.get('png', '') lines.append(f"#### {sid}. {title}") lines.append("") if png: lines.append(f"![{sid}]({PNG_REL}{png})") lines.append("") lines.append(f"- **frame_id**: `{fid}`") lines.append(f"- **template_id**: `{tpl_id}`") lines.append("") # description desc = tpl.get('description', '').strip() if desc: lines.append("**내용 설명**") lines.append("") for line in desc.split('\n'): if line.strip(): lines.append(line.strip()) lines.append("") # visual_pattern vp = tpl.get('visual_pattern', {}) lines.append("**구조 속성**") lines.append("") lines.append(f"- family: `{vp.get('family', '?')}`") lines.append(f"- layout: `{vp.get('layout', '?')}`") lines.append(f"- axis: `{vp.get('axis', '?')}`") lines.append(f"- relation_type: `{vp.get('relation_type', '?')}`") card = vp.get('cardinality', {}) lines.append(f"- cardinality: ideal **{card.get('ideal')}** / min {card.get('min')} / max {card.get('max')}") intents = vp.get('structure_intent', []) if intents: lines.append(f"- **structure_intent**: {', '.join(f'`{i}`' for i in intents)}") else: lines.append(f"- structure_intent: _(미태깅 — neutral 처리)_") lines.append("") # anchor_sets anchor_sets = tpl.get('anchor_sets', []) if anchor_sets: lines.append(f"**Anchor Sets ({len(anchor_sets)}개)**") lines.append("") for s in anchor_sets: set_id = s.get('id', '?') terms = ', '.join(s.get('terms', [])) extras = [] if 'min_hits' in s: extras.append(f"min_hits={s['min_hits']}") if 'confidence_cap' in s: extras.append(f"cap={s['confidence_cap']}") if 'cap_exempt_if_corroborated_by' in s: extras.append(f"exempt_if≥{s['cap_exempt_if_corroborated_by']}") extra_str = f" _[{', '.join(extras)}]_" if extras else "" lines.append(f"- **{set_id}**: {terms}{extra_str}") lines.append("") # slots slots = tpl.get('slots', []) if slots: required = [s for s in slots if s.get('required')] optional = [s for s in slots if not s.get('required')] lines.append(f"**Slots ({len(slots)}개, required {len(required)}개)**") lines.append("") for s in slots: marker = '🔒' if s.get('required') else '⭕' type_str = s.get('type', '?') max_c = s.get('max_chars', '?') lines.append(f"- {marker} `{s.get('id', '?')}` ({type_str}, max_chars={max_c})") lines.append("") # fit_notes fit = tpl.get('fit_notes', {}) if fit: lines.append("**적합/부적합 기준**") lines.append("") if fit.get('suits'): lines.append("_suits_:") for s in fit['suits']: lines.append(f"- {s}") lines.append("") if fit.get('not_suits'): lines.append("_not_suits_:") for s in fit['not_suits']: lines.append(f"- {s}") lines.append("") # adaptation_allowed adapt = tpl.get('adaptation_allowed', {}) if adapt: lines.append("**재구성 허용**") lines.append("") for k in ['split', 'merge', 'infer_missing_slot', 'rewrite_label', 'rewrite_body']: if k in adapt: icon = '✓' if adapt[k] else '✗' lines.append(f"- {icon} {k}") lines.append("") lines.append("---") lines.append("") out = HERE / "BEPS_SUMMARY_REPORT.md" out.write_text("\n".join(lines), encoding='utf-8') print(f"완료: {out}") if __name__ == "__main__": main()