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

177 lines
6.5 KiB
Python

"""BEPS 전문(1171281171/texts.md)에서 synonym 후보 mining.
Mining 전략:
1. 알려진 canonical 기준 — BEPS에서 등장하는 변형 확인
2. 한영 병기 패턴 — `XXX (YYY)`, `XXX: YYY`
3. 띄어쓰기 변형 — 같은 명사구의 공백 변형들
4. 형태 유사 — Kiwi 토큰화 후 편집거리 근접
"""
import sys
import re
import collections
from pathlib import Path
import yaml
sys.path.insert(0, str(Path(__file__).parent))
from methods import _get_kiwi, _extract_content_tokens
ROOT = Path(r"d:\ad-hoc\kei\design_agent")
BEPS_FILE = ROOT / "figma_to_html_agent" / "blocks" / "1171281171" / "texts.md"
HERE = Path(__file__).parent
def load_beps():
"""BEPS 텍스트의 실제 내용 라인만 (메타 제거)"""
text = BEPS_FILE.read_text(encoding="utf-8")
lines = []
for ln in text.split("\n"):
s = ln.strip()
if not s: continue
if s.startswith("#"): continue
if s.startswith(">"): continue
if s.startswith("- "):
lines.append(s[2:].strip())
else:
lines.append(s)
return lines
# ═══ 전략 1: 기존 canonical 기준 변형 확인 ═══
def strategy_known_canonicals(lines, canonicals):
"""알려진 canonical 각각에 대해 BEPS에서 어떤 표기로 등장하는지"""
result = {}
for c in canonicals:
# 공백 없는 형태 + 공백 있는 형태 모두
no_space = c.replace(" ", "")
variants_found = set()
for ln in lines:
# exact match of canonical
if c in ln:
variants_found.add(c)
# no-space variant
if no_space != c and no_space in ln:
variants_found.add(no_space)
# space-separated variant (글자 사이 공백 삽입)
if len(c) >= 2 and " " not in c:
# e.g. "필수조건" → "필수 조건" 검색
for split_pos in range(1, len(c)):
with_space = c[:split_pos] + " " + c[split_pos:]
if with_space in ln:
variants_found.add(with_space)
result[c] = variants_found
return result
# ═══ 전략 2: 한영 병기 패턴 ═══
def strategy_paren_pairs(lines):
"""`한글 (English)` 또는 `English (한글)` 패턴"""
pairs = []
# 패턴 1: "한글단어 (영어단어)"
p1 = re.compile(r"([가-힣][가-힣\s]*[가-힣])\s*\(([A-Za-z][A-Za-z\s&\.]*[A-Za-z])\)")
# 패턴 2: "영어단어 (한글단어)"
p2 = re.compile(r"([A-Za-z][A-Za-z\s&\.]*[A-Za-z])\s*\(([가-힣][가-힣\s]*[가-힣])\)")
for ln in lines:
for m in p1.finditer(ln):
k, e = m.group(1).strip(), m.group(2).strip()
if 2 <= len(k) <= 20 and 2 <= len(e) <= 40:
pairs.append((k, e))
for m in p2.finditer(ln):
e, k = m.group(1).strip(), m.group(2).strip()
if 2 <= len(k) <= 20 and 2 <= len(e) <= 40:
pairs.append((k, e))
return pairs
# ═══ 전략 3: 띄어쓰기 변형 (Kiwi 기반) ═══
def strategy_spacing(lines):
"""같은 연속 명사구가 '공백 버전'과 '붙인 버전' 둘 다 등장하는 경우"""
# 명사구 후보 추출
kiwi = _get_kiwi()
all_text = " ".join(lines)
# 2~4자 명사구 반복 찾기
freq = collections.Counter()
for ln in lines:
tokens = _extract_content_tokens(ln, kiwi)
# 2-gram 명사구
for i in range(len(tokens) - 1):
a, b = tokens[i], tokens[i + 1]
if 1 <= len(a) <= 4 and 1 <= len(b) <= 4:
spaced = f"{a} {b}"
joined = f"{a}{b}"
if spaced in all_text and joined in all_text:
freq[(joined, spaced)] += 1
return [(j, s) for (j, s), c in freq.most_common(50)]
# ═══ 전략 4: 괄호 안의 약어 ═══
# e.g., "Geographic Information System (GIS)"
def strategy_abbrev_expansion(lines):
p = re.compile(r"([A-Za-z][A-Za-z\s]{3,60})\s*\(([A-Z]{2,6})\)")
out = []
for ln in lines:
for m in p.finditer(ln):
full, abbr = m.group(1).strip(), m.group(2).strip()
if 2 < len(full) < 50 and 2 <= len(abbr) <= 6:
out.append((abbr, full))
return out
def main():
lines = load_beps()
print(f"BEPS 본문 라인 수: {len(lines)}")
print()
# 전략 1: 기존 canonical
canonicals = ["필수조건", "DX", "BIM", "과정혁신", "결과혁신",
"3D모델", "2D도면", "발주자", "시공자", "설계자",
"디지털기술", "전문지식", "Process", "Product",
"성장동력", "고부가가치", "건설산업"]
print("=== 전략 1: 알려진 canonical의 BEPS 등장 변형 ===")
r1 = strategy_known_canonicals(lines, canonicals)
for c, variants in r1.items():
if variants:
print(f" {c:12s}{sorted(variants)}")
print()
# 전략 2: 한영 병기
print("=== 전략 2: 한영 병기 패턴 (상위 20개) ===")
r2 = strategy_paren_pairs(lines)
counter2 = collections.Counter(r2)
for (k, e), cnt in counter2.most_common(20):
print(f" '{k}' ↔ '{e}' ({cnt}회)")
print()
# 전략 3: 띄어쓰기 변형
print("=== 전략 3: 띄어쓰기 변형 (같은 연속 명사구가 공백/붙임 둘다) ===")
r3 = strategy_spacing(lines)
for joined, spaced in r3[:20]:
print(f" '{joined}' ↔ '{spaced}'")
print()
# 전략 4: 약어 풀네임
print("=== 전략 4: 영어 약어 풀네임 ===")
r4 = strategy_abbrev_expansion(lines)
counter4 = collections.Counter(r4)
for (abbr, full), cnt in counter4.most_common(20):
print(f" '{abbr}' ≡ '{full}' ({cnt}회)")
print()
# 결과를 YAML 후보로 저장
proposals = {
"from_known_canonicals": {c: sorted(v) for c, v in r1.items() if v},
"ko_en_paren_pairs": [{"ko": k, "en": e, "count": cnt}
for (k, e), cnt in counter2.most_common(30)],
"spacing_variants": [{"joined": j, "spaced": s} for j, s in r3[:30]],
"abbreviation_expansions": [{"abbr": a, "full": f, "count": cnt}
for (a, f), cnt in counter4.most_common(30)],
}
out_path = HERE / "SYNONYM_MINING_PROPOSALS.yaml"
with open(out_path, "w", encoding="utf-8") as f:
yaml.safe_dump(proposals, f, allow_unicode=True, sort_keys=False)
print(f"완료: {out_path}")
if __name__ == "__main__":
main()