- 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>
177 lines
6.5 KiB
Python
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()
|