- 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>
146 lines
5.6 KiB
Python
146 lines
5.6 KiB
Python
"""Pipeline Step 17 — V4 (template-fit) 를 V3 Top-K 가 아니라 32 프레임 전체에 적용.
|
|
|
|
기존 V4 r2 는 V3 Top-5 만 평가 → 5 개 중 선택.
|
|
이 스크립트는 32 프레임 전체에 template-fit 적용 → confidence 기준 전체 랭킹.
|
|
|
|
V1/V2/V3 처럼 "32 중 Top-3" 형태 산출.
|
|
|
|
출력: v4_full32_result.yaml
|
|
"""
|
|
import datetime
|
|
import sys
|
|
from pathlib import Path
|
|
import yaml
|
|
|
|
HERE = Path(__file__).parent
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
from phase_common import load_32_frames, load_frame_index
|
|
from detect_mdx import detect_mdx_analysis
|
|
from template_fit import (
|
|
load_templates_v1, collect_anchor_vocab, compute_template_fit, route,
|
|
)
|
|
from embeddings import embed_texts, cosine
|
|
from pipeline_01_extract_nodes import MDX_SECTIONS, MDX_DIR
|
|
|
|
OUT_PATH = HERE / 'v4_full32_result.yaml'
|
|
|
|
|
|
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
|
|
section = lines[start:end]
|
|
return section[0].lstrip('#').strip(), '\n'.join(section)
|
|
|
|
|
|
def main():
|
|
v1 = yaml.safe_load((HERE / 'mdx_matching_result.yaml').read_text(encoding='utf-8'))
|
|
answer_map = v1['meta']['answer_map']
|
|
holdout = v1['meta']['holdout_sections']
|
|
|
|
templates = load_templates_v1()
|
|
anchor_vocab = collect_anchor_vocab(templates)
|
|
frames = load_32_frames()
|
|
idx_data, frame_to_short = load_frame_index()
|
|
fids = list(frames.keys())
|
|
frame_num_map = {fid: int(frame_to_short[fid]) for fid in fids}
|
|
frame_contents = [frames[fid].get('content', '') for fid in fids]
|
|
|
|
print('[V4 full-32] 32 frame content 임베딩 중...')
|
|
frame_vecs = embed_texts(frame_contents)
|
|
|
|
out_sections = {}
|
|
for sid in v1['mdx_sections']:
|
|
title, raw_text = extract_mdx_raw(sid)
|
|
mdx_analysis = detect_mdx_analysis(raw_text, title, anchor_vocab=anchor_vocab)
|
|
mdx_summary = mdx_analysis['summary']
|
|
mdx_vec = embed_texts([mdx_summary])[0]
|
|
|
|
judgments = []
|
|
for i, fid in enumerate(fids):
|
|
if fid not in templates:
|
|
continue
|
|
template = templates[fid]
|
|
content_emb = max(0.0, min(1.0, float(cosine(mdx_vec, frame_vecs[i]))))
|
|
fit = compute_template_fit(mdx_analysis, template, content_emb)
|
|
label = route(fit['confidence'], fit['axes'], fit['adaptation'], fit['not_suits'])
|
|
judgments.append({
|
|
'frame_id': fid,
|
|
'frame_number': frame_num_map[fid],
|
|
'template_id': template.get('template_id'),
|
|
'confidence': round(float(fit['confidence']), 4),
|
|
'base': round(float(fit['base']), 4),
|
|
'penalty': round(float(fit['total_penalty']), 4),
|
|
'label': label,
|
|
'content_embedding': round(content_emb, 4),
|
|
'axes': {
|
|
'anchor': round(float(fit['axes']['anchor']['score']), 4),
|
|
'cardinality': round(float(fit['axes']['cardinality']), 4),
|
|
'relation': round(float(fit['axes']['relation']), 4),
|
|
'slot': round(float(fit['axes']['slot']), 4),
|
|
'content': round(float(fit['axes']['content']), 4),
|
|
},
|
|
})
|
|
|
|
# confidence 내림차순
|
|
judgments.sort(key=lambda x: -x['confidence'])
|
|
for new_rank, item in enumerate(judgments, start=1):
|
|
item['v4_full_rank'] = new_rank
|
|
|
|
ans = answer_map.get(sid)
|
|
out_sections[sid] = {
|
|
'mdx_title': title,
|
|
'answer_frame_number': ans,
|
|
'is_holdout': sid in holdout,
|
|
'judgments_full32': judgments,
|
|
'usable_count': sum(1 for j in judgments if j['label'] != 'reject'),
|
|
'reject_count': sum(1 for j in judgments if j['label'] == 'reject'),
|
|
}
|
|
|
|
out = {
|
|
'meta': {
|
|
'pipeline_step': '8.v4.full32',
|
|
'description': 'V4 template-fit 을 V3 Top-K 가 아니라 32 프레임 전체에 적용',
|
|
'note': '기존 v4_template_fit_r2_result.yaml 는 V3 Top-5 만 평가. 이 파일은 32 전체.',
|
|
'generated_at': datetime.datetime.now().isoformat(timespec='seconds'),
|
|
'answer_map': answer_map,
|
|
'holdout_sections': holdout,
|
|
},
|
|
'mdx_sections': out_sections,
|
|
}
|
|
OUT_PATH.write_text(
|
|
yaml.safe_dump(out, allow_unicode=True, sort_keys=False, width=1000),
|
|
encoding='utf-8',
|
|
)
|
|
|
|
print('=' * 70)
|
|
print(f'V4 full-32 평가 완료: {OUT_PATH}')
|
|
print('=' * 70)
|
|
print(f'\n각 섹션별 사용 가능 프레임 (label != reject) 개수 + Top-3:')
|
|
for sid, s in out_sections.items():
|
|
ans = s.get('answer_frame_number')
|
|
ans_str = f'(정답 {ans})' if ans else '(holdout)'
|
|
print(f'\n[{sid}] {ans_str} 사용가능 {s["usable_count"]}/{s["usable_count"]+s["reject_count"]}')
|
|
usable = [j for j in s['judgments_full32'] if j['label'] != 'reject']
|
|
for j in usable[:3]:
|
|
mark = ' 🎯' if ans and j['frame_number'] == ans else ''
|
|
print(f' Frame {j["frame_number"]:>2}{mark} conf {j["confidence"]:.3f} {j["label"]}')
|
|
if not usable:
|
|
print(' (사용 가능 후보 없음 — 모두 reject)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|