- 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>
208 lines
8.4 KiB
Python
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()
|