"""도메인 용어 사전 로더 (계획서 §5.6, 수정사항 4번). `config/domain_glossary.yaml` 은 의존성을 늘리지 않으려고 YAML 파서 없이 읽는다. 지원 형식은 한 줄 리스트뿐이다: 총계정원장: [G/L, GL, 원장, ACDOCA, SKAT, FAGLL03] 질의 확장(query/expand.py)과 concepts 위키 생성(wiki_out/run.py)이 같은 사전을 쓰도록 로딩을 여기로 모았다 (이전에는 wiki_out/run.py 안에 인라인 정규식으로만 있었다). """ from __future__ import annotations import re from functools import lru_cache from pathlib import Path from .settings import ROOT # "용어: [동의어, 동의어]" — 주석(#)·빈 줄·중첩 키(tags:)는 건너뛴다 GLOSSARY_LINE = re.compile(r"^([^#:\s][^:]*):\s*\[(.*)\]\s*$") DEFAULT_PATH = ROOT / "config" / "domain_glossary.yaml" def parse_glossary(text: str) -> dict[str, list[str]]: """사전 텍스트 → {대표어: [동의어...]}""" out: dict[str, list[str]] = {} for line in text.splitlines(): m = GLOSSARY_LINE.match(line.strip()) if not m: continue term = m.group(1).strip() syns = [t.strip() for t in m.group(2).split(",") if t.strip()] if term: out[term] = syns return out @lru_cache(maxsize=4) def load_glossary(path: Path | None = None) -> dict[str, list[str]]: p = path or DEFAULT_PATH if not p.exists(): return {} return parse_glossary(p.read_text(encoding="utf-8")) @lru_cache(maxsize=4) def synonym_map(path: Path | None = None) -> dict[str, list[str]]: """양방향 동의어 맵 — 대표어로도, 동의어로도 질의가 들어올 수 있다. "입고" → [GR, MSEG, 101 …] 뿐 아니라 "GR" → [입고, MSEG …] 도 성립해야 한다. 키는 대문자 정규화(한글은 그대로)해 비교한다. """ glossary = load_glossary(path) groups: list[set[str]] = [{term, *syns} for term, syns in glossary.items()] out: dict[str, list[str]] = {} for group in groups: for member in group: key = member.upper() others = [m for m in group if m.upper() != key] if not others: continue bucket = out.setdefault(key, []) for o in others: if o not in bucket: bucket.append(o) return out