"""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()