# -*- coding: utf-8 -*- """원가계산 원문(PDF→md) 후처리: 노임 표 정밀 파싱 + 챕터 분할 (반기·연간 갱신 시 재사용) 전제: `collect_cost_sources.py docs` 로 PDF 수집 → `pdf2md.py ` 로 md 변환 완료. 사용법: python split_cost_docs.py cak # 건협 노임: 132직종 표 정밀 파싱 + 4챕터 분할 python split_cost_docs.py kbiz # 중기중앙회 노임: 4챕터 분할 (표는 pdf2md가 정상 변환) python split_cost_docs.py pumsem # 건설공사 표준품셈: 5부문/45장 분할 주의(갱신 시 점검): - 건협: 표 페이지 범위(PAGE_TABLE)·챕터 원문 페이지(CAK_CH)가 판마다 다를 수 있음 - 품셈: 부문 표제 라인·목차 구역은 자동 탐지하지만 결과 요약(장 수)이 목차와 일치하는지 확인 - 공통: 원문 PDF 프로즈는 어절 공백이 붙는 특성 있음(값·표는 정상) — W5 공백 기준 예외로 기록 """ import re import sys from pathlib import Path import pymupdf as fitz BASE = Path(__file__).resolve().parent.parent / "원가계산" # ────────────────────────── 건협 노임 ────────────────────────── CAK_DIR = "노임단가_건설업_대한건설협회" CAK_STEM = "2026상반기_건설업_임금실태조사_대한건설협회" PAGE_TABLE = (9, 13) # 0-based: 원문 p.10~13 = 개별직종 노임단가 표 CAK_CH = [ ("1. 조사개요", 1), ("2. 임금적용요령", 5), ("3. 개별직종 노임단가", 9), ("4. 직종해설", 13), ] CAK_COLS = "| 직종코드 | 직종명 | 신뢰도 | 2026.1.1 | 2025.9.1 | 2025.1.1 | 2024.9.1 |" def parse_cak_table(): """PDF 텍스트 스트림을 직종코드 앵커로 분할해 132개 직종 표 재구성.""" doc = fitz.open(BASE / CAK_DIR / f"{CAK_STEM}.pdf") t = "".join(doc[p].get_text() + "\n" for p in range(*PAGE_TABLE)) doc.close() parts = re.compile(r"(? 원문: {CAK_STEM}.pdf (대한건설협회, 공표 2025-12-31, 적용 2026-01-01)\n" "> 변환: pdf2md + 표 정밀 파싱. ⚠ 본문 프로즈는 원문 PDF 특성상 어절 공백이 붙어 있음 — 값·표는 정상.\n\n" ) for i, (fname, st) in enumerate(starts): en = starts[i + 1][1] if i + 1 < len(starts) else len(lines) body = "\n".join(lines[st:en]).strip() if "개별직종" in fname: body = ( "## Ⅲ. 개별직종 노임단가 (1일 8시간 기준, 원)\n\n" f"> PDF 좌표·스트림 정밀 파싱으로 재구성 — {cnt}개 직종 전수, 최근 4개 공표일 병기.\n" "> `-` = 해당 공표일 미공표(표본 부족·신설 등). 원문 각주는 PDF 참조.\n" "> **신뢰도** 열 = 원문이 직종번호 앞에 붙이는 기호 — `*` 조사현장 5개 미만(적용 시 유의), " "`**` 미조사(임금적용요령 Ⅱ 참조). 빈칸 = 정상 공표.\n\n" + table ) (BASE / CAK_DIR / f"{fname}.md").write_text( f"# {fname.split('. ', 1)[1]}\n\n{hdr}{body}\n", encoding="utf-8" ) print("wrote", fname) # ────────────────────────── 중기중앙회 노임 ────────────────────────── KBIZ_DIR = "노임단가_제조업_중소기업중앙회" KBIZ_STEM = "2026상반기_중소제조업_직종별_임금조사_중소기업중앙회" KBIZ_CH = [ ("1. 조사개요", 1), ("2. 조사결과 요약", 12), ("3. 직종별 조사노임", 18), ("4. 직종코드 및 직종명 해설", 30), ] def split_kbiz(): src = BASE / KBIZ_DIR / f"{KBIZ_STEM}.md" lines = src.read_text(encoding="utf-8").splitlines() starts = [(f, _page_idx(lines, p)) for f, p in KBIZ_CH] if starts[0][1] is None: starts[0] = (starts[0][0], 0) assert all(s[1] is not None for s in starts), starts hdr = ( f"> 원문: {KBIZ_STEM}.pdf (중소기업중앙회, 공표 2026-06-30, 적용 2026-07-01)\n" "> 변환: pdf2md. 표·수치 정상. `*` = 표본 부족 미공표, `**` = 원문 각주 참조.\n\n" ) for i, (fname, st) in enumerate(starts): en = starts[i + 1][1] if i + 1 < len(starts) else len(lines) body = "\n".join(lines[st:en]).strip() (BASE / KBIZ_DIR / f"{fname}.md").write_text( f"# {fname.split('. ', 1)[1]}\n\n{hdr}{body}\n", encoding="utf-8" ) print("wrote", fname) # ────────────────────────── 건설공사 표준품셈 ────────────────────────── PUM_DIR = "건설공사_표준품셈" PUM_STEM = "2026년_건설공사_표준품셈" def split_pumsem(): lines = (BASE / PUM_DIR / f"{PUM_STEM}.md").read_text(encoding="utf-8").splitlines() # 부문 표제 위치 자동 탐지 (짧은 단독 라인) sec_names = [ ("01_공통부문", "공통부문"), ("02_토목부문", "토목부문"), ("03_건축부문", "건축부문"), ("04_기계설비부문", "기계설비부문"), ("05_유지관리부문", "유지관리부문"), ] hits = {} for i, l in enumerate(lines): s = re.sub(r"[\s#>\-·ㆍ]", "", l) for sec, kr in sec_names: if s == kr: hits.setdefault(sec, []).append(i) toc_marks = [hits[s][0] for s, _ in sec_names if s in hits] body_marks = [hits[s][-1] for s, _ in sec_names if s in hits] toc_zone_end = body_marks[0] # 목차에서 장 이름 추출 chapter_names = {} for k, (sec, kr) in enumerate(sec_names): a = toc_marks[k] b = toc_marks[k + 1] if k + 1 < len(toc_marks) else toc_zone_end names = {} for i in range(a, b): s = re.sub(r"\s", "", lines[i]) for m in re.finditer(r"제(\d{1,2})장([가-힣·()]+?)(?=\d)", s): names.setdefault(int(m.group(1)), m.group(2)) chapter_names[sec] = names # 본문 분할: 장 표제(정방향/역방향 병기) 탐색 for k, (sec, kr) in enumerate(sec_names): a = body_marks[k] b = body_marks[k + 1] if k + 1 < len(body_marks) else len(lines) names = chapter_names[sec] starts, prev = [], a for n in sorted(names): nm = names[n] li = a if n == 1 else None if li is None: keys = [f"제{n}장{nm[:4]}", f"{nm[:4]}제{n}장"] for i in range(prev + 1, b): s = re.sub(r"\s", "", lines[i]) if any(kx in s for kx in keys) and len(re.findall(r"제\d{1,2}장", s)) == 1: li = i break if li is None: print(f"⚠ {sec} 제{n}장 {nm} 경계 미검출 — 수동 확인 필요") continue starts.append((n, nm, li)) prev = li outdir = BASE / PUM_DIR / sec outdir.mkdir(exist_ok=True) for j, (n, nm, st) in enumerate(starts): en = starts[j + 1][2] if j + 1 < len(starts) else b hdr = ( f"# {sec[3:]} 제{n}장 {nm}\n\n" f"> 원문: {PUM_STEM}.pdf (국토교통부 공고, 2026년 적용) — pdf2md 변환\n" "> ⚠ 장 경계는 표제 탐지 기준 — 앞뒤 1페이지 내외 겹침 가능. 수치 검증 시 원본 PDF 대조.\n\n" ) (outdir / f"제{n}장_{nm}.md").write_text( hdr + "\n".join(lines[st:en]).strip() + "\n", encoding="utf-8" ) print(sec, f"{len(starts)}/{len(names)} 장") if __name__ == "__main__": mode = sys.argv[1] if len(sys.argv) > 1 else "" if mode == "cak": split_cak() elif mode == "kbiz": split_kbiz() elif mode == "pumsem": split_pumsem() else: sys.exit("mode: cak | kbiz | pumsem")