feat(safety): B-1 PR① — law_monitor 스케줄 제거 + statute KR poll_changes + fixture 박제 (mig 356)

plan safety-library-1 B-1 PR① (fixture-first):
- law.go.kr 라이브 fixture 5종 박제 (OC 새니타이즈 검증 — 응답 법령상세링크에 키 포함 함정)
- R7-M3 판정: 전문 1콜 XML = 조문 853+별표 23 전체 스냅샷(부분 실패 개념 없음)
  + 별표번호/가지번호 = 구조화 필드 — 조문 취득 = 전문 1콜+로컬 파싱 확정(R2-m1)
- legal_acts KR 시드 26행(법령ID 라이브 실측, watch=26 전부, FK 계열 9그룹)
  ★ '유해ㆍ위험작업...' 정식명 = 가운뎃점 — law_monitor 하드코딩(점 없음)은 영구 미매칭 잠복
- statute_adapters/kr.py: poll_changes(lawSearch MST diff) — 순수 파서 분리, fixture 테스트 8/8
- statute_collector.py: 관찰 전용 코어(워터마크 영속 0 — ingest=PR②), 스케줄 미등록(R8-B1)
- main.py: law_monitor 스케줄 제거 — 버전 체인 밖 레거시 매일 증식의 유일 경로 차단

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-06-13 09:01:21 +09:00
parent 0c8fb41366
commit a28f12b12e
12 changed files with 431 additions and 2 deletions
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<법령 법령키="0017662026021921374">
<기본정보>
<법령ID>001766</법령ID>
<공포일자>20260219</공포일자>
<공포번호>21374</공포번호>
<언어>한글</언어>
<법종구분 법종구분코드="A0002">법률</법종구분>
<법령명_한글><![CDATA[산업안전보건법]]></법령명_한글>
<법령명_한자><![CDATA[산업안전보건법]]></법령명_한자>
<제명변경여부>N</제명변경여부>
<한글법령여부>Y</한글법령여부>
<편장절관>40040000</편장절관>
<소관부처 소관부처코드="1492000">고용노동부</소관부처>
<전화번호>044-202-8810, 8813, 8815, 8997</전화번호>
<시행일자>20260601</시행일자>
<제개정구분>일부개정</제개정구분>
<조문별시행일자>20260601</조문별시행일자>
<조문시행일자문자열>20260801:제10조의2, 제23조, 제175조제4항제1호의2</조문시행일자문자열>
<별표편집여부>N</별표편집여부>
<공포법령여부>Y</공포법령여부>
<시행일기준편집여부>Y</시행일기준편집여부>
</기본정보>
<조문>
<조문단위 조문키="0001000">
<조문번호>1</조문번호>
<조문여부>전문</조문여부>
<조문시행일자>20260601</조문시행일자>
<조문이동이전></조문이동이전>
<조문이동이후></조문이동이후>
<조문변경여부>N</조문변경여부>
</조문단위>
<조문단위 조문키="0001001">
<조문번호>1</조문번호>
<조문여부>조문</조문여부>
<조문제목><![CDATA[목적]]></조문제목>
<조문시행일자>20260601</조문시행일자>
<조문이동이전></조문이동이전>
<조문이동이후></조문이동이후>
<조문변경여부>N</조문변경여부>
<조문내용>
<![CDATA[제1조(목적) 이 법은 산업 안전 및 보건에 관한 기준을 확립하고 그 책임의 소재를 명확하게 하여 산업재해를 예방하고 쾌적한 작업환경을 조성함으로써 노무를 제공하는 사람의 안전 및 보건을 유지ㆍ증진함을 목적으로 한다. <개정 2020.5.26>]]>
</조문내용>
</조문단위>
</조문>
</법령>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?><LawSearch><target>law</target><키워드>산업안전보건기준에 관한 규칙</키워드><section>lawNm</section><totalCnt>1</totalCnt><page>1</page><numOfRows>1</numOfRows><resultCode>00</resultCode><resultMsg>success</resultMsg><law id="1"><법령일련번호>273603</법령일련번호><현행연혁코드>현행</현행연혁코드><법령명한글><![CDATA[산업안전보건기준에 관한 규칙]]></법령명한글><법령약칭명><![CDATA[안전보건규칙]]></법령약칭명><법령ID>007363</법령ID><공포일자>20250901</공포일자><공포번호>00450</공포번호><제개정구분명>일부개정</제개정구분명><소관부처코드>1492000</소관부처코드><소관부처명>고용노동부</소관부처명><법령구분명>고용노동부령</법령구분명><공동부령정보></공동부령정보><시행일자>20260302</시행일자><자법타법여부></자법타법여부><법령상세링크>/DRF/lawService.do?OC=__OC_REDACTED__&amp;target=law&amp;MST=273603&amp;type=HTML&amp;mobileYn=&amp;efYd=20260302</법령상세링크></law></LawSearch>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?><LawSearch><target>law</target><키워드>산업안전보건법</키워드><section>lawNm</section><totalCnt>3</totalCnt><page>1</page><numOfRows>3</numOfRows><resultCode>00</resultCode><resultMsg>success</resultMsg><law id="1"><법령일련번호>283449</법령일련번호><현행연혁코드>현행</현행연혁코드><법령명한글><![CDATA[산업안전보건법]]></법령명한글><법령약칭명><![CDATA[]]></법령약칭명><법령ID>001766</법령ID><공포일자>20260219</공포일자><공포번호>21374</공포번호><제개정구분명>일부개정</제개정구분명><소관부처코드>1492000</소관부처코드><소관부처명>고용노동부</소관부처명><법령구분명>법률</법령구분명><공동부령정보></공동부령정보><시행일자>20260601</시행일자><자법타법여부></자법타법여부><법령상세링크>/DRF/lawService.do?OC=__OC_REDACTED__&amp;target=law&amp;MST=283449&amp;type=HTML&amp;mobileYn=&amp;efYd=20260601</법령상세링크></law><law id="2"><법령일련번호>284771</법령일련번호><현행연혁코드>현행</현행연혁코드><법령명한글><![CDATA[산업안전보건법 시행령]]></법령명한글><법령약칭명><![CDATA[]]></법령약칭명><법령ID>003786</법령ID><공포일자>20260324</공포일자><공포번호>36220</공포번호><제개정구분명>타법개정</제개정구분명><소관부처코드>1492000</소관부처코드><소관부처명>고용노동부</소관부처명><법령구분명>대통령령</법령구분명><공동부령정보></공동부령정보><시행일자>20260324</시행일자><자법타법여부></자법타법여부><법령상세링크>/DRF/lawService.do?OC=__OC_REDACTED__&amp;target=law&amp;MST=284771&amp;type=HTML&amp;mobileYn=&amp;efYd=20260324</법령상세링크></law><law id="3"><법령일련번호>286657</법령일련번호><현행연혁코드>현행</현행연혁코드><법령명한글><![CDATA[산업안전보건법 시행규칙]]></법령명한글><법령약칭명><![CDATA[]]></법령약칭명><법령ID>007364</법령ID><공포일자>20260529</공포일자><공포번호>00470</공포번호><제개정구분명>일부개정</제개정구분명><소관부처코드>1492000</소관부처코드><소관부처명>고용노동부</소관부처명><법령구분명>고용노동부령</법령구분명><공동부령정보></공동부령정보><시행일자>20260601</시행일자><자법타법여부></자법타법여부><법령상세링크>/DRF/lawService.do?OC=__OC_REDACTED__&amp;target=law&amp;MST=286657&amp;type=HTML&amp;mobileYn=&amp;efYd=20260601</법령상세링크></law></LawSearch>
Binary file not shown.
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
산업안전보건법 001766 283449 20260219 20260601 법률
산업안전보건법 시행령 003786 284771 20260324 20260324 대통령령
산업안전보건법 시행규칙 007364 286657 20260529 20260601 고용노동부령
산업안전보건기준에 관한 규칙 007363 273603 20250901 20260302 고용노동부령
유해위험작업의 취업 제한에 관한 규칙 MISS
중대재해 처벌 등에 관한 법률 013993 228817 20210126 20220127 법률
중대재해 처벌 등에 관한 법률 시행령 014159 277417 20251001 20251001 대통령령
건설기술 진흥법 001807 276921 20251001 20251001 법률
건설기술 진흥법 시행령 002111 286847 20260609 20260609 대통령령
건설기술 진흥법 시행규칙 006175 286885 20260611 20260611 국토교통부령
시설물의 안전 및 유지관리에 관한 특별법 000237 266683 20241203 20251204 법률
위험물안전관리법 009502 259933 20240206 20250807 법률
위험물안전관리법 시행령 009707 273077 20250805 20250807 대통령령
위험물안전관리법 시행규칙 009732 262765 20240520 20250521 행정안전부령
화학물질관리법 000162 276815 20251001 20251001 법률
화학물질관리법 시행령 004390 280507 20251223 20251223 대통령령
화학물질의 등록 및 평가 등에 관한 법률 011857 279805 20251111 20260512 법률
소방시설 설치 및 관리에 관한 법률 009503 236977 20211130 20241201 법률
소방시설 설치 및 관리에 관한 법률 시행령 009694 284781 20260324 20260324 대통령령
전기사업법 001854 283981 20260310 20260310 법률
전기안전관리법 013718 268805 20250131 20260201 법률
고압가스 안전관리법 001850 283919 20260310 20260310 법률
고압가스 안전관리법 시행령 002246 286839 20260609 20260609 대통령령
액화석유가스의 안전관리 및 사업법 001849 276549 20251001 20251128 법률
근로기준법 001872 265959 20241022 20251023 법률
환경영향평가법 002016 276833 20251001 20251023 법률
1 산업안전보건법 001766 283449 20260219 20260601 법률
2 산업안전보건법 시행령 003786 284771 20260324 20260324 대통령령
3 산업안전보건법 시행규칙 007364 286657 20260529 20260601 고용노동부령
4 산업안전보건기준에 관한 규칙 007363 273603 20250901 20260302 고용노동부령
5 유해위험작업의 취업 제한에 관한 규칙 MISS
6 중대재해 처벌 등에 관한 법률 013993 228817 20210126 20220127 법률
7 중대재해 처벌 등에 관한 법률 시행령 014159 277417 20251001 20251001 대통령령
8 건설기술 진흥법 001807 276921 20251001 20251001 법률
9 건설기술 진흥법 시행령 002111 286847 20260609 20260609 대통령령
10 건설기술 진흥법 시행규칙 006175 286885 20260611 20260611 국토교통부령
11 시설물의 안전 및 유지관리에 관한 특별법 000237 266683 20241203 20251204 법률
12 위험물안전관리법 009502 259933 20240206 20250807 법률
13 위험물안전관리법 시행령 009707 273077 20250805 20250807 대통령령
14 위험물안전관리법 시행규칙 009732 262765 20240520 20250521 행정안전부령
15 화학물질관리법 000162 276815 20251001 20251001 법률
16 화학물질관리법 시행령 004390 280507 20251223 20251223 대통령령
17 화학물질의 등록 및 평가 등에 관한 법률 011857 279805 20251111 20260512 법률
18 소방시설 설치 및 관리에 관한 법률 009503 236977 20211130 20241201 법률
19 소방시설 설치 및 관리에 관한 법률 시행령 009694 284781 20260324 20260324 대통령령
20 전기사업법 001854 283981 20260310 20260310 법률
21 전기안전관리법 013718 268805 20250131 20260201 법률
22 고압가스 안전관리법 001850 283919 20260310 20260310 법률
23 고압가스 안전관리법 시행령 002246 286839 20260609 20260609 대통령령
24 액화석유가스의 안전관리 및 사업법 001849 276549 20251001 20251128 법률
25 근로기준법 001872 265959 20241022 20251023 법률
26 환경영향평가법 002016 276833 20251001 20251023 법률
+89
View File
@@ -0,0 +1,89 @@
"""B-1 PR① — KR 어댑터 순수 파서 fixture 테스트 (plan safety-library-1).
fixture = 2026-06-13 law.go.kr 라이브 박제 (OC 새니타이즈, tests/fixtures/statute_kr/).
파서는 순수 함수라 httpx/DB 불요 — 컨테이너 밖 로컬 실행.
"""
import gzip
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "app"))
from workers.statute_adapters import ChangeEvent # noqa: E402
from workers.statute_adapters.kr import detect_change, parse_search_hit # noqa: E402
FIX = Path(__file__).parent / "fixtures" / "statute_kr"
def _read(name: str) -> str:
p = FIX / name
if name.endswith(".gz"):
return gzip.decompress(p.read_bytes()).decode("utf-8")
return p.read_text(encoding="utf-8")
def test_parse_search_hit_exact_match():
hit = parse_search_hit(_read("lawsearch_sanab.xml"), "산업안전보건법")
assert hit is not None
assert hit["law_id"] == "001766"
assert hit["mst"] == "283449"
assert hit["promulgation_date"] == "20260219"
assert hit["effective_date"] == "20260601"
assert hit["status_code"] == "현행"
def test_parse_search_hit_rejects_partial_name():
# totalCnt 3 인 응답에서 '산업안전보건법 시행령' 등 부분 일치는 비매칭이어야 함
hit = parse_search_hit(_read("lawsearch_sanab.xml"), "산업안전보건")
assert hit is None
def test_detect_change_same_watermark_is_silent():
hit = parse_search_hit(_read("lawsearch_sanab.xml"), "산업안전보건법")
assert detect_change(hit, "kr-law:001766", "산업안전보건법", watermark="283449") is None
def test_detect_change_new_mst_is_amend():
hit = parse_search_hit(_read("lawsearch_sanab.xml"), "산업안전보건법")
ev = detect_change(hit, "kr-law:001766", "산업안전보건법", watermark="283448")
assert isinstance(ev, ChangeEvent)
assert ev.kind == "amend"
assert ev.new_version_key == "283449"
assert ev.effective_date == "20260601"
def test_detect_change_empty_watermark_is_amend():
# 첫 폴링(워터마크 부재) = 변경으로 감지 — PR② 부트스트랩 전 관찰 모드의 기대 동작
hit = parse_search_hit(_read("lawsearch_sanab.xml"), "산업안전보건법")
ev = detect_change(hit, "kr-law:001766", "산업안전보건법", watermark=None)
assert ev is not None and ev.kind == "amend"
def test_detect_change_repeal_keyword():
hit = {"mst": "9", "revision_type": "폐지", "promulgation_date": None,
"effective_date": None, "law_id": "x", "status_code": None}
ev = detect_change(hit, "kr-law:x", "x", watermark="1")
assert ev is not None and ev.kind == "repeal"
def test_lawservice_snapshot_semantics_rule():
"""R7-M3 판정 박제: 전문 1콜 XML = 조문+별표 전체 스냅샷 (PR② payload 계약의 전제)."""
root = ET.fromstring(_read("lawservice_rule.xml.gz"))
articles = root.findall(".//조문단위")
annexes = root.findall(".//별표단위")
assert len(articles) >= 800, "산안기준규칙 조문 853 기대 — 전문 1콜 판정 근거"
assert len(annexes) == 23, "별표 23 전부 본문 XML 포함 = 스냅샷 의미론"
# R7-M3 ②: 별표 식별 = 구조화 필드 (suffix 문자열 파싱 불요)
first = annexes[0]
assert first.findtext("별표번호") is not None
assert first.findtext("별표가지번호") is not None
def test_lawservice_sanab_basic_info():
root = ET.fromstring(_read("lawservice_sanab.xml.gz"))
assert root.findtext(".//법령ID") == "001766"
assert len(root.findall(".//조문단위")) >= 200
# 별표 없는 법령 = 별표단위 0 (스냅샷 의미론의 반대쪽 케이스)
assert len(root.findall(".//별표단위")) == 0