Merge feat/safety-library-a1 (C-1 후속 version_status+facets) into ds-board-merged

검색 결과 wrapper decoration: 법령 version_status + facets 집계(ranking 무관·additive).
This commit is contained in:
hyungi
2026-06-13 15:08:24 +09:00
3 changed files with 125 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
"""안전 자료실 C-1 후속 — 검색 결과 wrapper decoration (version_status + facets).
엔드포인트 wrapper 에서 run_search() 결과에 1회 적용 — 검색 코어(run_search) 무접촉(r3).
- version_status: 법령 결과(material_type='law')에 legal_meta.version_status
(current/superseded/pending/repealed) 부착. legal_meta.document_id 1:0..1 위성 →
매핑 없는 law(레거시 등)는 None 유지. law 결과 없으면 query skip.
- facets: top-K 결과 내 분류 축(material_type/jurisdiction/version_status) 분포 라벨(r2-M4).
facets=true 일 때만 계산(미요청 시 None = byte 불변·ranking 무관).
"""
from __future__ import annotations
from collections import Counter
from typing import TYPE_CHECKING
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
if TYPE_CHECKING:
from api.search import SearchResult
async def decorate_version_status(
session: AsyncSession, results: list["SearchResult"]
) -> None:
"""법령 결과에 legal_meta.version_status 부착 (in-place). law 결과 없으면 query skip."""
law_ids = [r.id for r in results if r.material_type == "law" and r.id is not None]
if not law_ids:
return
rows = await session.execute(
text(
"SELECT document_id, version_status FROM legal_meta "
"WHERE document_id = ANY(:ids)"
),
{"ids": law_ids},
)
status_by_id = {row.document_id: row.version_status for row in rows}
for r in results:
if r.id in status_by_id:
r.version_status = status_by_id[r.id]
def compute_facets(results: list["SearchResult"]) -> dict[str, dict[str, int]]:
"""top-K 결과의 분류 축 분포 라벨. None 값은 제외(present 라벨만, 빈 축은 미포함)."""
axes = {
"material_type": [r.material_type for r in results],
"jurisdiction": [r.jurisdiction for r in results],
"version_status": [getattr(r, "version_status", None) for r in results],
}
facets: dict[str, dict[str, int]] = {}
for axis, vals in axes.items():
counter = Counter(v for v in vals if v is not None)
if counter:
facets[axis] = dict(counter.most_common())
return facets