feat(safety): B-1 PR② — fetch_version(payload 리스트) + ingest 4축 + 생애주기 잡 통째 + 부트스트랩
plan safety-library-1 B-1 PR② (R8-B1: 승격·supersede·스윕·repeal = 잡 코드 통째 배포):
- kr.fetch_version: 전문 1콜 → primary+annex payload 리스트 (R4-M4)
★fixture 가 잡은 결함 2: 별표구분(별표/서식) 차원 누락 시 (번호,가지) 4건 충돌
→ version_key='MST|{구분}{번호}-{가지}' / 삭제 tombstone 3건(별표10·서식1·2) skip
— KR 별표 삭제 = absence 아닌 명시 tombstone (R7-M3 absence 추론 불요 확정)
- ingest: 전 버전 pending 적재 + 4축(law/KR/COALESCE날짜/public_domain) + backfill 마커
- 생애주기 잡: 버전 시리즈 단위 승격·supersede(R7-B1) + 상태 기반 레거시 스윕(primary
current 보유 한정) + repeal(레거시 매핑분 포함, R7-M2) — 단일 트랜잭션·KST
- 법령명 매핑: 정규화 동등 비교(prefix 금지 — 시행령 오폭 차단), 가운뎃점·공백 흡수
- 워터마크 = 파싱 검증 통과 후에만 / 스케줄 daily 07:00 KST (law_monitor 슬롯 승계)
- 테스트 14/14 (매핑 표본·시리즈 키·payload fixture)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""B-1 PR② — 매핑·시리즈·payload 순수 단위 테스트 (plan safety-library-1).
|
||||
|
||||
법령명 매핑 단위 테스트 = R8-B1 동반 계약 (검증(PR③) 전에 스윕이 도는 만큼
|
||||
매핑은 코드 레벨 선고정). 실 title 표본 = 2026-06-13 prod documents 실측 형태.
|
||||
"""
|
||||
|
||||
import gzip
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "app"))
|
||||
|
||||
from workers.statute_adapters.kr import parse_service_payloads # noqa: E402
|
||||
from workers.statute_collector import ( # noqa: E402
|
||||
legacy_law_name,
|
||||
normalize_law_name,
|
||||
series_suffix,
|
||||
)
|
||||
|
||||
FIX = Path(__file__).parent / "fixtures" / "statute_kr"
|
||||
|
||||
|
||||
# ─── 법령명 매핑 (실 title 표본) ───
|
||||
|
||||
def test_legacy_law_name_extraction():
|
||||
# prod 실측 형태: '법령명 (YYYYMMDD) 섹션'
|
||||
assert legacy_law_name("건설기술 진흥법 시행규칙 (20260611) 제6장_보칙") == "건설기술 진흥법 시행규칙"
|
||||
assert legacy_law_name("산업안전보건법 (20260219) 전문") == "산업안전보건법"
|
||||
assert legacy_law_name("패턴 불일치 제목") is None
|
||||
|
||||
|
||||
def test_mapping_equality_not_prefix():
|
||||
"""prefix 비교 금지 — '산업안전보건법' family 가 시행령 레거시를 오폭하면 안 됨."""
|
||||
name = legacy_law_name("산업안전보건법 시행령 (20260324) 제1장")
|
||||
assert name == "산업안전보건법 시행령"
|
||||
assert normalize_law_name(name) != normalize_law_name("산업안전보건법")
|
||||
assert normalize_law_name(name) == normalize_law_name("산업안전보건법 시행령")
|
||||
|
||||
|
||||
def test_mapping_absorbs_middle_dot_and_space():
|
||||
"""가운뎃점·공백 변형 흡수 — 유해ㆍ위험작업(정식) vs 유해위험작업(law_monitor 표기)."""
|
||||
assert (normalize_law_name("유해ㆍ위험작업의 취업 제한에 관한 규칙")
|
||||
== normalize_law_name("유해위험작업의 취업 제한에 관한 규칙"))
|
||||
assert (normalize_law_name("산업안전보건기준에 관한 규칙")
|
||||
== normalize_law_name("산업안전보건기준에관한규칙"))
|
||||
|
||||
|
||||
# ─── 버전 시리즈 식별자 (R7-B1 a) ───
|
||||
|
||||
def test_series_suffix():
|
||||
assert series_suffix("283449") is None # primary
|
||||
assert series_suffix("273603|별표0001-00") == "별표0001-00" # annex (구분 차원 포함)
|
||||
assert series_suffix("273603|서식0003-00") == "서식0003-00"
|
||||
|
||||
|
||||
# ─── fetch_version payload (fixture — R4-M4 리스트 계약) ───
|
||||
|
||||
def _read_gz(name: str) -> str:
|
||||
return gzip.decompress((FIX / name).read_bytes()).decode("utf-8")
|
||||
|
||||
|
||||
def test_parse_service_payloads_rule_with_annexes():
|
||||
payloads = parse_service_payloads(
|
||||
_read_gz("lawservice_rule.xml.gz"), "산업안전보건기준에 관한 규칙", "273603")
|
||||
assert payloads[0].law_doc_kind == "primary"
|
||||
assert payloads[0].version_key == "273603"
|
||||
assert len(payloads[0].content) > 100_000 # 853조 본문
|
||||
annexes = [p for p in payloads if p.law_doc_kind == "annex"]
|
||||
# 별표단위 23 중 삭제 tombstone 3 skip(별표10 '삭제 <2023.11.14>'·서식1·2 '삭제 <2012.3.5>')
|
||||
# — KR 별표/서식 삭제 = absence 아닌 명시 tombstone (R7-M3 absence 추론 불요의 fixture 증거)
|
||||
assert len(annexes) == 20
|
||||
keys = [p.version_key for p in annexes]
|
||||
assert len(keys) == len(set(keys)), "annex version_key 유일성 (uq_legal_meta_version 전제)"
|
||||
assert all(k.startswith("273603|") for k in keys)
|
||||
# 구분 차원 — 별표1 vs 서식N 공존 (fixture 실측: (번호,가지)만으로는 4건 충돌)
|
||||
assert any("별표" in k for k in keys) and any("서식" in k for k in keys)
|
||||
|
||||
|
||||
def test_parse_service_payloads_sanab_no_annex():
|
||||
payloads = parse_service_payloads(
|
||||
_read_gz("lawservice_sanab.xml.gz"), "산업안전보건법", "283449")
|
||||
assert len(payloads) == 1 # 별표 없는 법령 = primary 단독
|
||||
p = payloads[0]
|
||||
assert p.promulgation_date == "20260219"
|
||||
assert p.effective_date == "20260601"
|
||||
assert "제2조(정의)" in p.content # 조문내용 보존
|
||||
assert p.content.startswith("# 산업안전보건법")
|
||||
Reference in New Issue
Block a user