Files
Aislo/resources/knowledge/original/_pipeline/patch_law_md.py
T
eomsangdon 06640d04ea fix(pipeline): 법령 md 부칙 절단·목 미출력 결함 수정 + 보정 도구 신설
- collect_law.py: 부칙단위[:20] 상한 제거(법제처 XML은 오래된 순 → 최신 부칙
  절단됨), 목이 항의 직속 자식으로 오는 구조 처리(문서 순서 순회), _meta에
  수집일 필드 추가
- split_cost_docs.py: 노임 표 신뢰도 플래그(*/**) 캡처 → 별도 열로 복원
- patch_law_md.py 신설: 보관 XML 기준 누락분만 보충(전량 재생성은 후처리
  파괴하므로 금지). README에 사유·절차 명문화
2026-08-14 20:37:39 +09:00

145 lines
5.7 KiB
Python

# -*- coding: utf-8 -*-
"""보관된 법령 XML 기준으로 기존 md 의 누락분만 보충한다 (네트워크 미사용).
기존 md 에는 수동·스크립트 후처리(박스표 → 마크다운 표, 수식, 이미지)가
적용돼 있어 전체 재렌더링은 그 결과를 파괴한다. 따라서 **추가만** 한다.
보충 대상 (2026-08-14 감사에서 확인된 변환 누락 2종):
1. 부칙 절단 — 과거 collect_law.py 가 `부칙단위[:20]` 만 출력해 최신 부칙이
통째로 빠졌다. md 에 없는 부칙단위를 뒤에 이어붙인다.
2. 목(가·나·다…) 미출력 — 목이 호가 아니라 항의 직속 자식으로 오는 구조를
처리하지 못해 누락됐다. 부모 호 줄 뒤에 들여쓰기 4칸으로 삽입한다.
사용:
python patch_law_md.py --dry # 변경량만 출력
python patch_law_md.py # 저장
"""
import re
import sys
import xml.etree.ElementTree as ET
from collect_law import ROOT, T, clean
분류목록 = ("법률", "행정규칙")
STEM = re.compile(r"^(현행|교본시점)_\d{8}$")
부칙헤더 = re.compile(r"부칙\s*<[^>]*>")
def 부칙키(text):
"""'부칙 <제20903호,2025.4.2>' → 정규화 키."""
m = 부칙헤더.search(text or "")
return re.sub(r"\s+", "", m.group(0)) if m else None
def 목텍스트(mo):
return clean(T(mo, "목내용")) or clean(" ".join(x for x in mo.itertext() if x))
def patch(md_path, xml_path):
"""(부칙 추가수, 목 추가수, 목 앵커실패수)"""
root = ET.parse(xml_path).getroot()
text = md_path.read_text(encoding="utf-8")
lines = text.splitlines()
부칙추가, 목추가, 앵커실패 = 0, 0, 0
# ── 1) 목 삽입 (뒤에서부터 삽입해 인덱스 밀림 방지) ──
조문 = root.find("조문")
작업 = []
if 조문 is not None:
for j in 조문.findall("조문단위"):
for h in j.findall("항"):
앵커 = None # 직전 호 내용
for kid in h:
if kid.tag == "호":
앵커 = clean(T(kid, "호내용"))
for mo in kid.findall("목"):
작업.append((앵커, 목텍스트(mo)))
elif kid.tag == "목":
작업.append((앵커, 목텍스트(kid)))
for ho in j.findall("호"):
앵커 = clean(T(ho, "호내용"))
for mo in ho.findall("목"):
작업.append((앵커, 목텍스트(mo)))
# 앵커별로 묶어 한 번에 삽입
묶음 = {}
for 앵커, mv in 작업:
if not mv or mv in text: # 이미 있으면 건너뜀
continue
if not 앵커:
앵커실패 += 1
continue
묶음.setdefault(앵커, []).append(mv)
for 앵커, 목들 in 묶음.items():
idx = next((i for i, l in enumerate(lines) if l.strip() == 앵커.strip()), None)
if idx is None:
idx = next((i for i, l in enumerate(lines) if 앵커.strip() and 앵커.strip() in l), None)
if idx is None:
앵커실패 += len(목들)
continue
lines[idx + 1:idx + 1] = [f" {mv}" for mv in 목들]
목추가 += len(목들)
# ── 2) 부칙 보충 ──
부칙 = root.find("부칙")
if 부칙 is not None:
units = 부칙.findall("부칙단위")
본문 = "\n".join(lines)
기존키 = {re.sub(r"\s+", "", m) for m in 부칙헤더.findall(본문)}
누락 = [b for b in units if (부칙키(T(b, "부칙내용")) or "") not in 기존키]
if 누락:
if "## 부칙" not in 본문:
lines += ["", "## 부칙", ""]
add = []
for b in 누락:
body = clean(T(b, "부칙내용"))
if not body.strip():
continue
for ln in body.split("\n"):
add.append(f"> {ln}" if ln.strip() else ">")
add.append("")
부칙추가 += 1
# 첨부파일 절이 뒤에 있으면 그 앞에, 없으면 끝에
pos = next((i for i, l in enumerate(lines) if l.startswith("## 첨부파일")), len(lines))
lines[pos:pos] = add
if 부칙추가 or 목추가:
md_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return 부칙추가, 목추가, 앵커실패
def main(dry=False):
합계 = [0, 0, 0]
파일 = 0
for 분류 in 분류목록:
base = ROOT / 분류
if not base.is_dir():
continue
for folder in sorted(p for p in base.iterdir() if p.is_dir()):
for xml_path in sorted(folder.glob("*.xml")):
if not STEM.match(xml_path.stem):
continue
md_path = xml_path.with_suffix(".md")
if not md_path.exists():
continue
if dry:
orig = md_path.read_text(encoding="utf-8")
b, m, f = patch(md_path, xml_path)
if dry:
md_path.write_text(orig, encoding="utf-8")
if b or m or f:
파일 += 1
print(f" {분류}/{folder.name}/{md_path.name}: 부칙+{b} 목+{m}"
+ (f" (앵커실패 {f})" if f else ""))
합계[0] += b
합계[1] += m
합계[2] += f
print(f"\n파일 {파일}건 / 부칙 +{합계[0]} · 목 +{합계[1]}"
+ (f" · 앵커실패 {합계[2]}" if 합계[2] else "")
+ (" (dry-run — 원복함)" if dry else " 저장 완료"))
if __name__ == "__main__":
main(dry="--dry" in sys.argv)