docs(knowledge): 원가계산 PDF 3종 마크다운화·챕터 분할 (미결 18 신설 포함)

- 미결 No.18 신설: 사급 잡자재 단가 원천 결정(유료지) — 사용자 결정 대기
- 건협 노임: pdf2md + 4챕터 분할. 개별직종 노임단가 표는 pdf2md 실패로
  PDF 스트림 정밀 파싱 재구성 — 132직종 전수·4개 공표일 병기
- 중기중앙회 노임: pdf2md(표 정상) + 4챕터 분할 (129직종 표 포함)
- 건설공사 표준품셈(982p): pdf2md + 부문 5폴더/45장 전체 분할
  (공통8·토목9·건축11·기계설비13·유지관리4 — 목차 대조 전수 일치)
- 재사용 도구 _pipeline/split_cost_docs.py (cak|kbiz|pumsem 모드)
- 각 폴더 _meta.md에 변환 방법·한계(어절 공백·장 경계 ±1p) 기록
This commit is contained in:
2026-08-14 17:29:25 +09:00
parent 2a661e1041
commit 6d9863d658
332 changed files with 61568 additions and 0 deletions
@@ -9,6 +9,7 @@
|---|---|---|---|---|---|
| 14 | [공통] 설계심사 체크리스트 — 실무 사례 대조 | **정리 완료(2026-08-13)**: 사방 = 고시 별표2 전 24항 표화([02_타당성평가 §4](technical_info/02_사방댐/01_대상지_타당성평가/02_타당성평가.md)), 임도 = 규정 제12조의2+별지18 9항 반영([설계제원_총괄 §10](technical_info/01_임도/02_상세설계/설계제원_총괄.md)). 잔여 = **실무 심의 사례와 대조** (사례 자료 미확보) | 심의 사례 자료 확보 시 대조 | 좌동 | 자료 대기 (2026-08-13) |
| 15 | [공통] 사방댐 부위별 강도 실무값 (구 No.6 통합) | 잔여 = 부위별(본체/방수로 부배합/물받이) 강도 수치 관측 — **콘크리트 사방댐 실시설계 내역서** 필요 (기존 실무문서는 혼합쌓기라 데이터 없음). 부위 구분 4단위는 해소됨([콘크리트_재료 §1-1](technical_info/01_임도/02_상세설계/구조물/콘크리트_재료.md)). 선정=개발 단계 | 내역서 사용자 제공 대기 | [02_사방댐/05_원가정보/01_원가_특이사항](technical_info/02_사방댐/05_원가정보/01_원가_특이사항.md) §3 | 자료 대기 (2026-08-13) |
| 18 | [공통] 사급 잡자재 단가 원천 결정 (유료지 구독 여부) | No.17 파생(2026-08-14). 관급(쇼핑몰 API)·특수자재(가격정보 API)·노임·중기·품셈은 전부 무료 경로 확보 완료 — 남은 공백 = **사급 잡자재 공사당 15종 내외**(PVC·유로폼·치즐·뇌관·번호판·지역골재 등, 실측 = [원가_기초데이터_원천 §5](technical_info/01_임도/05_원가정보/원가_기초데이터_원천.md)). 실무 대비표 실사용 유료지 = 물가정보·물가자료 중심 (연 약 30만원/종, API는 한국물가정보 KPI-DPS만 확인) | 선택지: ① 유료지 1종 구독 ② 실무 관측값 라이브러리(과거 참조)+업체 견적 입력 UI ③ 혼합. **사업 판단 — 개발 단계 사용자 결정** | [원가_기초데이터_원천](technical_info/01_임도/05_원가정보/원가_기초데이터_원천.md) §4~5, [자재단가 _meta](original/원가계산/자재단가/_meta.md) | 미결 — 사용자 결정 대기 (2026-08-14) |
| 17 | [공통] ★원가 기초 데이터의 현행 원천 확보 가능성 | 견적 엔진 전제조건. 대상 표: **일위대가목록표/일위대가표ㆍ단가산출근거목록표/단가산출근거ㆍ환율및기초자료ㆍ중기목록표/중기사용료ㆍ재료비·노무비·경비 목록표ㆍ자재단가대비표ㆍ재료비·노무비·경비·중기 집계표**. STC/실행내역 xlsx는 최종 결과물이라 값만 있고 **단가를 어디서 가져왔는지(원천) 미표기** — Aislo가 현재 기준 데이터를 직접 입수할 경로가 있는지부터 확인 필요. 원천 후보(추정): 노임 = 대한건설협회 시중노임(연 2회 공표ㆍ공개), 자재 = 조달청 나라장터 가격 or 물가정보지(한국물가정보ㆍ물가자료ㆍ거래가격 — 유료 구독), 중기 = 표준품셈 기계손료 산식(확보) + 기계가격ㆍ연료단가, 일위대가/단가산출근거 = 원천 아닌 파생물(품셈 × 단가 조립) | ① 실무 xlsx 각 표의 비고ㆍ출처 표기("물가정보 20xx.x월호" 류) 역추적으로 실무 원천 식별 ② 원천별 현행 데이터 입수 경로(공개 API/유료 구독/수동 다운로드)ㆍ갱신 주기 조사 ③ 결과에 따라 01_수집지침 W8 신설 검토. **조사 예정 — 착수 전 사용자 협의**. +노무 관측 편입(구 No.10, 2026-08-13): 실무 내역 실측 결과 노임 = **시중노임 직종ㆍ단가 무할증 그대로**(울진 보통인부 165,545원) — 원천 추정(대한건설협회) 실물 뒷받침. 산림품셈 26계열 노무 할증 적용 흔적 없음, 상세 = [품셈_적용기준 §2 실무 관측](technical_info/01_임도/04_수량분석정보/품셈_적용기준.md). ESTX 배포본 관측 편입(2026-08-14): 원천 추정이 상용 프로그램 실물로 확인 — 노임 건협(반기)·자재 간행물 5종+나라장터(월간)·중기 환율(서울외국환중개)+유류(한국석유공사, 상반기엔 조달청)·품셈 KICT(연간)·표준시장단가 KICT/항만협회(반기). 정리 = [원가_기초데이터_원천](technical_info/01_임도/05_원가정보/원가_기초데이터_원천.md) | [원가_기초데이터_원천](technical_info/01_임도/05_원가정보/원가_기초데이터_원천.md), [원가계산_체계](technical_info/01_임도/05_원가정보/원가계산_체계.md), [품셈_적용기준](technical_info/01_임도/04_수량분석정보/품셈_적용기준.md), [STC포맷분석](original/실무문서/_STC포맷분석.md), [숨김탭분석](original/실무문서/_숨김탭분석.md) | 미결 — 원천 확인됨, 입수 경로 조사 대기 (2026-08-14 갱신) |
### §1-보조 — No.17 원가 기초데이터 원천 참고표 (내역 프로그램 판매처 게시판, 2026-08-13 등록)
@@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
"""원가계산 원문(PDF→md) 후처리: 노임 표 정밀 파싱 + 챕터 분할 (반기·연간 갱신 시 재사용)
전제: `collect_cost_sources.py docs` 로 PDF 수집 → `pdf2md.py <PDF>` 로 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"(?<![\d,.])([1-5]\d{3})(?![\d,.])").split(t)
result = {}
for i in range(1, len(parts) - 1, 2):
code, seg = parts[i], parts[i + 1]
if not (1 <= int(code[1:]) <= 99):
continue
slots = re.findall(r"\d{1,3}(?:,\d{3})+|(?<![\d,])-(?![\d,])", seg)[:4]
first = re.search(r"\d{1,3}(?:,\d{3})+|(?<![\d,])-(?![\d,])", seg)
name = "".join(re.findall(r"[가-힣A-Za-z/]", seg[: first.start()] if first else seg))
if code not in result and len(slots) == 4 and name:
result[code] = (name, slots)
rows = [CAK_COLS, "|---|---|---|---|---|---|"]
rows += [f"| {c} | {result[c][0]} | {' | '.join(result[c][1])} |" for c in sorted(result)]
return len(result), "\n".join(rows) + "\n"
def _page_idx(lines, n):
for i, l in enumerate(lines):
if re.fullmatch(rf"-?\s*-\s*{n}\s*-.*", l.strip()) and len(l.strip()) < 40:
return i
return None
def split_cak():
src = BASE / CAK_DIR / f"{CAK_STEM}.md"
lines = src.read_text(encoding="utf-8").splitlines()
cnt, table = parse_cak_table()
print("parsed trades:", cnt)
starts = [(f, _page_idx(lines, p)) for f, p in CAK_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"> 원문: {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\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")
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Some files were not shown because too many files have changed in this diff Show More