Files
KyeongminandClaude Opus 4.8 b836e79ee1 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>
2026-07-02 17:03:42 +09:00

331 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()