- 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>
197 lines
7.1 KiB
Python
197 lines
7.1 KiB
Python
"""Milestone 2 / Step 1: structure_ontology.yaml 의 anchor_sets 를 keyword_base 기준으로 청소.
|
|
|
|
1. keyword_base.yaml 로드 → canonical set + excluded set
|
|
2. structure_ontology.yaml templates_v1 의 모든 anchor_sets 순회
|
|
3. 각 anchor_set.terms 필터링:
|
|
- canonical 에 있으면 유지
|
|
- excluded 에 있으면 제거 + 로그
|
|
4. 특수 케이스: Frame 20 (1171281198) dx_sw_necessity — '필수성' → '필수' 로 대체
|
|
5. 제거 후 terms 가 비어버린 set 삭제 + 로그
|
|
6. 결과 저장 (backup: structure_ontology.yaml.pre_milestone2.bak)
|
|
7. CLEAN_ANCHOR_SETS_REPORT.md 변경사항 리포트 생성
|
|
"""
|
|
import sys
|
|
import shutil
|
|
from pathlib import Path
|
|
import yaml
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
HERE = Path(__file__).parent
|
|
|
|
|
|
def load_keyword_base():
|
|
path = HERE / "keyword_base.yaml"
|
|
with open(path, encoding='utf-8') as f:
|
|
data = yaml.safe_load(f)
|
|
canonical = set(data['keywords'].keys())
|
|
excluded = set(e['term'] for e in data['excluded'])
|
|
# excluded 는 bucket 정보도 필요
|
|
excluded_by_bucket = {}
|
|
for e in data['excluded']:
|
|
excluded_by_bucket.setdefault(e['bucket'], set()).add(e['term'])
|
|
return canonical, excluded, excluded_by_bucket
|
|
|
|
|
|
def load_ontology():
|
|
path = HERE / "structure_ontology.yaml"
|
|
with open(path, encoding='utf-8') as f:
|
|
return yaml.safe_load(f)
|
|
|
|
|
|
SPECIAL_REPLACEMENTS = {
|
|
# Frame 20 (1171281198) 필수성 → 필수
|
|
('1171281198', 'dx_sw_necessity', '필수성'): '필수',
|
|
}
|
|
|
|
|
|
def clean_anchor_sets(ontology, canonical, excluded):
|
|
"""templates_v1 의 anchor_sets 를 keyword_base 기준으로 필터링."""
|
|
templates = ontology.get('templates_v1', {})
|
|
changes = [] # (frame_id, set_id, action, detail)
|
|
|
|
for fid, tpl in templates.items():
|
|
new_anchor_sets = []
|
|
for s in tpl.get('anchor_sets', []):
|
|
set_id = s.get('id', '?')
|
|
original_terms = list(s.get('terms', []))
|
|
kept_terms = []
|
|
removed_terms = []
|
|
replaced_terms = []
|
|
|
|
for t in original_terms:
|
|
special_key = (fid, set_id, t)
|
|
if special_key in SPECIAL_REPLACEMENTS:
|
|
new_t = SPECIAL_REPLACEMENTS[special_key]
|
|
if new_t in canonical:
|
|
kept_terms.append(new_t)
|
|
replaced_terms.append((t, new_t))
|
|
else:
|
|
removed_terms.append(t)
|
|
continue
|
|
|
|
if t in excluded:
|
|
removed_terms.append(t)
|
|
elif t in canonical:
|
|
kept_terms.append(t)
|
|
else:
|
|
# 분류 불명 - keyword_base 에 없음. 보수적으로 유지
|
|
kept_terms.append(t)
|
|
changes.append((fid, set_id, 'UNKNOWN_KEEP',
|
|
f"'{t}' keyword_base 에 없으나 excluded 도 아님 — 유지"))
|
|
|
|
if replaced_terms:
|
|
for old, new in replaced_terms:
|
|
changes.append((fid, set_id, 'REPLACE', f"'{old}' → '{new}'"))
|
|
if removed_terms:
|
|
changes.append((fid, set_id, 'REMOVE', f"제거: {removed_terms}"))
|
|
|
|
if not kept_terms:
|
|
changes.append((fid, set_id, 'DROP_SET',
|
|
f'set 전체 삭제 (원본: {original_terms})'))
|
|
continue
|
|
|
|
# min_hits 재검증
|
|
min_hits = s.get('min_hits', 1)
|
|
if min_hits > len(kept_terms):
|
|
new_min = len(kept_terms)
|
|
changes.append((fid, set_id, 'ADJUST_MIN_HITS',
|
|
f'min_hits {min_hits} → {new_min} (terms 감소)'))
|
|
s['min_hits'] = new_min
|
|
|
|
s['terms'] = kept_terms
|
|
new_anchor_sets.append(s)
|
|
|
|
if len(new_anchor_sets) != len(tpl.get('anchor_sets', [])):
|
|
removed_ids = [s.get('id', '?')
|
|
for s in tpl.get('anchor_sets', [])
|
|
if s.get('id', '?') not in [ns.get('id', '?') for ns in new_anchor_sets]]
|
|
changes.append((fid, '*', 'TEMPLATE_ANCHOR_SETS_REDUCED',
|
|
f'{len(tpl["anchor_sets"])}→{len(new_anchor_sets)}, 삭제: {removed_ids}'))
|
|
|
|
tpl['anchor_sets'] = new_anchor_sets
|
|
|
|
return ontology, changes
|
|
|
|
|
|
def write_report(changes, excluded_by_bucket, canonical):
|
|
lines = []
|
|
lines.append("# Milestone 2 / Step 1 — anchor_sets 정리 리포트")
|
|
lines.append("")
|
|
lines.append("**목적**: `keyword_base.yaml` 기준으로 `structure_ontology.yaml` templates_v1 의 anchor_sets 정리.")
|
|
lines.append("")
|
|
lines.append(f"**canonical 수**: {len(canonical)}")
|
|
lines.append("**excluded 수 (bucket 별):**")
|
|
for bk, terms in sorted(excluded_by_bucket.items()):
|
|
lines.append(f"- {bk}: {len(terms)}")
|
|
lines.append("")
|
|
|
|
# 변경사항 요약
|
|
action_counts = {}
|
|
for _, _, action, _ in changes:
|
|
action_counts[action] = action_counts.get(action, 0) + 1
|
|
lines.append("## 변경 요약")
|
|
lines.append("")
|
|
lines.append("| Action | 수 |")
|
|
lines.append("|--------|----|")
|
|
for action, cnt in sorted(action_counts.items()):
|
|
lines.append(f"| {action} | {cnt} |")
|
|
lines.append("")
|
|
|
|
# frame 별 상세
|
|
by_frame = {}
|
|
for fid, set_id, action, detail in changes:
|
|
by_frame.setdefault(fid, []).append((set_id, action, detail))
|
|
|
|
lines.append(f"## Frame 별 변경 ({len(by_frame)}개 frame 영향)")
|
|
lines.append("")
|
|
for fid in sorted(by_frame.keys()):
|
|
lines.append(f"### Frame {fid}")
|
|
for set_id, action, detail in by_frame[fid]:
|
|
lines.append(f"- **[{set_id}]** {action}: {detail}")
|
|
lines.append("")
|
|
|
|
out = HERE / "CLEAN_ANCHOR_SETS_REPORT.md"
|
|
out.write_text("\n".join(lines), encoding='utf-8')
|
|
print(f"리포트: {out}")
|
|
|
|
|
|
def main():
|
|
print("[1] keyword_base 로드...")
|
|
canonical, excluded, excluded_by_bucket = load_keyword_base()
|
|
print(f" canonical: {len(canonical)}, excluded: {len(excluded)}")
|
|
|
|
print("[2] structure_ontology 로드...")
|
|
ontology = load_ontology()
|
|
|
|
print("[3] anchor_sets 청소...")
|
|
ontology, changes = clean_anchor_sets(ontology, canonical, excluded)
|
|
|
|
print(f"[4] 변경사항: {len(changes)}건")
|
|
action_counts = {}
|
|
for _, _, action, _ in changes:
|
|
action_counts[action] = action_counts.get(action, 0) + 1
|
|
for a, c in sorted(action_counts.items()):
|
|
print(f" {a}: {c}")
|
|
|
|
print("[5] 백업 생성...")
|
|
src = HERE / "structure_ontology.yaml"
|
|
bak = HERE / "structure_ontology.yaml.pre_milestone2.bak"
|
|
shutil.copy(src, bak)
|
|
print(f" {bak}")
|
|
|
|
print("[6] 저장...")
|
|
# yaml.safe_dump 는 anchor/alias 불필요, 순서 보존 위해 default_flow_style=False + sort_keys=False
|
|
with open(src, 'w', encoding='utf-8') as f:
|
|
yaml.safe_dump(ontology, f, allow_unicode=True, sort_keys=False, width=120)
|
|
print(f" {src}")
|
|
|
|
print("[7] 리포트 작성...")
|
|
write_report(changes, excluded_by_bucket, canonical)
|
|
|
|
print("\n완료.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|