Files

57 lines
2.2 KiB
Python

"""질의 확장 — 도메인 용어 사전으로 동의어를 펼친다 (계획서 §5.6, 수정사항 4번).
"입고"로 물었을 때 GR / MSEG / 101 로 색인된 문서도 찾아야 한다.
## 왜 한 MATCH 식에 동의어를 섞지 않는가
`index.db.fts_or` 는 토큰과 한글 2-gram 을 전부 OR 로 잇는다. 이미 재현율 편향이라
동의어까지 같은 식에 넣으면 정밀도가 더 나빠진다 ("총계정원장" 질의가 "원장" 2-gram 하나로
걸린 문서와 동일 가중치가 된다).
그래서 **2단 검색**을 한다:
1단 — 원질의만으로 검색. 이걸 항상 상위에 둔다.
2단 — 결과가 부족할 때만(top_k 미만) 동의어 식으로 보충하고, 점수에 감쇠 계수를 걸고
matched_by='동의어 확장' 으로 표시해 호출 측이 구분할 수 있게 한다.
감쇠 계수(EXPANDED_WEIGHT)는 원질의 히트가 항상 앞서도록 하는 장치다.
"""
from __future__ import annotations
from config.glossary import synonym_map
from index.db import fts_or, query_tokens
# 동의어 히트에 곱하는 점수 감쇠 — 원질의 히트보다 항상 뒤에 놓이게 한다
EXPANDED_WEIGHT = 0.35
MAX_SYNONYMS = 24
def expansion_terms(q: str) -> list[str]:
"""질의 토큰의 동의어 목록 (원질의 토큰 자체는 제외)."""
syn = synonym_map()
tokens = query_tokens(q)
seen = {t.upper() for t in tokens}
out: list[str] = []
for t in tokens:
for s in syn.get(t.upper(), []):
if s.upper() in seen:
continue
seen.add(s.upper())
out.append(s)
if len(out) >= MAX_SYNONYMS:
return out
return out
def match_exprs(q: str) -> tuple[str, str | None]:
"""(원질의 MATCH 식, 동의어 MATCH 식 | None)"""
terms = expansion_terms(q)
return fts_or(query_tokens(q)), (fts_or(terms) if terms else None)
def explain(q: str) -> dict:
"""확장 결과 설명 — API 응답에 실어 "왜 이게 나왔나"를 보이게 한다."""
terms = expansion_terms(q)
return {"query": q, "tokens": query_tokens(q), "synonyms": terms,
"expanded": bool(terms), "expanded_weight": EXPANDED_WEIGHT}