feat(safety): C-1 후속 — version_status decorate + facets 집계
검색 엔드포인트 wrapper decoration(run_search 코어 무접촉·ranking 무관): - version_status: 법령 결과(material_type=law)에 legal_meta.version_status 부착 (decorate_version_status, law 무결과 시 query skip). SearchResult.version_status 신설. - facets=true: top-K 결과 분류 축(material_type/jurisdiction/version_status) 분포 라벨 (compute_facets). 미요청=None(byte 불변). SearchResponse.facets 신설. - result_decorate.py 신설. 단위 4건. freshness incident 변경(law_365d 제거+흡수)=ranking 변경이라 별 슬라이스 defer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""C-1 후속 — facets 집계 + version_status decorate 순수 로직 테스트.
|
||||
|
||||
version_status 의 실제 legal_meta 조회는 GPU 라이브(법령 검색)로 검증 — 여기선 facets 분포
|
||||
계약 + decorate 의 law 무결과 skip 경로(DB 미접촉)만.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "app"))
|
||||
|
||||
from services.search.result_decorate import ( # noqa: E402
|
||||
compute_facets,
|
||||
decorate_version_status,
|
||||
)
|
||||
|
||||
|
||||
class _R:
|
||||
"""SearchResult 흉내 — 분류 축 속성만."""
|
||||
|
||||
def __init__(self, material_type=None, jurisdiction=None, version_status=None, id=1):
|
||||
self.material_type = material_type
|
||||
self.jurisdiction = jurisdiction
|
||||
self.version_status = version_status
|
||||
self.id = id
|
||||
|
||||
|
||||
def test_compute_facets_distribution():
|
||||
results = [
|
||||
_R("law", "KR", "current"),
|
||||
_R("law", "KR", "superseded"),
|
||||
_R("incident", "KR", None),
|
||||
_R("paper", None, None),
|
||||
]
|
||||
f = compute_facets(results)
|
||||
assert f["material_type"] == {"law": 2, "incident": 1, "paper": 1}
|
||||
assert f["jurisdiction"] == {"KR": 3} # paper jurisdiction None 제외
|
||||
assert f["version_status"] == {"current": 1, "superseded": 1} # None 제외
|
||||
|
||||
|
||||
def test_compute_facets_empty_and_all_none():
|
||||
assert compute_facets([]) == {}
|
||||
assert compute_facets([_R(), _R()]) == {} # 모든 축 None → 빈 축 미포함
|
||||
|
||||
|
||||
def test_compute_facets_excludes_empty_axes():
|
||||
f = compute_facets([_R(jurisdiction="US"), _R(jurisdiction="EU")])
|
||||
assert f == {"jurisdiction": {"US": 1, "EU": 1}}
|
||||
assert "material_type" not in f
|
||||
|
||||
|
||||
def test_decorate_version_status_skips_without_law():
|
||||
# law 결과 없으면 legal_meta 조회 skip → session 미사용(None 으로도 무오류)
|
||||
results = [_R("incident", "KR"), _R("paper")]
|
||||
asyncio.run(decorate_version_status(None, results))
|
||||
assert all(r.version_status is None for r in results)
|
||||
Reference in New Issue
Block a user