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

208 lines
8.4 KiB
Python

"""Raw 후보 키워드 → 정제된 카테고리 분류
분류 카테고리:
- matchable : 도메인/내용어 (Phase 23~25 keyword 축에 사용)
- structure : 구조·레이아웃 서술어 (구조 축 전용, keyword 축에서 제외)
- weak_row : row label / 일반어 / 범용 polarity (keyword 축에서 제외)
규칙:
1) STRUCTURE_PATTERNS regex (3열, 2열비교, 4사분면, X섹션, X카드, X강조…)
2) STRUCTURE_EXACT 정확 매칭 (표, 카드, 다이어그램, split-panel, 순환도, 관계도 등)
3) WEAK_ROW_EXACT (정의, 특징, 개요, 구성, 결론, 장점, 단점, 고객, 활용, 관리 등)
4) 나머지 = matchable
목적: keyword 축에 구조어가 새어 들어가 Phase 23~25 비교가 오염되는 문제 해결.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from phase_common import load_32_frames, load_frame_index
HERE = Path(__file__).parent
# ─── 구조어 패턴 (regex) ───────────────────────────────
STRUCTURE_PATTERNS = [
r'^\d+열\w*$', # 2열, 3열, 3열비교, 3열카드, 3열표
r'^\d+사분면$',
r'^\d+섹션$',
r'^\d+강조$',
r'^\d+카테고리$',
r'^\d+카드$',
r'^\d+col-.*$', # 3col-parallel 등
r'^cards-\d+.*$', # cards-3-horizontal
]
# ─── 구조어 정확 매칭 ─────────────────────────────────
STRUCTURE_EXACT = {
# 기본 구조 명사
'표', '카드', '다이어그램', '맵',
# 레이아웃 영문/하이픈
'split-panel', 'paired', 'central-split', 'paired-rows',
# 한글 레이아웃 서술어
'순환도', '교차다이어그램', '관계도', '4각관계도',
'상단배너', '풀페이지시각화', '위치기반시각화',
# 주체관계 구조어 (데이터 관계 표현 자체)
'주체별관계',
# 차트/시각화 서술어
'Rome', '인용구', # BIM 이슈 4사분면 중심 인용
# Phase 17 layouts.yaml 영향 서술어
'3요소', '5요소', # cardinality + 구조 맥락 (매칭 오염 위험)
}
# ─── 약한 row label / 일반어 ──────────────────────────
WEAK_ROW_EXACT = {
# 표 컬럼 헤더 전형
'정의', '특징', '개요', '구성', '결론',
'장점', '단점', '고객',
# 일반 동사형 범용
'관리', '활용', '지원', '운영', '구분',
# polarity / 애매어
'적극', '구체', '소극', '의존',
# 위치/범주 범용
'해외', '국내',
# 과다 등장 수식어
'과다', '기반',
}
def classify_keyword(kw):
"""Return one of: 'matchable', 'structure', 'weak_row'."""
# 1) 구조 패턴
for pat in STRUCTURE_PATTERNS:
if re.match(pat, kw):
return 'structure'
# 2) 구조 exact
if kw in STRUCTURE_EXACT:
return 'structure'
# 3) weak row exact
if kw in WEAK_ROW_EXACT:
return 'weak_row'
# 4) default matchable
return 'matchable'
def classify_frame_keywords(keywords):
"""Returns dict of lists: matchable / structure / weak_row."""
result = {'matchable': [], 'structure': [], 'weak_row': []}
for kw in keywords:
cat = classify_keyword(kw)
result[cat].append(kw)
return result
def refined_frame_keywords(keywords):
"""Phase 23~25 keyword axis 용: matchable 만 반환."""
return [kw for kw in keywords if classify_keyword(kw) == 'matchable']
# ═══ 검수 리포트 생성 ═══
def main():
frames = load_32_frames()
idx_data, frame_to_short = load_frame_index()
# 전체 키워드 set
all_kws = set().union(*[set(v["keywords"]) for v in frames.values()])
# 분류
by_cat = {'matchable': set(), 'structure': set(), 'weak_row': set()}
for kw in all_kws:
by_cat[classify_keyword(kw)].add(kw)
lines = []
lines.append("# 후보 키워드 정제 분류 리포트")
lines.append("")
lines.append("**문제**: analysis.md 의 '후보 키워드' 필드에 도메인 내용어·구조어·row label 이 혼재.")
lines.append("")
lines.append("**해결**: 3개 카테고리로 분류해 Phase 23~25 의 keyword 축에는 `matchable` 만 투입.")
lines.append("")
lines.append("| 카테고리 | 정의 | Phase 23~25 keyword 축 사용 |")
lines.append("|----------|------|--------------------------|")
lines.append("| **matchable** | 도메인/내용어 (BIM, DX, 발주자, 기술, 품질향상 등) | ✓ |")
lines.append("| **structure** | 구조·레이아웃 서술어 (3열비교, 표, 카드, 다이어그램, split-panel 등) | ✗ (구조 축에서 처리) |")
lines.append("| **weak_row** | row label / 범용 polarity (정의, 특징, 장점, 적극, 국내 등) | ✗ (변별력 낮음) |")
lines.append("")
# 전체 통계
lines.append("## 1. 전체 분류 결과")
lines.append("")
total = len(all_kws)
lines.append(f"- 총 고유 키워드: **{total}**")
lines.append(f"- matchable: **{len(by_cat['matchable'])}** ({len(by_cat['matchable'])/total*100:.1f}%)")
lines.append(f"- structure: **{len(by_cat['structure'])}** ({len(by_cat['structure'])/total*100:.1f}%)")
lines.append(f"- weak_row: **{len(by_cat['weak_row'])}** ({len(by_cat['weak_row'])/total*100:.1f}%)")
lines.append("")
# 카테고리별 전체 리스트
lines.append("## 2. structure 카테고리 전체 ")
lines.append("")
lines.append("이 어휘들은 '디자인이 어떻게 생겼나'를 표현하는 구조어. "
"MDX 본문에 거의 등장하지 않거나 등장해도 매칭 신호로 부적합.")
lines.append("")
for kw in sorted(by_cat['structure']):
lines.append(f"- `{kw}`")
lines.append("")
lines.append("## 3. weak_row 카테고리 전체")
lines.append("")
lines.append("row label 또는 일반 polarity 어휘. 여러 MDX/Frame 에 쉽게 등장해 변별력 낮음.")
lines.append("")
for kw in sorted(by_cat['weak_row']):
lines.append(f"- `{kw}`")
lines.append("")
# 프레임별 정제 전후 비교
lines.append("## 4. 프레임별 정제 전후 비교")
lines.append("")
lines.append("| # | Frame | raw (수) | matchable (수) | 제거된 structure | 제거된 weak_row |")
lines.append("|---|-------|---------|---------------|----------------|---------------|")
ordered = sorted(frames.items(), key=lambda kv: frame_to_short.get(kv[0], "99"))
for fid, v in ordered:
sid = frame_to_short.get(fid, "?")
cat = classify_frame_keywords(v["keywords"])
removed_structure = ", ".join(cat['structure']) or "-"
removed_weak = ", ".join(cat['weak_row']) or "-"
lines.append(
f"| {sid} | {fid} | {len(v['keywords'])} | {len(cat['matchable'])} | "
f"{removed_structure} | {removed_weak} |"
)
lines.append("")
# 프레임별 matchable 리스트
lines.append("## 5. 프레임별 정제 후 matchable 키워드")
lines.append("")
lines.append("| # | Frame | matchable 키워드 |")
lines.append("|---|-------|------------------|")
for fid, v in ordered:
sid = frame_to_short.get(fid, "?")
cat = classify_frame_keywords(v["keywords"])
matched = ", ".join(cat['matchable']) or "(비어있음)"
lines.append(f"| {sid} | {fid} | {matched} |")
lines.append("")
# 정책
lines.append("## 6. 적용 정책")
lines.append("")
lines.append("- **Phase 23~25**: `matchable` 만 keyword 축 vocab 으로 투입.")
lines.append("- **Phase 25** 구조 축: `structural_match_v3` (ontology schema) 사용 — 키워드 축과 독립.")
lines.append("- **Phase 26** (template-fit-v1): `anchor_sets` 는 이미 구조화된 명명 set 이므로 별도 정제 없음.")
lines.append("")
lines.append("이 정책으로 'Phase 25 결과 개선 = 구조 축 효과 vs 키워드 안에 섞인 구조어 효과' 구분이 명확해짐.")
lines.append("")
out = HERE / "KEYWORD_REFINEMENT_REPORT.md"
out.write_text("\n".join(lines), encoding="utf-8")
print(f"완료: {out}")
print()
print(f"분류 결과 요약:")
print(f" - matchable: {len(by_cat['matchable'])}")
print(f" - structure: {len(by_cat['structure'])}")
print(f" - weak_row: {len(by_cat['weak_row'])}")
if __name__ == "__main__":
main()