"""Pipeline Step 12-promote — Frame 13 review_queue 해제 + SSOT 라벨 확정. 사용자 지시 (A 단계): review_queue §13 에 "필수조건 → capability_requirements 여야 할 가능성" 로 이미 기록되어 있음. V3 r4 결과에서 MDX 03-1 aff=capability_requirements 정상 / Frame 13 현재 primary 가 comparative_matrix 여서 primary 매칭 실패 → 03-1 실패 원인 확정. **성능 맞추기 땜질 아님**. review_queue 에 남아 있던 미확정 라벨의 SSOT 정리. 원칙: 1. 원본 structure_ontology_v2_final.yaml 는 **보존** 2. 새 파일 structure_ontology_v2_final_r2.yaml 생성 3. Frame 13 entry 에 resolved_from_review_queue provenance 추가 4. meta.review_queue 에서 §13 제거하되, meta.resolved_review_items 에 이력 보존 """ import datetime import sys from pathlib import Path import yaml HERE = Path(__file__).parent sys.path.insert(0, str(HERE)) FINAL_PATH = HERE / 'structure_ontology_v2_final.yaml' FINAL_R2_PATH = HERE / 'structure_ontology_v2_final_r2.yaml' FRAME13_SHORT_ID = '13' NEW_PRIMARY = 'capability_requirements' PROVENANCE = { 'source_review_queue_item': { 'frame': '13', 'reason_original': '"필수조건" 이 comparative_matrix 로 잡힘 — capability_requirements 여야 할 가능성', 'queue_file': 'structure_ontology_v2_final.yaml::meta.review_queue', }, 'confirmation_evidence': { 'pipeline_step': 'V3 r4 (pipeline_08_v3_r4_structure_rerank.py)', 'mdx_section': '03-1 (DX 시행을 위한 필수 요건)', 'finding': ( 'MDX 03-1 aff = capability_requirements (정상). ' 'Frame 13 aff primary = comparative_matrix (오라벨) 로 primary match = 0.0. ' 'Frame 20 (aff=capability_requirements) 이 1.0 으로 올라가 Frame 13 를 밀어냄. ' 'B-1 + B-2 (MDX 하네스) 수정 후에도 남은 실패 → Frame 쪽 SSOT 문제 확정.' ), 'r4_result_file': 'v3_structure_rerank_r4_result.yaml', }, 'decision_rationale': ( '성능 땜질이 아니라 review_queue 에 이미 올라온 미해결 분류의 정상적 확정 단계. ' 'Spec-review / SSOT 원칙에 따라 ontology 를 바로잡음.' ), 'confirmed_at': datetime.datetime.now().isoformat(timespec='seconds'), 'confirmed_by': 'pipeline_12_promote_frame13.py', } def main(): final = yaml.safe_load(FINAL_PATH.read_text(encoding='utf-8')) templates = final['templates_v2'] short_to_fid = {v['short_id']: k for k, v in templates.items()} fid = short_to_fid.get(FRAME13_SHORT_ID) if not fid: raise SystemExit(f"Frame {FRAME13_SHORT_ID} 를 찾지 못함.") entry = templates[fid] old_primary = entry['content_affinity']['primary'] if old_primary == NEW_PRIMARY: print(f"Frame {FRAME13_SHORT_ID} 는 이미 {NEW_PRIMARY} — 변경 없음.") return # content_affinity 업데이트 entry['content_affinity']['primary'] = NEW_PRIMARY # 기존 secondary 에서 new primary 제거 (있으면) entry['content_affinity']['secondary'] = [ s for s in entry['content_affinity'].get('secondary', []) if s != NEW_PRIMARY ] # evidence 에 기록 entry['content_affinity']['evidence']['primary'] = { 'source': 'resolved_from_review_queue', 'prior_primary': old_primary, 'new_primary': NEW_PRIMARY, 'confidence': 1.0, 'provenance': PROVENANCE, } # frame-level provenance entry.setdefault('v2_meta', {}) entry['v2_meta']['resolved_from_review_queue'] = PROVENANCE # meta 업데이트 meta = dict(final['meta']) meta['schema_version'] = 'template-fit-v2-final-r2' meta['generated_at'] = datetime.datetime.now().isoformat(timespec='seconds') meta['generator'] = 'pipeline_12_promote_frame13.py' meta['predecessor_drafts'] = meta.get('predecessor_drafts', []) + ['structure_ontology_v2_final.yaml'] # review_queue 에서 §13 제거, resolved_review_items 에 이동 remaining_queue = [q for q in meta.get('review_queue', []) if q.get('frame') != FRAME13_SHORT_ID] moved = [q for q in meta.get('review_queue', []) if q.get('frame') == FRAME13_SHORT_ID] meta['review_queue'] = remaining_queue meta.setdefault('resolved_review_items', []) for item in moved: item_record = dict(item) item_record['resolved_at'] = PROVENANCE['confirmed_at'] item_record['resolution'] = { 'from': old_primary, 'to': NEW_PRIMARY, 'axis': 'content_affinity.primary', 'evidence_pipeline': PROVENANCE['confirmation_evidence']['pipeline_step'], } meta['resolved_review_items'].append(item_record) # manual_overrides 로그에도 추가 meta.setdefault('manual_overrides', []) meta['manual_overrides'].append({ 'frame': FRAME13_SHORT_ID, 'frame_id': fid, 'field': 'content_affinity.primary', 'from': old_primary, 'to': NEW_PRIMARY, 'reason': 'review_queue 해제 (SSOT 정리) — V3 r4 결과 기반', }) meta['status'] = 'locked_for_v3_r5_matching' out = {'meta': meta, 'templates_v2': templates} FINAL_R2_PATH.write_text( yaml.safe_dump(out, allow_unicode=True, sort_keys=False, width=1000), encoding='utf-8', ) print('=' * 70) print('Frame 13 review_queue 해제 + SSOT 확정') print('=' * 70) print(f' 입력: {FINAL_PATH} (보존)') print(f' 출력: {FINAL_R2_PATH}') print() print(f' Frame 13 content_affinity.primary: {old_primary} → {NEW_PRIMARY}') print(f' review_queue: {len(final["meta"].get("review_queue", []))} → {len(remaining_queue)} 건') print(f' resolved_review_items: +1 건 (Frame 13)') if __name__ == '__main__': main()