docs + V4 catalog + samples + Phase Q legacy 보존
전체 26 files (20 추가 + 6 수정), 10507 insertions. Phase Z 문서 : - docs/architecture/PHASE-Z-CHANGE-LOG.md (신설) — axis-by-axis 의사결정 history (newest-on-top). Step 7-A 부터 6 entry 박힘 + 2026-05-08 / 2026-05-08 #2 (compat 매트릭스 폐기 / 6-B 폐기 / F14 표현 정정 / label gate policy 분리). - docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md (수정) — Step 5/6/9 Gap note append (구조 무변, append-only). 6-B 폐기 사실 + Refinement F. - docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md (수정) — snapshot date 2026-05-08 갱신. §3 핵심 missing item 5 (Step 5/6/9 boundary axis breakdown + 폐기 기록). §6 한 줄 갱신 — 다음 axis 후보 A~F. Project root docs : - PLAN.md / PROGRESS.md / README.md (수정) — 토큰 체계 / 폴더 구조 / 설계 문서 / 역할 분리 반영. - IMPROVEMENT-REDESIGN.md (신설) — Phase Z 설계 핵심 문서. - PROCESS_OVERVIEW.html (신설) — 파이프라인 개요 시각. - docs/tasks/* (신설) — Phase Z task 문서. V4 catalog (Phase Z runtime 필수 의존성) : - tests/matching/v4_full32_result.yaml (신설, 4888 줄) — V4 매칭 결과 32 frame × 10 MDX section. lookup_v4_match() / lookup_v4_candidates() 가 본 파일 read. Phase Z runtime 이 *없으면 즉시 abort* — clone 후 즉시 동작 가능 보장. Samples : - samples/mdx_batch/04.mdx (신설) — MDX04 기본 sample. - samples/mdx/04. DX 지연 요인.mdx (신설) — MDX04 원본. Phase Q legacy 보존 (별 axis "Phase Q audit & salvage" 영역) : - src/block_matcher_tfidf.py / catalog_blocks.py / frame_extractor.py / pipeline_v2.py — Phase Q (옛 파이프라인) src 신규 untracked 파일들. Phase Z runtime 와 의존성 0. Phase Q audit axis 에서 검토 예정. - scripts/eval_block_matcher.py / fetch_all_frame_screenshots.py / match_17_units_my_matcher.py / match_mdx_strict.py / match_mdx_to_frames_tfidf.py / ocr_augment_texts.py / run_pipeline_v2.py / previews/ — Phase Q 작업 시 사용한 옛 script. 같이 보존. - run_mdx03_pipeline.py (수정) — Phase Q 진입점 (no flag) + Phase Z 진입점 (--phase-z2 flag) 동시 wrapper. Phase Z 만 사용 시 `python -m src.phase_z2_pipeline samples/mdx_batch/03.mdx <run_id>` 직접 호출. 비-scope : - tests/matching/ (v4_full32_result.yaml 외 ~63MB) — V4 진화 history / reports / DECK / ATTACH. Phase Q audit axis 에서 검토. - tests/pipeline/ (~15MB) — pipeline data. Phase Q audit 영역. - templates/catalog/blocks.yaml — 옛 block catalog. Phase Q audit. - templates/phase_z2/frames/ — 옛 frame partial 위치. Phase Q audit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
416
scripts/previews/mdx04_f16_override.py
Normal file
416
scripts/previews/mdx04_f16_override.py
Normal file
@@ -0,0 +1,416 @@
|
||||
"""MDX04 F16 override slide-fit preview — slide_fit_preview, NOT a Phase Z final.
|
||||
|
||||
배경:
|
||||
- V4 top1 = F26. 사용자 semantic review 로 F16 채택
|
||||
- 사유: MDX04 04-2.* 는 4-issue diagnostic 구조 → F16 quadrant pattern 적합
|
||||
- F26 figma 1:1 변환 부재 (별도 작업 보류)
|
||||
- anchor 보정 / detect_mdx 수정 / v4_full32_result.yaml 변경 모두 없음
|
||||
|
||||
매핑 (사용자 결정 — B 수정판, 그대로 유지):
|
||||
04-2.1 (4) + 04-2.2 (4) = 8 항목을 4 원인군으로 그룹핑. 04-2.2 보존.
|
||||
|
||||
레이아웃 전환 (composition_preview → slide_fit_preview):
|
||||
이전: 1280×1230 비표준 (composition preview)
|
||||
현재: 1280×720 표준 슬라이드 (slide_fit_preview)
|
||||
├ title bar (1280×56)
|
||||
├ body (1200×590)
|
||||
│ ├ zone-left (340×590) = 04-1 compact 5-card stack
|
||||
│ └ zone-right (840×590) = F16 quadrant zone-fit (4분면 + center quote)
|
||||
└ footer pill (1280×48)
|
||||
F16 native dim (1280×1015) 폐기. zone (840×590) 에 맞게 좌표 재계산. 폰트 축소.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MDX_PATH = ROOT / "samples" / "mdx_batch" / "04.mdx"
|
||||
V4_RESULT = ROOT / "tests" / "matching" / "v4_full32_result.yaml"
|
||||
RUN_DIR = ROOT / "data" / "runs" / "mdx04_f16_override"
|
||||
TEMPLATES_DIR = RUN_DIR / "templates"
|
||||
|
||||
|
||||
# ─── 그룹핑 정의 (사용자 예시 그대로) ──────────────────────────
|
||||
|
||||
GROUPING_RULE = {
|
||||
'description': '04-2.1 (4 정책 항목) + 04-2.2 (4 조직 항목) = 8 항목을 4 원인군으로 그룹핑. F16 4 분면 ribbon = 그룹명.',
|
||||
'reason': 'user wants 04-2.2 보존 + F16 4 분면 디자인 활용. 1:1 짝짓기 강제 회피.',
|
||||
'groups': [
|
||||
{
|
||||
'quadrant': 'q1',
|
||||
'name': '정책 집행 / 제도 운용 문제',
|
||||
'items': [
|
||||
{'source': '04-2.1', 'index': 0}, # 실질적 기술 경쟁을 저해하는 정책 집행
|
||||
{'source': '04-2.1', 'index': 1}, # 적용 효과가 있는 사례도 없이 방침부터 도입
|
||||
],
|
||||
},
|
||||
{
|
||||
'quadrant': 'q2',
|
||||
'name': '개념 이해 부족',
|
||||
'items': [
|
||||
{'source': '04-2.1', 'index': 2}, # 엔지니어링 S/W에 대한 개념 부재
|
||||
{'source': '04-2.2', 'index': 0}, # 공학적 개념 정립 부재
|
||||
{'source': '04-2.2', 'index': 2}, # DX/BIM의 근본 취지와 목표의 이해 부족
|
||||
],
|
||||
},
|
||||
{
|
||||
'quadrant': 'q3',
|
||||
'name': '기술 투자 / 본업 기술력 부족',
|
||||
'items': [
|
||||
{'source': '04-2.1', 'index': 3}, # 기술투자(R&D) 없는 성과 창출 기대
|
||||
{'source': '04-2.2', 'index': 1}, # '본업 기술력 확보' 우선의 개념 부재
|
||||
],
|
||||
},
|
||||
{
|
||||
'quadrant': 'q4',
|
||||
'name': '조직 / 수행 역량 문제',
|
||||
'items': [
|
||||
{'source': '04-2.2', 'index': 3}, # 과거의 타성에 머무르고 있는 기술자 집단
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# ─── MDX 04 파싱 ────────────────────────────────────────────────
|
||||
|
||||
RE_SUBSECTION_HEAD = re.compile(r'^###\s+(\d+\.\d+)\s+(.+)$', re.MULTILINE)
|
||||
RE_TOP_BULLET = re.compile(r'^-\s+\*\*([^*]+)\*\*\s*$')
|
||||
|
||||
|
||||
def extract_subsection_items(text, num_label):
|
||||
lines = text.split('\n')
|
||||
start = None
|
||||
for i, ln in enumerate(lines):
|
||||
m = RE_SUBSECTION_HEAD.match(ln.strip())
|
||||
if m and m.group(1) == num_label:
|
||||
start = i
|
||||
break
|
||||
if start is None:
|
||||
return None, []
|
||||
end = len(lines)
|
||||
for j in range(start + 1, len(lines)):
|
||||
s = lines[j].strip()
|
||||
if RE_SUBSECTION_HEAD.match(s) or s == '---':
|
||||
end = j
|
||||
break
|
||||
section_title = lines[start].lstrip('# ').strip()
|
||||
body_lines = lines[start + 1:end]
|
||||
items = []
|
||||
cur = None
|
||||
for ln in body_lines:
|
||||
stripped = ln.strip()
|
||||
m = RE_TOP_BULLET.match(stripped)
|
||||
if m:
|
||||
if cur is not None:
|
||||
items.append(cur)
|
||||
cur = {'headline': m.group(1).strip(), 'subs': []}
|
||||
continue
|
||||
m2 = re.match(r'^-\s+(.+)$', stripped)
|
||||
if m2 and cur is not None and not stripped.startswith('- **'):
|
||||
cur['subs'].append(m2.group(1).strip())
|
||||
if cur is not None:
|
||||
items.append(cur)
|
||||
return section_title, items
|
||||
|
||||
|
||||
def extract_section_04_1_cards(text):
|
||||
m = re.search(r'## 1\. DX에 대한 인식(.*?)(?=^## 2\.)', text, re.DOTALL | re.MULTILINE)
|
||||
if not m:
|
||||
return None, []
|
||||
body = m.group(1)
|
||||
cards = []
|
||||
h3_iter = list(re.finditer(r'<h3[^>]*>([^<]+)</h3>', body))
|
||||
for idx, h3m in enumerate(h3_iter):
|
||||
label = h3m.group(1).strip()
|
||||
section_end = h3_iter[idx + 1].start() if idx + 1 < len(h3_iter) else len(body)
|
||||
section_text = body[h3m.end():section_end]
|
||||
# 인용 (첫 <p> 의 따옴표 텍스트)
|
||||
quote_m = re.search(r'<p[^>]*>(?:["“])(.+?)(?:["”])</p>', section_text, re.DOTALL)
|
||||
if not quote_m:
|
||||
quote_m = re.search(r'<p[^>]*>([^<]+)</p>', section_text, re.DOTALL)
|
||||
quote = quote_m.group(1).strip() if quote_m else ''
|
||||
bullets = [b.strip() for b in re.findall(r'<li[^>]*>([^<]+)</li>', section_text)]
|
||||
cards.append({'label': label, 'quote': quote, 'bullets': bullets})
|
||||
return '1. DX에 대한 인식', cards
|
||||
|
||||
|
||||
# ─── F16 grouped mapper ────────────────────────────────────────
|
||||
|
||||
def map_to_f16_grouped(items_2_1, items_2_2, slide_title):
|
||||
"""8 items (2.1 4 + 2.2 4) → 4 quadrant groups (사용자 그룹핑 룰 적용)."""
|
||||
source_map = {'04-2.1': items_2_1, '04-2.2': items_2_2}
|
||||
payload = {'center_quote': slide_title}
|
||||
for group in GROUPING_RULE['groups']:
|
||||
q = group['quadrant']
|
||||
items_for_q = []
|
||||
for ref in group['items']:
|
||||
src_items = source_map[ref['source']]
|
||||
idx = ref['index']
|
||||
if idx < len(src_items):
|
||||
src_item = src_items[idx]
|
||||
items_for_q.append({
|
||||
'source': '[' + ref['source'].replace('04-', '') + ']',
|
||||
'headline': src_item['headline'],
|
||||
'subs': src_item['subs'],
|
||||
})
|
||||
payload[f'{q}_label'] = group['name']
|
||||
payload[f'{q}_items'] = items_for_q
|
||||
return payload
|
||||
|
||||
|
||||
def map_to_5card_compact_slots(cards, section_title):
|
||||
return {'section_title': section_title, 'cards': cards}
|
||||
|
||||
|
||||
# ─── V4 metadata lookup ───────────────────────────────────────
|
||||
|
||||
def get_top1(v4, sid):
|
||||
sec = v4.get('mdx_sections', {}).get(sid)
|
||||
if not sec:
|
||||
return None
|
||||
j = sec.get('judgments_full32', [])
|
||||
return j[0] if j else None
|
||||
|
||||
|
||||
def get_frame_judgment(v4, sid, frame_number):
|
||||
sec = v4.get('mdx_sections', {}).get(sid)
|
||||
if not sec:
|
||||
return None
|
||||
for e in sec.get('judgments_full32', []):
|
||||
if e['frame_number'] == frame_number:
|
||||
return e
|
||||
return None
|
||||
|
||||
|
||||
# ─── 메인 ──────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
if not MDX_PATH.exists():
|
||||
print(f"ERROR: MDX 04 not found at {MDX_PATH}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not V4_RESULT.exists():
|
||||
print(f"ERROR: V4 result not found at {V4_RESULT}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
mdx_text = MDX_PATH.read_text(encoding='utf-8')
|
||||
v4 = yaml.safe_load(V4_RESULT.read_text(encoding='utf-8'))
|
||||
|
||||
title_2_1, items_2_1 = extract_subsection_items(mdx_text, '2.1')
|
||||
title_2_2, items_2_2 = extract_subsection_items(mdx_text, '2.2')
|
||||
title_1, cards_1 = extract_section_04_1_cards(mdx_text)
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(str(TEMPLATES_DIR)),
|
||||
autoescape=select_autoescape(['html', 'xml']),
|
||||
)
|
||||
f16_zonefit_tpl = env.get_template("bim_issues_quadrant_four_zonefit.html.j2")
|
||||
cards5_left_tpl = env.get_template("cards_5_left_zone.html.j2")
|
||||
slide_fit_tpl = env.get_template("slide_fit_base.html.j2")
|
||||
|
||||
# 04-2 통합 (그룹핑) → F16 zone-fit
|
||||
payload_f16 = map_to_f16_grouped(items_2_1, items_2_2, slide_title='DX 지연<br>요인')
|
||||
html_f16_zonefit = f16_zonefit_tpl.render(slot_payload=payload_f16)
|
||||
|
||||
# 04-1 → 5-card left zone
|
||||
payload_cards = map_to_5card_compact_slots(cards_1, section_title=title_1)
|
||||
html_cards_left = cards5_left_tpl.render(slot_payload=payload_cards)
|
||||
|
||||
# slide_fit base 조립 (1280×720)
|
||||
slide_fit_html = slide_fit_tpl.render(
|
||||
slide_title='4. DX 지연 요인',
|
||||
slide_meta='F16 user_semantic_override · slide_fit_preview',
|
||||
zone_left=html_cards_left,
|
||||
zone_right=html_f16_zonefit,
|
||||
slide_footer='검증 없는 정책의 일방적 추진과 조직의 회피, 이해 부족이 DX 지연을 반복시킨다',
|
||||
)
|
||||
|
||||
# 통합 1 슬라이드 페이지 (banner + slide_fit + metadata)
|
||||
timestamp = datetime.now().isoformat(timespec='seconds')
|
||||
page_html = f'''<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MDX04 1280×720 slide_fit · F16 user_semantic_override</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: 'Noto Sans KR', 'Pretendard', sans-serif; background: #e8ecf0; padding: 24px; }}
|
||||
.preview-banner {{ max-width: 1280px; margin: 0 auto 16px; background: #fff7ed; border: 2px solid #f59e0b; border-radius: 8px; padding: 12px 16px; font-size: 12px; color: #92400e; line-height: 1.6; }}
|
||||
.preview-banner strong {{ color: #78350f; }}
|
||||
.preview-banner ul {{ margin-top: 6px; padding-left: 18px; font-family: monospace; font-size: 11px; }}
|
||||
.slide-wrap {{ display: flex; justify-content: center; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="preview-banner">
|
||||
<strong>MDX04 slide_fit_preview · 1280×720 표준 (NOT a Phase Z final)</strong><br>
|
||||
composition_preview (1280×1230) → <strong>slide_fit_preview (1280×720)</strong> 전환.
|
||||
같은 grouping rule 유지 (04-2.* 8 항목 → 4 원인군). 04-2.2 보존. F16 native dim 폐기, zone-fit 적용.
|
||||
<ul>
|
||||
<li>layout = title (56) + body (1200×590, left 340 + right 840) + footer pill (48)</li>
|
||||
<li>zone-left = 04-1 compact 5-card stack</li>
|
||||
<li>zone-right = F16 quadrant zone-fit (4분면 + center quote)</li>
|
||||
<li>q1 = 정책 집행 / 제도 운용 (2.1×2) · q2 = 개념 이해 부족 (2.1×1 + 2.2×2)</li>
|
||||
<li>q3 = 기술 투자 / 본업 기술력 부족 (2.1×1 + 2.2×1) · q4 = 조직 / 수행 역량 (2.2×1)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="slide-wrap">
|
||||
{slide_fit_html}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
(RUN_DIR / "index.html").write_text(page_html, encoding='utf-8')
|
||||
|
||||
# 단독 slide_fit (banner 없이 슬라이드 자체만)
|
||||
standalone_slide = f'''<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><title>MDX04 1280×720 slide_fit (standalone)</title>
|
||||
<style>* {{margin:0;padding:0;box-sizing:border-box}} body {{font-family:'Noto Sans KR',sans-serif;background:#e8ecf0;padding:20px;display:flex;justify-content:center}}</style></head><body>
|
||||
{slide_fit_html}
|
||||
</body></html>
|
||||
'''
|
||||
(RUN_DIR / "slide_1280x720.html").write_text(standalone_slide, encoding='utf-8')
|
||||
|
||||
# debug.json
|
||||
top1_2_1 = get_top1(v4, '04-2.1')
|
||||
top1_2_2 = get_top1(v4, '04-2.2')
|
||||
top1_1 = get_top1(v4, '04-1')
|
||||
f16_2_1 = get_frame_judgment(v4, '04-2.1', 16)
|
||||
f16_2_2 = get_frame_judgment(v4, '04-2.2', 16)
|
||||
|
||||
# grouping coverage 검증 (모든 8 항목 사용됐는지)
|
||||
used = set()
|
||||
for g in GROUPING_RULE['groups']:
|
||||
for ref in g['items']:
|
||||
used.add((ref['source'], ref['index']))
|
||||
expected = set([('04-2.1', i) for i in range(len(items_2_1))]
|
||||
+ [('04-2.2', i) for i in range(len(items_2_2))])
|
||||
missing = sorted(expected - used)
|
||||
extra = sorted(used - expected)
|
||||
|
||||
debug = {
|
||||
'kind': 'mdx04_f16_override_slide_fit',
|
||||
'preview_stage': 'slide_fit_preview',
|
||||
'transition_from': 'composition_preview (1280×1230 비표준)',
|
||||
'transition_to': 'slide_fit_preview (1280×720 표준)',
|
||||
'transition_note': '같은 grouping rule 유지. F16 native height (1015px) 폐기. zone-fit 좌표 재계산. 폰트 축소.',
|
||||
'is_phase_z_final': False,
|
||||
'is_diagnostic': True,
|
||||
'is_preview_or_result_candidate': True,
|
||||
'generated_at': timestamp,
|
||||
'v4_source': str(V4_RESULT.relative_to(ROOT)),
|
||||
'mdx_source': str(MDX_PATH.relative_to(ROOT)),
|
||||
'integrated_slide': True,
|
||||
'layout': {
|
||||
'slide_dimensions': '1280×720',
|
||||
'title_bar_height': 56,
|
||||
'body': {'width': 1200, 'height': 590, 'left_zone': 340, 'right_zone': 840, 'gap': 20},
|
||||
'footer_pill_height': 48,
|
||||
'zone_left': '04-1 compact 5-card stack (frame library gap)',
|
||||
'zone_right': '04-2 통합 F16 quadrant zone-fit (grouped)',
|
||||
'mdx_one_slide_principle': True,
|
||||
'standard_16_9': True,
|
||||
},
|
||||
'override_decision': {
|
||||
'selected_frame_source': 'user_semantic_override',
|
||||
'selected_frame': 'F16',
|
||||
'selected_template_id': 'bim_issues_quadrant_four',
|
||||
'reason': 'F16 quadrant pattern semantically/visually appropriate for MDX04 04-2.* '
|
||||
'(four-issue diagnostic structure). V4 top1 F26 figma 변환 부재 + semantic '
|
||||
'review 에서 F16 가 더 적합 판단.',
|
||||
},
|
||||
'grouping_rule': GROUPING_RULE,
|
||||
'grouping_coverage': {
|
||||
'total_items': len(items_2_1) + len(items_2_2),
|
||||
'mapped_items': len(used),
|
||||
'missing': [{'source': s, 'index': i} for s, i in missing],
|
||||
'extra': [{'source': s, 'index': i} for s, i in extra],
|
||||
'all_items_preserved': not missing,
|
||||
},
|
||||
'sections': {
|
||||
'04-2.1': {
|
||||
'mdx_title': title_2_1,
|
||||
'item_count': len(items_2_1),
|
||||
'v4_top1': {
|
||||
'frame_number': top1_2_1['frame_number'],
|
||||
'template_id': top1_2_1['template_id'],
|
||||
'label': top1_2_1['label'],
|
||||
'confidence': top1_2_1['confidence'],
|
||||
},
|
||||
'selected_frame': 16,
|
||||
'original_label': f16_2_1['label'] if f16_2_1 else None,
|
||||
'original_confidence': f16_2_1['confidence'] if f16_2_1 else None,
|
||||
},
|
||||
'04-2.2': {
|
||||
'mdx_title': title_2_2,
|
||||
'item_count': len(items_2_2),
|
||||
'v4_top1': {
|
||||
'frame_number': top1_2_2['frame_number'],
|
||||
'template_id': top1_2_2['template_id'],
|
||||
'label': top1_2_2['label'],
|
||||
'confidence': top1_2_2['confidence'],
|
||||
},
|
||||
'selected_frame': 16,
|
||||
'original_label': f16_2_2['label'] if f16_2_2 else None,
|
||||
'original_confidence': f16_2_2['confidence'] if f16_2_2 else None,
|
||||
'preserved_in_grouping': True,
|
||||
},
|
||||
'04-1': {
|
||||
'mdx_title': title_1,
|
||||
'card_count': len(cards_1),
|
||||
'v4_top1': {
|
||||
'frame_number': top1_1['frame_number'],
|
||||
'template_id': top1_1['template_id'],
|
||||
'label': top1_1['label'],
|
||||
'confidence': top1_1['confidence'],
|
||||
} if top1_1 else None,
|
||||
'selected_frame': None,
|
||||
'override_note': '5-card library gap (32 frame DB 에 cardinality.ideal=5 frame 부재). '
|
||||
'compact 5-column grid 로 통합 슬라이드 상단에 배치.',
|
||||
},
|
||||
},
|
||||
'caveats': [
|
||||
'정식 Phase Z final 아님 — V4 lookup 우회',
|
||||
'preview_stage = slide_fit_preview (1280×720 표준). 이전 composition_preview (1280×1230) 에서 전환',
|
||||
'F16 partial template = preview 전용 (data/runs/mdx04_f16_override/templates/) — design_agent/templates/phase_z2 미수정',
|
||||
'anchor 보정 / detect_mdx 수정 / v4_full32_result.yaml 변경 없음',
|
||||
'04-2.1 의 F16 original_label = reject (anchor=0). override 로 진행',
|
||||
'04-2.2 의 F16 original_label = restructure (사용 가능 라벨)',
|
||||
'04-2.2 보존 — 그룹핑으로 8 항목 모두 분면에 매핑',
|
||||
'04-1 = 5-card library gap. zone-left 에 compact stack 으로 배치',
|
||||
'그룹핑 룰은 사용자 semantic 결정 (yaml/dict 로 명시). 자동 생성 아님',
|
||||
'F16 native dim (1280×1015) 폐기 — zone (840×590) 에 맞춰 좌표 재계산. 폰트 14px(ribbon)/11.5px(headline)/9.5px(sub)',
|
||||
'slide-fit 으로 폰트 작아짐 → 가독성 trade-off. composition_preview 와 비교 필요',
|
||||
],
|
||||
}
|
||||
(RUN_DIR / "debug.json").write_text(
|
||||
json.dumps(debug, ensure_ascii=False, indent=2), encoding='utf-8',
|
||||
)
|
||||
|
||||
# 이전 composition_preview 산출물 정리 — slide_fit_preview 로 대체
|
||||
for old in ["slide_04-2.1.html", "slide_04-2.2.html", "slide_04-1.html",
|
||||
"slide_04-2_grouped.html", "slide_04-1_compact.html"]:
|
||||
p = RUN_DIR / old
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
|
||||
print(f"[mdx04_f16_override_slide_fit] generated:")
|
||||
print(f" index : {RUN_DIR / 'index.html'}")
|
||||
print(f" slide 1280×720 : {RUN_DIR / 'slide_1280x720.html'}")
|
||||
print(f" debug : {RUN_DIR / 'debug.json'}")
|
||||
print()
|
||||
print(f"Coverage: {len(used)}/{len(expected)} items mapped, missing={list(missing)}")
|
||||
print(f"Stage: composition_preview → slide_fit_preview")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
404
scripts/previews/mdx04_partial_preview.py
Normal file
404
scripts/previews/mdx04_partial_preview.py
Normal file
@@ -0,0 +1,404 @@
|
||||
"""MDX04 partial preview — diagnostic only, NOT a Phase Z final.
|
||||
|
||||
목적: F16 (`bim_issues_quadrant_four`) 가 04-2.1 / 04-2.2 의 4 항목 구조와
|
||||
시각적으로 정합하는지 사용자가 눈으로 확인.
|
||||
|
||||
방식:
|
||||
- V4 runtime 우회 (정식 Phase Z 아님)
|
||||
- F16 figma 원본 HTML 을 iframe 으로 임베드 (디자인 형태 그대로)
|
||||
- 04-2.1 / 04-2.2 의 MDX 4 항목을 옆에 시각화 (구조 비교)
|
||||
- 04-1 = frame library gap (5-card 구조, 매칭 frame 부재) placeholder
|
||||
- diagnostic banner + V4 metadata + debug.json
|
||||
|
||||
출력:
|
||||
data/runs/mdx04_partial_preview/index.html
|
||||
data/runs/mdx04_partial_preview/debug.json
|
||||
data/runs/mdx04_partial_preview/f16_original/ (figma 원본 + assets)
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MDX_PATH = ROOT / "samples" / "mdx_batch" / "04.mdx"
|
||||
V4_RESULT = ROOT / "tests" / "matching" / "v4_full32_result.yaml"
|
||||
RUN_DIR = ROOT / "data" / "runs" / "mdx04_partial_preview"
|
||||
|
||||
|
||||
# ─── MDX 04 의 04-2.1 / 04-2.2 섹션 추출 (### bullet) ──────────────
|
||||
|
||||
RE_SUBSECTION_HEAD = re.compile(r'^###\s+(\d+\.\d+)\s+(.+)$', re.MULTILINE)
|
||||
RE_TOP_BULLET = re.compile(r'^-\s+\*\*([^*]+)\*\*\s*$')
|
||||
|
||||
|
||||
def extract_subsection(text, num_label):
|
||||
"""### {num_label} ... 부터 다음 ### 또는 --- 직전까지 추출."""
|
||||
lines = text.split('\n')
|
||||
start = None
|
||||
for i, ln in enumerate(lines):
|
||||
m = RE_SUBSECTION_HEAD.match(ln.strip())
|
||||
if m and m.group(1) == num_label:
|
||||
start = i
|
||||
break
|
||||
if start is None:
|
||||
return None, []
|
||||
end = len(lines)
|
||||
for j in range(start + 1, len(lines)):
|
||||
s = lines[j].strip()
|
||||
if RE_SUBSECTION_HEAD.match(s) or s == '---':
|
||||
end = j
|
||||
break
|
||||
section_title = lines[start].lstrip('# ').strip()
|
||||
body_lines = lines[start + 1:end]
|
||||
|
||||
# 4 항목 추출 (top bullet + nested bullets)
|
||||
items = []
|
||||
cur = None
|
||||
for ln in body_lines:
|
||||
stripped = ln.strip()
|
||||
m = RE_TOP_BULLET.match(stripped)
|
||||
if m:
|
||||
if cur is not None:
|
||||
items.append(cur)
|
||||
cur = {'headline': m.group(1).strip(), 'subs': []}
|
||||
continue
|
||||
m2 = re.match(r'^-\s+(.+)$', stripped)
|
||||
if m2 and cur is not None and not stripped.startswith('- **'):
|
||||
cur['subs'].append(m2.group(1).strip())
|
||||
if cur is not None:
|
||||
items.append(cur)
|
||||
return section_title, items
|
||||
|
||||
|
||||
def extract_section_04_1(text):
|
||||
"""04-1 = ## 1. DX에 대한 인식. <h3> 카드 5 개 + 각 카드 안 인용 + bullet 3 개."""
|
||||
lines = text.split('\n')
|
||||
start = None
|
||||
for i, ln in enumerate(lines):
|
||||
if ln.strip() == '## 1. DX에 대한 인식':
|
||||
start = i
|
||||
break
|
||||
if start is None:
|
||||
return None, []
|
||||
end = len(lines)
|
||||
for j in range(start + 1, len(lines)):
|
||||
s = lines[j].strip()
|
||||
if s.startswith('## ') and s != '## 1. DX에 대한 인식':
|
||||
end = j
|
||||
break
|
||||
|
||||
body = '\n'.join(lines[start:end])
|
||||
|
||||
# <h3> 라벨 + 다음 <p> 인용 + <ul><li> bullet 3 개
|
||||
cards = []
|
||||
for m in re.finditer(r'<h3[^>]*>([^<]+)</h3>', body):
|
||||
cards.append({'label': m.group(1).strip()})
|
||||
|
||||
return lines[start].lstrip('# ').strip(), cards
|
||||
|
||||
|
||||
# ─── V4 metadata lookup ──────────────────────────────────────────
|
||||
|
||||
def get_f16_judgment(v4, section_id):
|
||||
sec = v4['mdx_sections'].get(section_id)
|
||||
if not sec:
|
||||
return None
|
||||
for e in sec['judgments_full32']:
|
||||
if e['frame_number'] == 16:
|
||||
return e
|
||||
return None
|
||||
|
||||
|
||||
def get_top1(v4, section_id):
|
||||
sec = v4['mdx_sections'].get(section_id)
|
||||
if not sec:
|
||||
return None
|
||||
j = sec.get('judgments_full32', [])
|
||||
return j[0] if j else None
|
||||
|
||||
|
||||
# ─── HTML 렌더링 ─────────────────────────────────────────────────
|
||||
|
||||
def render_items_html(items):
|
||||
parts = ['<div class="items-list">']
|
||||
for i, it in enumerate(items, 1):
|
||||
parts.append('<div class="item">')
|
||||
parts.append(f'<div class="item-headline">{i}. {escape(it["headline"])}</div>')
|
||||
if it['subs']:
|
||||
parts.append('<ul class="item-subs">')
|
||||
for s in it['subs']:
|
||||
parts.append(f'<li>{escape(s)}</li>')
|
||||
parts.append('</ul>')
|
||||
parts.append('</div>')
|
||||
parts.append('</div>')
|
||||
return '\n'.join(parts)
|
||||
|
||||
|
||||
def render_cards_html(cards):
|
||||
parts = ['<div class="cards-list">']
|
||||
for i, c in enumerate(cards, 1):
|
||||
parts.append(f'<div class="card">{i}. {escape(c["label"])}</div>')
|
||||
parts.append('</div>')
|
||||
return '\n'.join(parts)
|
||||
|
||||
|
||||
def render_v4_metadata_html(j, label_note=''):
|
||||
if j is None:
|
||||
return '<div class="v4-meta v4-meta-missing">V4 entry not found</div>'
|
||||
axes = j.get('axes', {})
|
||||
return f'''<div class="v4-meta">
|
||||
<div class="v4-meta-row">
|
||||
<span class="v4-meta-key">V4 rank:</span><span class="v4-meta-val">{j["v4_full_rank"]}</span>
|
||||
<span class="v4-meta-key">conf:</span><span class="v4-meta-val">{j["confidence"]:.4f}</span>
|
||||
<span class="v4-meta-key">label:</span><span class="v4-meta-val v4-label-{j["label"]}">{j["label"]}</span>
|
||||
</div>
|
||||
<div class="v4-meta-row v4-axes">
|
||||
<span class="v4-meta-key">axes:</span>
|
||||
anchor={axes.get("anchor", 0):.2f} ·
|
||||
cardinality={axes.get("cardinality", 0):.2f} ·
|
||||
relation={axes.get("relation", 0):.2f} ·
|
||||
slot={axes.get("slot", 0):.2f} ·
|
||||
content={axes.get("content", 0):.4f}
|
||||
</div>
|
||||
{f'<div class="v4-meta-row v4-note">{label_note}</div>' if label_note else ''}
|
||||
</div>'''
|
||||
|
||||
|
||||
def render_section(section_id, mdx_title, items_html, j_f16, top1, note):
|
||||
"""좌: F16 figma 원본 iframe / 우: MDX 텍스트 4 항목 / 하: V4 metadata."""
|
||||
label_note = note
|
||||
return f'''<section class="preview-section" id="sec-{section_id}">
|
||||
<header class="section-head">
|
||||
<h2>{escape(section_id)} · {escape(mdx_title)}</h2>
|
||||
<div class="section-sub">
|
||||
F16 (bim_issues_quadrant_four) candidate · top1 = F{top1["frame_number"]} ({top1["label"]}, conf {top1["confidence"]:.4f})
|
||||
</div>
|
||||
</header>
|
||||
<div class="section-body">
|
||||
<div class="col col-figma">
|
||||
<div class="col-label">F16 figma 원본 (디자인 형태)</div>
|
||||
<div class="iframe-frame">
|
||||
<iframe src="f16_original/index.html" frameborder="0" scrolling="no"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-mdx">
|
||||
<div class="col-label">MDX 04 {escape(section_id)} 본문 (4 항목)</div>
|
||||
{items_html}
|
||||
</div>
|
||||
</div>
|
||||
{render_v4_metadata_html(j_f16, label_note)}
|
||||
</section>'''
|
||||
|
||||
|
||||
def render_04_1_placeholder(top1):
|
||||
return f'''<section class="preview-section preview-gap" id="sec-04-1">
|
||||
<header class="section-head">
|
||||
<h2>04-1 · DX에 대한 인식</h2>
|
||||
<div class="section-sub">
|
||||
Frame library gap — 5-card 구조, 32 frame DB 에 cardinality.ideal=5 frame 부재 (이번 preview 제외)
|
||||
</div>
|
||||
</header>
|
||||
<div class="gap-note">
|
||||
<strong>왜 제외</strong>: 04-1 은 5 개 카드 (기술/효과/인력/경제/실무) — h3_cards=5 인식까지는 정상. 다만 32 frame
|
||||
중 5-card 대응 frame 이 없어 V4 multi-constraint 통과 가능 frame 자체가 없음 (사용 가능 0/32, 모두 reject).
|
||||
이건 detect bug 가 아니라 <strong>frame library readiness 문제</strong>.
|
||||
<br><br>
|
||||
V4 top1 = F{top1["frame_number"]} (conf {top1["confidence"]:.4f}, {top1["label"]}) — F16 도 rank 15, conf 0.361, reject.
|
||||
</div>
|
||||
</section>'''
|
||||
|
||||
|
||||
# ─── 메인 ────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
if not V4_RESULT.exists():
|
||||
print(f"ERROR: V4 result not found at {V4_RESULT}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not MDX_PATH.exists():
|
||||
print(f"ERROR: MDX 04 not found at {MDX_PATH}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
mdx_text = MDX_PATH.read_text(encoding='utf-8')
|
||||
v4 = yaml.safe_load(V4_RESULT.read_text(encoding='utf-8'))
|
||||
|
||||
# 04-2.1
|
||||
title_2_1, items_2_1 = extract_subsection(mdx_text, '2.1')
|
||||
j16_2_1 = get_f16_judgment(v4, '04-2.1')
|
||||
top1_2_1 = get_top1(v4, '04-2.1')
|
||||
|
||||
# 04-2.2
|
||||
title_2_2, items_2_2 = extract_subsection(mdx_text, '2.2')
|
||||
j16_2_2 = get_f16_judgment(v4, '04-2.2')
|
||||
top1_2_2 = get_top1(v4, '04-2.2')
|
||||
|
||||
# 04-1
|
||||
title_1, cards_1 = extract_section_04_1(mdx_text)
|
||||
top1_1 = get_top1(v4, '04-1')
|
||||
|
||||
# HTML 조립
|
||||
section_2_1_html = render_section(
|
||||
'04-2.1', title_2_1,
|
||||
render_items_html(items_2_1),
|
||||
j16_2_1, top1_2_1,
|
||||
note='F16 candidate — V4 label=reject (anchor=0). 의미 매칭 회복했으나 anchor terms 부재로 multi-constraint 탈락. preview 목적 = F16 디자인 / 04-2.1 본문 정합성 시각 확인.'
|
||||
)
|
||||
section_2_2_html = render_section(
|
||||
'04-2.2', title_2_2,
|
||||
render_items_html(items_2_2),
|
||||
j16_2_2, top1_2_2,
|
||||
note='F16 restructure — V4 label=restructure 통과 (사용 가능 라벨). preview 목적 = F16 디자인이 04-2.2 본문에 시각적으로 fit 한지 확인.'
|
||||
)
|
||||
section_1_html = render_04_1_placeholder(top1_1)
|
||||
|
||||
timestamp = datetime.now().isoformat(timespec='seconds')
|
||||
page_html = f'''<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MDX04 Partial Preview · diagnostic only</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{
|
||||
font-family: -apple-system, "Pretendard", "Apple SD Gothic Neo", sans-serif;
|
||||
background: #f5f6f8; color: #111; line-height: 1.5;
|
||||
padding: 24px;
|
||||
}}
|
||||
.banner {{
|
||||
background: #fff7ed; border: 2px solid #f59e0b; border-radius: 8px;
|
||||
padding: 16px 20px; margin: 0 auto 24px; max-width: 1400px;
|
||||
}}
|
||||
.banner h1 {{ font-size: 18px; color: #92400e; margin-bottom: 6px; }}
|
||||
.banner p {{ font-size: 13px; color: #78350f; }}
|
||||
.banner .timestamp {{ font-size: 11px; color: #b45309; margin-top: 8px; font-family: monospace; }}
|
||||
|
||||
.preview-section {{
|
||||
max-width: 1400px; margin: 0 auto 32px;
|
||||
background: #fff; border: 1px solid #d1d5db; border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}}
|
||||
.section-head {{ padding: 16px 20px; border-bottom: 1px solid #e5e7eb; background: #f9fafb; }}
|
||||
.section-head h2 {{ font-size: 18px; color: #111827; margin-bottom: 4px; }}
|
||||
.section-sub {{ font-size: 13px; color: #6b7280; }}
|
||||
|
||||
.section-body {{
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 0;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}}
|
||||
.col {{ padding: 16px 20px; }}
|
||||
.col-figma {{ border-right: 1px solid #e5e7eb; background: #fafafa; }}
|
||||
.col-label {{
|
||||
font-size: 12px; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px;
|
||||
margin-bottom: 12px; font-weight: 600;
|
||||
}}
|
||||
|
||||
.iframe-frame {{
|
||||
width: 100%; height: 380px;
|
||||
background: #fff; border: 1px solid #d1d5db; border-radius: 4px;
|
||||
overflow: hidden; position: relative;
|
||||
}}
|
||||
.iframe-frame iframe {{
|
||||
width: 1280px; height: 720px;
|
||||
transform: scale(0.5); transform-origin: top left;
|
||||
}}
|
||||
|
||||
.items-list {{ display: flex; flex-direction: column; gap: 14px; }}
|
||||
.item {{ padding: 12px 14px; background: #f3f4f6; border-left: 3px solid #2563eb; border-radius: 4px; }}
|
||||
.item-headline {{ font-weight: 700; color: #111; font-size: 14px; margin-bottom: 6px; }}
|
||||
.item-subs {{ list-style: disc; padding-left: 20px; font-size: 13px; color: #374151; }}
|
||||
.item-subs li {{ margin-bottom: 3px; }}
|
||||
|
||||
.cards-list {{ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }}
|
||||
.card {{ padding: 10px 12px; background: #f3f4f6; border-radius: 4px; font-size: 13px; }}
|
||||
|
||||
.v4-meta {{ padding: 12px 20px; background: #f9fafb; font-family: monospace; font-size: 12px; color: #374151; }}
|
||||
.v4-meta-row {{ margin-bottom: 4px; }}
|
||||
.v4-meta-key {{ color: #6b7280; margin-right: 4px; }}
|
||||
.v4-meta-val {{ color: #111; margin-right: 12px; font-weight: 600; }}
|
||||
.v4-axes {{ font-size: 11px; color: #6b7280; }}
|
||||
.v4-note {{ font-size: 12px; color: #6b7280; margin-top: 6px; line-height: 1.5; font-family: inherit; }}
|
||||
.v4-label-use_as_is {{ color: #059669; }}
|
||||
.v4-label-light_edit {{ color: #2563eb; }}
|
||||
.v4-label-restructure {{ color: #d97706; }}
|
||||
.v4-label-reject {{ color: #dc2626; }}
|
||||
|
||||
.preview-gap {{ background: #fef2f2; }}
|
||||
.preview-gap .section-head {{ background: #fee2e2; border-bottom-color: #fecaca; }}
|
||||
.gap-note {{ padding: 16px 20px; font-size: 13px; color: #7f1d1d; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="banner">
|
||||
<h1>MDX04 Partial Preview · diagnostic only</h1>
|
||||
<p>이 출력은 정식 Phase Z final 이 아닙니다. F16 (`bim_issues_quadrant_four`) 가 04-2.1 / 04-2.2 의 4 항목 구조와
|
||||
시각적으로 정합하는지 확인하기 위한 진단용 preview 입니다. V4 runtime / mapper / partial 우회.
|
||||
04-1 은 frame library gap 으로 이번 preview 제외.</p>
|
||||
<div class="timestamp">generated: {timestamp}</div>
|
||||
</div>
|
||||
|
||||
{section_2_1_html}
|
||||
|
||||
{section_2_2_html}
|
||||
|
||||
{section_1_html}
|
||||
|
||||
</body>
|
||||
</html>'''
|
||||
|
||||
out_html = RUN_DIR / "index.html"
|
||||
out_html.write_text(page_html, encoding='utf-8')
|
||||
|
||||
debug = {
|
||||
'kind': 'mdx04_partial_preview',
|
||||
'is_phase_z_final': False,
|
||||
'is_diagnostic': True,
|
||||
'purpose': 'F16 디자인 / 04-2.* 4 항목 구조 시각 정합성 확인',
|
||||
'generated_at': timestamp,
|
||||
'v4_source': str(V4_RESULT.relative_to(ROOT)),
|
||||
'mdx_source': str(MDX_PATH.relative_to(ROOT)),
|
||||
'sections': {
|
||||
'04-2.1': {
|
||||
'mdx_title': title_2_1,
|
||||
'item_count': len(items_2_1),
|
||||
'top1': top1_2_1,
|
||||
'f16_judgment': j16_2_1,
|
||||
'preview_label': 'F16 candidate (V4 label = reject, conf 0.648, anchor=0)',
|
||||
},
|
||||
'04-2.2': {
|
||||
'mdx_title': title_2_2,
|
||||
'item_count': len(items_2_2),
|
||||
'top1': top1_2_2,
|
||||
'f16_judgment': j16_2_2,
|
||||
'preview_label': 'F16 restructure (V4 label = restructure, 사용 가능 통과)',
|
||||
},
|
||||
'04-1': {
|
||||
'mdx_title': title_1,
|
||||
'card_count': len(cards_1),
|
||||
'top1': top1_1,
|
||||
'preview_label': 'EXCLUDED — frame library gap (5-card structure, no matching frame in 32 DB)',
|
||||
},
|
||||
},
|
||||
'caveats': [
|
||||
'정식 Phase Z final 아님 — V4 runtime / mapper / partial 모두 우회',
|
||||
'F16 figma 원본 HTML 을 그대로 임베드 — 디자인 형태만 시각화 (텍스트 슬롯 매핑 X)',
|
||||
'04-2.1 의 F16 V4 label = reject (anchor=0) — 의미 매칭 회복했으나 anchor terms 부재',
|
||||
'04-2.2 의 F16 V4 label = restructure — 사용 가능 라벨, 단 정식 partial 미작성',
|
||||
'04-1 = frame library readiness 문제 (detect bug 아님)',
|
||||
],
|
||||
}
|
||||
out_debug = RUN_DIR / "debug.json"
|
||||
out_debug.write_text(json.dumps(debug, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
|
||||
print(f"[mdx04_partial_preview] generated:")
|
||||
print(f" html : {out_html}")
|
||||
print(f" debug : {out_debug}")
|
||||
print(f" figma : {RUN_DIR / 'f16_original' / 'index.html'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user