- 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>
331 lines
14 KiB
Python
331 lines
14 KiB
Python
"""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"")
|
||
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()
|