Files
C.E.L_Slide_test2/tests/matching/pipeline_08_v3_structure_rerank.py
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

168 lines
6.1 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.
"""Pipeline Step 8 — V3: MDX 구조 × Figma 구조 rerank
파이프라인 위치:
V1 → V2 → **V3 (이 스크립트)** → V4
설계 원칙:
- V2 결과(v2_semantic_rerank_result.yaml)의 Top-K 를 그대로 사용.
- MDX 구조는 코드가 파악 (phase_common.detect_mdx_layout_v2).
- Figma 구조는 AI가 이미 만들어 놓은 산출물(structure_ontology.yaml.templates_v1) 재사용.
- Figma 구조 라벨은 `source.original_layout` 사용 (phase_common._COMPAT 키 체계와 일치).
- 구조 호환도(_COMPAT) 로 재정렬. 가중합 아님 — 순수 rerank.
- V3 에서 새 AI 패스 없음.
입력:
- v2_semantic_rerank_result.yaml
- MDX_SECTIONS 원문
- structure_ontology.yaml (templates_v1)
출력:
- v3_structure_rerank_result.yaml
"""
import hashlib
import sys
import datetime
from pathlib import Path
import yaml
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from phase_common import detect_mdx_layout_v2, structural_match_v2
from pipeline_01_extract_nodes import MDX_SECTIONS, MDX_DIR
LOCK_SNAPSHOT_FILES = [
'pipeline_08_v3_structure_rerank.py',
'pipeline_08_v2_semantic_rerank.py',
'phase_common.py',
'structure_ontology.yaml',
'v2_semantic_rerank_result.yaml',
]
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def extract_mdx_raw(section_id: str) -> str:
cfg = MDX_SECTIONS[section_id]
p = MDX_DIR / cfg['file']
lines = p.read_text(encoding='utf-8').split('\n')
start_idx = None
for i, ln in enumerate(lines):
if ln.strip() == cfg['start'].strip():
start_idx = i
break
if start_idx is None:
raise ValueError(f"start_heading 찾지 못함: {cfg['start']!r}")
end_idx = len(lines)
if cfg.get('end_prefix'):
for i in range(start_idx + 1, len(lines)):
if lines[i].strip().startswith(cfg['end_prefix']):
end_idx = i
break
return '\n'.join(lines[start_idx:end_idx])
def main():
v2 = yaml.safe_load((HERE / 'v2_semantic_rerank_result.yaml').read_text(encoding='utf-8'))
ontology = yaml.safe_load((HERE / 'structure_ontology.yaml').read_text(encoding='utf-8'))
templates_v1 = ontology['templates_v1']
out_sections = {}
for sid, v2_sec in v2['mdx_sections'].items():
raw = extract_mdx_raw(sid)
mdx_layout = detect_mdx_layout_v2(raw)
rerank = []
for cand in v2_sec['v2_rerank']:
fid = cand['frame_id']
tpl = templates_v1.get(fid, {})
# Figma 구조 라벨: source.original_layout 우선, 없으면 visual_pattern.layout
fig_layout = None
if tpl:
fig_layout = tpl.get('source', {}).get('original_layout')
if not fig_layout:
fig_layout = tpl.get('visual_pattern', {}).get('layout')
compat = structural_match_v2(mdx_layout, fig_layout) if fig_layout else 0.0
rerank.append({
'frame_id': fid,
'frame_number': cand['frame_number'],
'v1_rank': cand['v1_rank'],
'v2_rank': cand['v2_rank'],
'v1_score': cand['v1_score'],
'semantic_score': cand['semantic_score'],
'fig_layout': fig_layout,
'structure_compat': round(float(compat), 4),
})
# 재정렬: structure_compat 내림차순, tie-break 은 V2 rank 오름차순
rerank.sort(key=lambda x: (-x['structure_compat'], x['v2_rank']))
for new_rank, item in enumerate(rerank, start=1):
item['v3_rank'] = new_rank
out_sections[sid] = {
'section_type': v2_sec.get('section_type'),
'answer_frame_number': v2_sec.get('answer_frame_number'),
'mdx_title': v2_sec['mdx_title'],
'mdx_layout': mdx_layout,
'v3_rerank': rerank,
}
lock = {
'timestamp': datetime.datetime.now().isoformat(timespec='seconds'),
'top_k': v2['meta']['top_k'],
'figma_structure_source': 'structure_ontology.yaml::templates_v1.<fid>.source.original_layout',
'mdx_structure_source': 'phase_common.detect_mdx_layout_v2',
'compat_table': 'phase_common._COMPAT (legacy)',
'files': {name: sha256_file(HERE / name) for name in LOCK_SNAPSHOT_FILES},
'principle': [
'V2 후보 집합 유지 — Top-K 내에서만 재정렬',
'MDX 구조는 코드 추출, Figma 구조는 기존 AI 산출 재사용',
'Holdout 성적을 설계 근거로 사용하지 않음',
],
}
out = {
'meta': {
'pipeline_step': '8.v3',
'description': 'V2 후보를 MDX 구조 × Figma 구조 호환도로 재정렬 (캐스케이드 rerank 2단)',
'top_k': v2['meta']['top_k'],
'holdout_sections': v2['meta']['holdout_sections'],
'answer_map': v2['meta']['answer_map'],
'lock_snapshot': lock,
},
'mdx_sections': out_sections,
}
out_path = HERE / 'v3_structure_rerank_result.yaml'
out_path.write_text(
yaml.safe_dump(out, allow_unicode=True, sort_keys=False, width=1000),
encoding='utf-8',
)
print("=" * 70)
print(f"V3 재정렬 완료: {out_path}")
print("=" * 70)
answer_map = v2['meta']['answer_map']
for sid, s in out_sections.items():
v2_top1 = next(r for r in s['v3_rerank'] if r['v2_rank'] == 1)['frame_number']
v3_top1 = s['v3_rerank'][0]['frame_number']
v3_top1_compat = s['v3_rerank'][0]['structure_compat']
mdx_lo = s['mdx_layout']
fig_lo = s['v3_rerank'][0]['fig_layout']
answer_num = answer_map.get(sid)
mark = ''
if answer_num is not None:
v3_ok = '✓' if v3_top1 == answer_num else '✗'
mark = f" 정답={answer_num} V3{v3_ok}"
else:
mark = " (holdout)"
print(f" [{sid:8}] MDX={mdx_lo:14} V2 top1=Frame {v2_top1:>2} → V3 top1=Frame {v3_top1:>2} (Fig={fig_lo}, compat={v3_top1_compat}){mark}")
print()
if __name__ == '__main__':
main()