Files
Aislo/resources/knowledge/original/_pipeline/check_law.py
T
eomsangdonandClaude Opus 5 4cb9b15939 style: 저장소 전체 포맷터 일괄 적용 (prettier·biome·ruff)
파일마다 포맷 폭이 달라(≈80 대 100) 한 줄만 고쳐도 포맷터가 무관한 줄을 대량
재포맷했음. 사용자 지시로 전체를 한 번에 맞춤. 코드 동작 변경 없음 — 포맷만.

- 프론트엔드 `.ts/.css/.html` → 저장소 prettier (`.prettierrc`, printWidth 100)
- `B07_DesignDetail/openwebcad/**` → 자체 biome (tab 들여쓰기·single quote·lineWidth 100).
  `biome format` 만 사용 — `biome lint --write` 는 포맷 아닌 코드 수정까지 하므로 제외
- 파이썬 → `ruff format` (엔진 코드는 이미 정합, resources·scratch 스크립트 24개만 변경)

두 포맷터가 서로 되돌리지 않도록 `.prettierignore` 신규 — openwebcad 와 빌드·산출물
폴더를 prettier 대상에서 뺌. `.prettierrc` 에 `endOfLine: "auto"` 추가 — 기본값 `lf` 가
`core.autocrlf=true` 로 받은 CRLF 파일을 매번 전부 다시 써서 `--list-different` 가
실제 포맷 차이를 가리고 있었음.

검증: `tsc --noEmit` 통과(루트·openwebcad 둘 다), pytest 349 passed / 17 skipped /
0 failed, CAD vitest 87건 중 81 passed / 6 failed(laptop-sub 기준선과 동일, 회귀 없음).
포맷터 재실행 시 prettier·biome 모두 변경 0건.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 07:08:24 +09:00

154 lines
5.6 KiB
Python

# -*- coding: utf-8 -*-
"""국가법령정보센터 Open API로 목록 항목의 보유 여부·시행일·개정일 조회."""
import json, re, time, urllib.parse, urllib.request
from pathlib import Path
import os as _os
from pathlib import Path as _P
# 이 스크립트는 original/_pipeline/ 에 위치. 코퍼스 루트 = 상위 폴더.
ROOT_DIR = _P(__file__).resolve().parent.parent # ...\original
DATA_DIR = _P(__file__).resolve().parent / "data" # 생성 데이터(JSON)
# API 키: 커밋 금지 파일(law/.secrets.local.md) 또는 환경변수에서 읽는다.
def _load_key(name):
v = _os.environ.get(name)
if v:
return v.strip()
sec = ROOT_DIR.parent / ".secrets.local.md"
if sec.exists():
import re as _re
for pat in (r"KCSC[\s\S]*?`([A-Za-z0-9]{20,})`", r"인증키[:\s]*`?([A-Za-z0-9]{30,})`?"):
m = _re.search(pat, sec.read_text(encoding="utf-8"))
if m:
return m.group(1)
return ""
import xml.etree.ElementTree as ET
OUT = Path(str(DATA_DIR))
def api(target, query, display=5):
url = (
"https://www.law.go.kr/DRF/lawSearch.do?OC=umsangdon&type=XML"
f"&target={target}&display={display}&query=" + urllib.parse.quote(query)
)
for _ in range(3):
try:
with urllib.request.urlopen(url, timeout=25) as r:
return ET.fromstring(r.read())
except Exception as e:
time.sleep(1.2)
return None
def txt(node, *names):
for n in names:
el = node.find(n)
if el is not None and el.text:
return el.text.strip()
return ""
def ymd(s):
s = re.sub(r"\D", "", s or "")
return f"{s[:4]}.{s[4:6]}.{s[6:8]}" if len(s) == 8 else ""
LAWS = [
"산림자원의 조성 및 관리에 관한 법률",
"산림자원의 조성 및 관리에 관한 법률 시행령",
"산림자원의 조성 및 관리에 관한 법률 시행규칙",
"산림기술 진흥 및 관리에 관한 법률",
"산림기술 진흥 및 관리에 관한 법률 시행령",
"산림보호법",
"산지관리법",
"자연환경보전법",
"자연재해대책법",
"환경영향평가법",
"국가균형발전특별법",
"보조금 관리에 관한 법률",
"국가를 당사자로 하는 계약에 관한 법률",
"국가를 당사자로 하는 계약에 관한 법률 시행령",
"지방자치단체를 당사자로 하는 계약에 관한 법률",
"지방자치단체를 당사자로 하는 계약에 관한 법률 시행령",
"도로법",
"농어촌도로 정비법",
"국토의 계획 및 이용에 관한 법률",
"도로명주소법",
"도로명주소법 시행령",
"공간정보의 구축 및 관리 등에 관한 법률",
"산업안전보건법",
"산업재해보상보험법",
"고용보험 및 산업재해보상보험의 보험료징수 등에 관한 법률",
"고용보험 및 산업재해보상보험의 보험료징수 등에 관한 법률 시행령",
"고용보험법 시행령",
"국민건강보험법",
"국민연금법",
"노인장기요양보험법",
"노인장기요양보험법 시행령",
"부가가치세법",
"근로기준법",
"산업표준화법",
"전자서명법",
"공동주택관리법",
]
RULES = [
"임도설치 및 관리 등에 관한 규정",
"산림관리기반시설의 설계 및 시설기준",
"산림관리기반시설의 범위 및 기준",
"산림관리기반시설의 타당성평가",
"임도 품셈",
"자연휴양림업무 처리지침",
"중기운용관리",
"훈령·예규 등의 발령 및 관리에 관한 규정",
"국가지점번호",
"재난구호 및 재난복구 비용 부담기준 등에 관한 규정",
"사업종류별 산재보험료율",
"건설업 산업안전보건관리비 계상 및 사용기준",
"사회보험의 보험료 적용기준",
"엔지니어링사업대가의 기준",
"예산안 편성",
"토목공사원가계산 제비율",
"입찰유의서",
"수치지도 작성 작업규칙",
"비산분진 발생원 시설관리기준",
"건설공사 감독자 업무 지침",
"콘크리트 표준시방서",
"도로공사 표준시방서",
"임도시설공사 표준시방서",
]
res = {}
for grp, target, items in (("법령", "law", LAWS), ("행정규칙", "admrul", RULES)):
for q in items:
root = api(target, q)
rows = []
if root is not None:
for node in root.findall("law") + root.findall("admrul"):
rows.append(
{
"명": txt(node, "법령명한글", "행정규칙명"),
"약칭": txt(node, "법령약칭명"),
"종류": txt(node, "법령구분명", "행정규칙종류"),
"부처": txt(node, "소관부처명", "소관부처명"),
"공포": ymd(txt(node, "공포일자", "발령일자")),
"시행": ymd(txt(node, "시행일자")),
"제개정": txt(node, "제개정구분명", "제개정구분코드"),
"번호": txt(node, "공포번호", "발령번호"),
}
)
res[q] = {"grp": grp, "target": target, "rows": rows}
print(
f"[{grp}] {q} -> {len(rows)}건"
+ (f" | {rows[0]['명']} 시행 {rows[0]['시행']}" if rows else "")
)
time.sleep(0.35)
json.dump(res, open(OUT / "lawapi.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)