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,426 @@
|
||||
"""Pipeline Step 11 — templates_v1 + _COMPAT 구조 기준 갭 감사.
|
||||
|
||||
목적:
|
||||
V3 의 "대안 탐색" 역할이 왜 현재 세팅에서 약한지 수치/사례로 규명.
|
||||
다음 단계 (taxonomy v2 설계) 의 근거 자료.
|
||||
|
||||
감사 축:
|
||||
1. _COMPAT 커버리지 — MDX × Figma layout 쌍 중 실제 정의 비율
|
||||
2. layout 관계 그래프 — _COMPAT 을 그래프로 보고, 고립된 layout / 대체 가능 layout 분석
|
||||
3. structure_intent 분포 — 얼마나 세분화되어 있나, free-form / controlled 어느 쪽인가
|
||||
4. slot 체계 — slot id 어휘, 재사용성
|
||||
5. V3 실패 사례 분해 — Holdout 평가에서 비합리 판정 받은 V3 프레임의 구조 지표
|
||||
6. layout 간 "대체 가능" 관계 발굴 — 현재 압묵 정보 확인
|
||||
|
||||
입력:
|
||||
- structure_ontology.yaml (templates_v1)
|
||||
- phase_common._COMPAT
|
||||
- v3_structure_rerank_result.yaml (V3 실제 출력)
|
||||
- holdout_labels.yaml (라벨 결과)
|
||||
- phase_common.detect_mdx_layout_v2 (MDX 측 layout 생성)
|
||||
|
||||
출력:
|
||||
- TEMPLATES_V1_AUDIT.md
|
||||
- TEMPLATES_V1_AUDIT.html
|
||||
"""
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import yaml
|
||||
import markdown
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
from phase_common import _COMPAT, detect_mdx_layout_v2, structural_match_v2, load_frame_index
|
||||
from pipeline_01_extract_nodes import MDX_SECTIONS, MDX_DIR
|
||||
|
||||
MD_PATH = HERE / 'TEMPLATES_V1_AUDIT.md'
|
||||
HTML_PATH = HERE / 'TEMPLATES_V1_AUDIT.html'
|
||||
|
||||
|
||||
def extract_mdx_raw(sid):
|
||||
cfg = MDX_SECTIONS[sid]
|
||||
p = MDX_DIR / cfg['file']
|
||||
lines = p.read_text(encoding='utf-8').split('\n')
|
||||
start = None
|
||||
for i, ln in enumerate(lines):
|
||||
if ln.strip() == cfg['start'].strip():
|
||||
start = i
|
||||
break
|
||||
end = len(lines)
|
||||
if cfg.get('end_prefix'):
|
||||
for i in range(start + 1, len(lines)):
|
||||
if lines[i].strip().startswith(cfg['end_prefix']):
|
||||
end = i
|
||||
break
|
||||
return '\n'.join(lines[start:end])
|
||||
|
||||
|
||||
def main():
|
||||
ontology = yaml.safe_load((HERE / 'structure_ontology.yaml').read_text(encoding='utf-8'))
|
||||
templates = ontology['templates_v1']
|
||||
v3 = yaml.safe_load((HERE / 'v3_structure_rerank_result.yaml').read_text(encoding='utf-8'))
|
||||
labels = yaml.safe_load((HERE / 'holdout_labels.yaml').read_text(encoding='utf-8'))
|
||||
idx_data, frame_to_short = load_frame_index()
|
||||
frame_num_map = {fid: int(frame_to_short[fid]) for fid in templates.keys()}
|
||||
num_to_fid = {v: k for k, v in frame_num_map.items()}
|
||||
|
||||
# ============================================================
|
||||
# 1. _COMPAT 커버리지
|
||||
# ============================================================
|
||||
# 실제 등장하는 fig_layout 집합 (source.original_layout 기준)
|
||||
fig_layouts_used = Counter()
|
||||
for fid, tpl in templates.items():
|
||||
fig_layouts_used[tpl['source']['original_layout']] += 1
|
||||
fig_layouts_set = set(fig_layouts_used.keys())
|
||||
|
||||
# MDX 쪽은 _COMPAT 키 + detect_mdx_layout_v2 결과의 union
|
||||
mdx_layouts_from_compat = set(_COMPAT.keys())
|
||||
mdx_layouts_from_sections = set()
|
||||
section_mdx_layout = {}
|
||||
for sid in MDX_SECTIONS:
|
||||
raw = extract_mdx_raw(sid)
|
||||
mdx_layout = detect_mdx_layout_v2(raw)
|
||||
section_mdx_layout[sid] = mdx_layout
|
||||
mdx_layouts_from_sections.add(mdx_layout)
|
||||
mdx_all_layouts = mdx_layouts_from_compat | mdx_layouts_from_sections
|
||||
|
||||
# 각 MDX layout 에 대해 _COMPAT 매핑 수 / 실제 32 프레임 커버리지
|
||||
compat_coverage = {}
|
||||
for mdx_layout in sorted(mdx_all_layouts):
|
||||
row = _COMPAT.get(mdx_layout, {})
|
||||
defined_pairs = len(row)
|
||||
covered_frames = sum(1 for fig in fig_layouts_used if fig in row)
|
||||
total_frames_with_any_layout = len(fig_layouts_used)
|
||||
# fallback 비율
|
||||
compat_coverage[mdx_layout] = {
|
||||
'defined_pairs': defined_pairs,
|
||||
'covered_frame_types': covered_frames,
|
||||
'total_frame_types': total_frames_with_any_layout,
|
||||
'fallback_frame_types': total_frames_with_any_layout - covered_frames,
|
||||
'coverage_pct': round(100 * covered_frames / total_frames_with_any_layout, 1),
|
||||
}
|
||||
|
||||
# 전체 쌍 집계
|
||||
total_possible_pairs = len(mdx_all_layouts) * len(fig_layouts_set)
|
||||
total_defined_pairs = sum(
|
||||
1 for mdx_l in mdx_all_layouts for fig_l in fig_layouts_set
|
||||
if fig_l in _COMPAT.get(mdx_l, {})
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 2. structure_intent 분포
|
||||
# ============================================================
|
||||
intents = Counter()
|
||||
intent_to_frames = defaultdict(list)
|
||||
for fid, tpl in templates.items():
|
||||
for it in tpl['visual_pattern'].get('structure_intent', []):
|
||||
intents[it] += 1
|
||||
intent_to_frames[it].append(frame_num_map[fid])
|
||||
intent_stats = {
|
||||
'total_unique': len(intents),
|
||||
'total_tags': sum(intents.values()),
|
||||
'orphan_count': sum(1 for _, c in intents.items() if c == 1),
|
||||
'top_shared': intents.most_common(),
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 3. slot 체계
|
||||
# ============================================================
|
||||
slot_ids = Counter()
|
||||
frame_slot_counts = []
|
||||
for fid, tpl in templates.items():
|
||||
for s in tpl.get('slots', []):
|
||||
slot_ids[s['id']] += 1
|
||||
frame_slot_counts.append(len(tpl.get('slots', [])))
|
||||
slot_stats = {
|
||||
'unique_ids': len(slot_ids),
|
||||
'most_common': slot_ids.most_common(10),
|
||||
'frame_slot_count_min': min(frame_slot_counts) if frame_slot_counts else 0,
|
||||
'frame_slot_count_max': max(frame_slot_counts) if frame_slot_counts else 0,
|
||||
'frame_slot_count_mean': round(sum(frame_slot_counts) / len(frame_slot_counts), 1)
|
||||
if frame_slot_counts else 0,
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 4. V3 실패 사례 분해 (Holdout 3 섹션 기반)
|
||||
# ============================================================
|
||||
failure_cases = []
|
||||
for sid, sec in labels['sections'].items():
|
||||
v3_pick = sec['picks']['V3']
|
||||
v3_rating = v3_pick['rating']
|
||||
if v3_rating == 'rational':
|
||||
continue # 성공 케이스 skip
|
||||
|
||||
# V3 의 전체 결과에서 해당 섹션 Top-5
|
||||
v3_full = v3['mdx_sections'][sid]['v3_rerank']
|
||||
v3_sorted = sorted(v3_full, key=lambda x: x['v3_rank'])
|
||||
v3_top = v3_sorted[0]
|
||||
|
||||
# V1 이 잡은 1위 (비교용)
|
||||
# v3 yaml 에는 v1_rank 도 있으므로 그걸 이용
|
||||
v1_top_row = [r for r in v3_full if r['v1_rank'] == 1]
|
||||
v1_top = v1_top_row[0] if v1_top_row else None
|
||||
|
||||
mdx_layout = section_mdx_layout.get(sid)
|
||||
|
||||
case = {
|
||||
'sid': sid,
|
||||
'mdx_title': sec['mdx_title'],
|
||||
'mdx_layout': mdx_layout,
|
||||
'v3_rating': v3_rating,
|
||||
'v3_score': v3_pick.get('score', 0),
|
||||
'v3_top': {
|
||||
'frame_number': v3_top['frame_number'],
|
||||
'fig_layout': v3_top.get('fig_layout'),
|
||||
'structure_compat': v3_top.get('structure_compat'),
|
||||
'template_intent': templates[v3_top['frame_id']]['visual_pattern'].get('structure_intent', []),
|
||||
},
|
||||
}
|
||||
if v1_top:
|
||||
case['v1_top'] = {
|
||||
'frame_number': v1_top['frame_number'],
|
||||
'fig_layout': v1_top.get('fig_layout'),
|
||||
'structure_compat': v1_top.get('structure_compat'),
|
||||
'template_intent': templates[v1_top['frame_id']]['visual_pattern'].get('structure_intent', []),
|
||||
}
|
||||
# V3 가 잘못 올린 이유 분석
|
||||
case['diagnosis'] = {
|
||||
'v3_pick_compat': v3_top['structure_compat'],
|
||||
'v1_pick_compat': v1_top['structure_compat'],
|
||||
'intent_overlap_v3': len(set(case['v3_top']['template_intent'])),
|
||||
'intent_overlap_v1': len(set(case['v1_top']['template_intent'])),
|
||||
}
|
||||
failure_cases.append(case)
|
||||
|
||||
# ============================================================
|
||||
# 5. layout 대체 가능 관계 발굴
|
||||
# ============================================================
|
||||
# 각 MDX layout 에 대해 compat ≥ 0.7 인 fig_layout 이 몇 개 있나 — "대안이 있는가"
|
||||
alternative_richness = {}
|
||||
for mdx_layout in sorted(mdx_all_layouts):
|
||||
row = _COMPAT.get(mdx_layout, {})
|
||||
high = sum(1 for v in row.values() if v >= 0.7)
|
||||
mid = sum(1 for v in row.values() if 0.4 <= v < 0.7)
|
||||
low = sum(1 for v in row.values() if v < 0.4)
|
||||
alternative_richness[mdx_layout] = {
|
||||
'high_compat_count': high,
|
||||
'mid_compat_count': mid,
|
||||
'low_compat_count': low,
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Markdown 리포트
|
||||
# ============================================================
|
||||
md = []
|
||||
md.append('# templates_v1 + _COMPAT 구조 기준 갭 감사')
|
||||
md.append('')
|
||||
md.append(
|
||||
'_V3 의 "대안 탐색" 역할이 현재 세팅에서 왜 약한지 수치/사례로 규명. '
|
||||
'C.2 taxonomy v2 설계의 근거 자료._'
|
||||
)
|
||||
md.append('')
|
||||
|
||||
# 1. _COMPAT 커버리지
|
||||
md.append('## 1. _COMPAT 커버리지 — V3 구분력의 근본 한계')
|
||||
md.append('')
|
||||
md.append(
|
||||
f"- 전체 가능 쌍: **{len(mdx_all_layouts)} MDX layouts × {len(fig_layouts_set)} Figma layouts = "
|
||||
f"{total_possible_pairs} 쌍**"
|
||||
)
|
||||
md.append(
|
||||
f"- _COMPAT 에 실제 정의된 쌍: **{total_defined_pairs} / {total_possible_pairs} "
|
||||
f"({round(100 * total_defined_pairs / total_possible_pairs, 1)}%)**"
|
||||
)
|
||||
md.append(
|
||||
f"- 정의 안 된 쌍 (fallback 0.15): **{total_possible_pairs - total_defined_pairs} 쌍 "
|
||||
f"({round(100 * (total_possible_pairs - total_defined_pairs) / total_possible_pairs, 1)}%)**"
|
||||
)
|
||||
md.append('')
|
||||
md.append('### MDX layout 별 커버리지')
|
||||
md.append('')
|
||||
md.append('| MDX layout | _COMPAT 정의 쌍 | 커버 Figma 유형 | 총 유형 | 커버리지 | fallback 유형 수 |')
|
||||
md.append('|---|---:|---:|---:|---:|---:|')
|
||||
for mdx_l in sorted(mdx_all_layouts):
|
||||
c = compat_coverage[mdx_l]
|
||||
used_in_sections = ', '.join(sid for sid, ml in section_mdx_layout.items() if ml == mdx_l)
|
||||
mark = f' **[섹션 {used_in_sections}]**' if used_in_sections else ''
|
||||
md.append(
|
||||
f"| `{mdx_l}`{mark} | {c['defined_pairs']} | {c['covered_frame_types']} | "
|
||||
f"{c['total_frame_types']} | {c['coverage_pct']}% | {c['fallback_frame_types']} |"
|
||||
)
|
||||
md.append('')
|
||||
|
||||
# 2. alternative richness
|
||||
md.append('## 2. MDX layout 별 "대안 탐색" 풍부도')
|
||||
md.append('')
|
||||
md.append('각 MDX layout 에 대해 _COMPAT 에서 compat ≥ 0.7 인 Figma layout 이 몇 개 있나.')
|
||||
md.append(
|
||||
'값이 작으면 그 MDX 구조에 "대안이 없음" — V3 가 대안을 제시할 재료가 부족한 상태.'
|
||||
)
|
||||
md.append('')
|
||||
md.append('| MDX layout | 고호환 (≥0.7) | 중 (0.4~0.7) | 저 (<0.4) |')
|
||||
md.append('|---|---:|---:|---:|')
|
||||
for mdx_l in sorted(mdx_all_layouts):
|
||||
a = alternative_richness[mdx_l]
|
||||
md.append(
|
||||
f"| `{mdx_l}` | {a['high_compat_count']} | {a['mid_compat_count']} | {a['low_compat_count']} |"
|
||||
)
|
||||
md.append('')
|
||||
|
||||
# 3. structure_intent
|
||||
md.append('## 3. structure_intent 분포')
|
||||
md.append('')
|
||||
md.append(f"- 고유 intent 수: **{intent_stats['total_unique']}**")
|
||||
md.append(f"- 전체 태그 수: {intent_stats['total_tags']}")
|
||||
md.append(
|
||||
f"- 1 프레임만 쓰는 고아 intent: {intent_stats['orphan_count']} — "
|
||||
f"{'많음 (구조 재사용 약함)' if intent_stats['orphan_count'] > 5 else '적음'}"
|
||||
)
|
||||
md.append('')
|
||||
md.append('| intent | 빈도 | 프레임 |')
|
||||
md.append('|---|---:|---|')
|
||||
for it, c in intent_stats['top_shared']:
|
||||
frames = ', '.join(str(n) for n in intent_to_frames[it])
|
||||
md.append(f"| `{it}` | {c} | {frames} |")
|
||||
md.append('')
|
||||
md.append(
|
||||
'**판정**: 10개 intent, 32 프레임 — controlled vocabulary 느낌은 있으나 '
|
||||
'어휘 자체가 풍부하지 않음. 새 MDX 유형이 오면 기존 intent 에 억지로 매핑해야 할 가능성.'
|
||||
)
|
||||
md.append('')
|
||||
|
||||
# 4. slot
|
||||
md.append('## 4. slot 체계')
|
||||
md.append('')
|
||||
md.append(
|
||||
f"- 고유 slot id: **{slot_stats['unique_ids']}**"
|
||||
)
|
||||
md.append(
|
||||
f"- 프레임당 slot 수: 평균 {slot_stats['frame_slot_count_mean']} · "
|
||||
f"최소 {slot_stats['frame_slot_count_min']} / 최대 {slot_stats['frame_slot_count_max']}"
|
||||
)
|
||||
md.append('')
|
||||
md.append('### 가장 자주 쓰이는 slot id (Top 10)')
|
||||
md.append('')
|
||||
md.append('| slot id | 프레임 수 |')
|
||||
md.append('|---|---:|')
|
||||
for sid, c in slot_stats['most_common']:
|
||||
md.append(f"| `{sid}` | {c} |")
|
||||
md.append('')
|
||||
|
||||
# 5. V3 실패 사례 분해
|
||||
md.append('## 5. V3 실패 사례 분해 (Holdout 평가 기준)')
|
||||
md.append('')
|
||||
if not failure_cases:
|
||||
md.append('V3 실패 사례 없음.')
|
||||
else:
|
||||
md.append(f'V3 가 비합리/애매 판정 받은 섹션: {len(failure_cases)} 건.')
|
||||
md.append('')
|
||||
for case in failure_cases:
|
||||
md.append(f"### {case['sid']} — {case['mdx_title']}")
|
||||
md.append('')
|
||||
md.append(f"- MDX layout (코드 추출): `{case['mdx_layout']}`")
|
||||
md.append(f"- V3 판정: **{case['v3_rating']}** ({case['v3_score']}점)")
|
||||
md.append('')
|
||||
md.append('| | V3 가 올린 Top-1 | V1 이 잡은 1위 |')
|
||||
md.append('|---|---|---|')
|
||||
v3_top = case['v3_top']
|
||||
v1_top = case.get('v1_top', {})
|
||||
md.append(
|
||||
f"| Frame | {v3_top['frame_number']} | "
|
||||
f"{v1_top.get('frame_number', '—')} |"
|
||||
)
|
||||
md.append(
|
||||
f"| fig_layout | `{v3_top['fig_layout']}` | "
|
||||
f"`{v1_top.get('fig_layout', '—')}` |"
|
||||
)
|
||||
md.append(
|
||||
f"| structure_compat | **{v3_top['structure_compat']}** | "
|
||||
f"**{v1_top.get('structure_compat', '—')}** |"
|
||||
)
|
||||
md.append(
|
||||
f"| structure_intent | {v3_top['template_intent']} | "
|
||||
f"{v1_top.get('template_intent', [])} |"
|
||||
)
|
||||
md.append('')
|
||||
if v1_top and v3_top['structure_compat'] >= v1_top.get('structure_compat', 0):
|
||||
md.append(
|
||||
f"**진단**: V3 가 올린 Frame {v3_top['frame_number']} 의 구조 호환도가 "
|
||||
f"V1 의 합리적 1위 Frame {v1_top['frame_number']} 과 같거나 높음. "
|
||||
f"즉 _COMPAT 만으로는 **의미적으로 더 맞는 프레임을 골라낼 근거 부족**. "
|
||||
f"structure_intent 나 content_affinity 같은 **2차 신호** 가 없어서 "
|
||||
f"동일 layout family 안에서 분별이 안 됨."
|
||||
)
|
||||
md.append('')
|
||||
|
||||
# 6. 결론 + 개선 방향
|
||||
md.append('## 6. 결론 + taxonomy v2 설계 방향')
|
||||
md.append('')
|
||||
md.append('### 주요 갭 3가지')
|
||||
md.append('')
|
||||
md.append(
|
||||
f"1. **_COMPAT 커버리지 {round(100 * total_defined_pairs / total_possible_pairs, 1)}% "
|
||||
f"— 2/3 쌍이 fallback 0.15**. V3 의 "
|
||||
f'"구분력" 자체가 약함. 수동 테이블이 아니라 **자동 계산 가능한 관계 그래프** 필요.'
|
||||
)
|
||||
md.append(
|
||||
'2. **structure_intent 10개 — 어휘 얕음**. 같은 layout family 안에서 '
|
||||
'프레임 간 의미 구분 불가. **더 세분화된 의도 taxonomy + controlled vocabulary** 필요.'
|
||||
)
|
||||
md.append(
|
||||
'3. **"대안 레이아웃" 필드 없음**. 현재 스키마는 "이 layout 은 무엇인가" 만 기술. '
|
||||
'"이 layout 이 못 담는 콘텐츠 유형 / 대체 가능한 layout" 을 추가해야 '
|
||||
'V3 가 대안 탐색을 할 수 있음.'
|
||||
)
|
||||
md.append('')
|
||||
md.append('### v2 taxonomy 에 추가할 필드 (제안)')
|
||||
md.append('')
|
||||
md.append('- `content_affinity`: 이 layout 이 선호하는 콘텐츠 성격 enum '
|
||||
'(concept_definition / quantitative_compare / persona_benefit / '
|
||||
'process_steps / policy_requirements / ...)')
|
||||
md.append('- `slot_semantic_role`: 각 slot 이 담는 의미적 역할 (주체/시점/원인/결과/예시/...)')
|
||||
md.append('- `alternative_patterns`: 이 layout 의 대안 layout 목록 + 대체 조건')
|
||||
md.append('- `structure_intent` controlled vocab 정의 (현재 free-form → enum)')
|
||||
md.append('- layout 관계 그래프 (수동 _COMPAT → 필드 기반 자동 계산)')
|
||||
md.append('')
|
||||
|
||||
md_text = '\n'.join(md)
|
||||
MD_PATH.write_text(md_text, encoding='utf-8')
|
||||
|
||||
# HTML
|
||||
style = """
|
||||
body { font-family: -apple-system, "Segoe UI", Pretendard, sans-serif; max-width: 1300px; margin: 2em auto; padding: 0 1.5em 4em; line-height: 1.65; color: #222; background: #f8fafc; }
|
||||
h1 { border-bottom: 3px solid #2563eb; padding-bottom: 0.25em; }
|
||||
h2 { margin-top: 2.5em; background: #e0e7ff; padding: 0.6em 0.9em; border-left: 4px solid #0a6; border-radius: 4px; }
|
||||
h3 { margin-top: 1.5em; color: #1a365d; }
|
||||
table { border-collapse: collapse; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.05); margin: 0.5em 0 1em; }
|
||||
th, td { border: 1px solid #e2e8f0; padding: 8px 10px; text-align: left; vertical-align: top; }
|
||||
th { background: #1e293b; color: #fff; font-weight: 700; text-align: center; }
|
||||
code { background: #f4f4f4; padding: 1px 6px; border-radius: 3px; font-size: 0.9em; color: #111; }
|
||||
strong { color: #0a6; }
|
||||
"""
|
||||
html_body = markdown.markdown(md_text, extensions=['tables'])
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head><meta charset="utf-8"><title>templates_v1 감사</title><style>{style}</style></head>
|
||||
<body>
|
||||
{html_body}
|
||||
</body></html>"""
|
||||
HTML_PATH.write_text(html, encoding='utf-8')
|
||||
|
||||
print("=" * 70)
|
||||
print("templates_v1 감사 완료")
|
||||
print("=" * 70)
|
||||
print(f" md: {MD_PATH}")
|
||||
print(f" html: {HTML_PATH}")
|
||||
print()
|
||||
print(f" _COMPAT 커버리지: {total_defined_pairs}/{total_possible_pairs} "
|
||||
f"({round(100 * total_defined_pairs / total_possible_pairs, 1)}%)")
|
||||
print(f" structure_intent 종류: {intent_stats['total_unique']}")
|
||||
print(f" V3 실패 사례: {len(failure_cases)} 건")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user