파일마다 포맷 폭이 달라(≈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>
154 lines
5.6 KiB
Python
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)
|