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) 기록
@@ -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")
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 127 KiB |