- 대한건설협회 임금실태조사 보고서 13회(2020.1 · 2020.9 · 2021.1 · 2021.9 ~ 2026.9) 원문 보관 — 2020.9 · 2021.1 은 게시판에 HWP 만 있어 HWP 로 보관 - 회차마다 전체본 md + 장별 md 4개(쪽 표기 · 표 좌표 재구성) — _pipeline/cak_report_md.py · cak_hwp_md.py - 표 안 수 기계 대조(cak_verify.py) — md 대 PDF 이상 0 · 회차 사이 충돌 1(3011 · 2020.1 원문 그대로) · 쪽 그림 대조 - 미공표직종_추적.md — 원문 4-라 산정 근거(마지막 공표값 · 전체직종 평균 · 이어 곱한 증가율) · ESTX 견줌 14 중 12 일치 - 마스터 미변경 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""건설업 임금실태조사 회차별 md 의 표 안 수를 기계 대조.
|
|
|
|
1) 개별직종 표: md ↔ PDF 글자층을 다른 방식(직종코드 앵커 분할 · split_cost_docs.parse_cak_table 와 같은 방식)으로 읽은 값
|
|
2) 회차 사이: 같은 (직종, 공표일) 값이 여러 회차에 실려 있으면 서로 같은지
|
|
3) 평균임금 표(쪽 4): 회차 사이 같은 공표일 행이 서로 같은지
|
|
|
|
사용: python cak_verify.py (노임단가_건설업_대한건설협회 아래 <적용일>/ 폴더 전부)
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pymupdf as fitz
|
|
|
|
BASE = Path(__file__).resolve().parent.parent / "원가계산" / "노임단가_건설업_대한건설협회"
|
|
NUM = r"\(※\d{1,3}(?:,\d{3})+\)|\d{1,3}(?:,\d{3})+|(?<![\d,])-(?![\d,])"
|
|
|
|
|
|
def md_rows(folder):
|
|
"""md → {코드: (이름, 신뢰도, {공표일: 값})}"""
|
|
f = next(folder.glob("3. *.md"))
|
|
out, dates = {}, None
|
|
for ln in f.read_text(encoding="utf-8").splitlines():
|
|
if ln.startswith("| 직종코드"):
|
|
dates = [c.strip() for c in ln.strip("|").split("|")][3:]
|
|
continue
|
|
m = re.match(r"\| (\d{4}) \| (.*?) \| (\**) \| (.*) \|$", ln)
|
|
if m and dates:
|
|
vals = [c.strip() for c in m.group(4).split("|")]
|
|
assert len(vals) == 4, ln
|
|
out[m.group(1)] = (m.group(2), m.group(3), dict(zip(dates, vals)))
|
|
return out
|
|
|
|
|
|
def pdf_rows(folder, pages=(9, 10, 11, 12)):
|
|
"""PDF 글자층 → 앵커 분할로 {코드: (이름, 신뢰도, [4값])}"""
|
|
doc = fitz.open(next(folder.glob("*.pdf")))
|
|
t = "".join(doc[p].get_text() + "\n" for p in pages)
|
|
parts = re.compile(r"(?<![\d,.])([1-5]\d{3})(?![\d,.])").split(t)
|
|
res = {}
|
|
for i in range(1, len(parts) - 1, 2):
|
|
code, seg = parts[i], parts[i + 1]
|
|
if not (1 <= int(code[1:]) <= 99):
|
|
continue
|
|
fm = re.search(r"(\*{1,2})\s*$", parts[i - 1])
|
|
v = re.findall(NUM, seg)[:4]
|
|
f = re.search(NUM, seg)
|
|
nm = "".join(re.findall(r"[가-힣A-Za-z/]", seg[: f.start()] if f else seg))
|
|
if code not in res and len(v) == 4 and nm:
|
|
res[code] = (nm, fm.group(1) if fm else "", v)
|
|
return res
|
|
|
|
|
|
def avg_rows(folder):
|
|
"""쪽 4 평균임금 표 → {공표일 앞부분: [칸들]}"""
|
|
for f in folder.glob("*.md"):
|
|
if f.name[0].isdigit() and f.name[1] == ".":
|
|
continue
|
|
if f.name.startswith("_"):
|
|
continue
|
|
full = f
|
|
out = {}
|
|
on = False
|
|
for ln in full.read_text(encoding="utf-8").splitlines():
|
|
if ln.startswith("| 공표일 (조사기준)"):
|
|
on = True
|
|
continue
|
|
if on and ln.startswith("|---"):
|
|
continue
|
|
if on and ln.startswith("|"):
|
|
c = [x.strip() for x in ln.strip("|").split("|")]
|
|
out[re.sub(r"\(.*", "", c[0]).strip()] = c[1:]
|
|
elif on:
|
|
break
|
|
return out
|
|
|
|
|
|
def main():
|
|
folders = sorted(
|
|
d for d in BASE.iterdir() if d.is_dir() and re.fullmatch(r"\d{4}-\d\d-\d\d", d.name)
|
|
)
|
|
series, conflicts, bad = {}, 0, 0
|
|
avg = {}
|
|
for d in folders:
|
|
m = md_rows(d)
|
|
p = pdf_rows(d) if any(d.glob("*.pdf")) else {}
|
|
if p and set(m) != set(p):
|
|
print(d.name, "직종 수 다름", len(m), len(p), sorted(set(m) ^ set(p))[:5])
|
|
bad += 1
|
|
for c in m:
|
|
if c not in p:
|
|
continue
|
|
vals = list(m[c][2].values())
|
|
if vals != p[c][2] or m[c][1] != p[c][1]:
|
|
bad += 1
|
|
print(d.name, c, "md", m[c][1], vals, "pdf", p[c][1], p[c][2])
|
|
for k, v in m[c][2].items():
|
|
s = series.setdefault(c, {})
|
|
if k in s and s[k][0] != v:
|
|
conflicts += 1
|
|
print("회차 사이 다름", c, k, s[k], v, d.name)
|
|
s.setdefault(k, (v, d.name))
|
|
for k, v in avg_rows(d).items() if any(d.glob("*.pdf")) else []:
|
|
if k in avg and avg[k][0] != v:
|
|
conflicts += 1
|
|
print("평균임금 회차 사이 다름", k, avg[k], v, d.name)
|
|
avg.setdefault(k, (v, d.name))
|
|
print(d.name, "직종", len(m), "md↔PDF 다른 행", 0 if not bad else "위 참조")
|
|
print("md↔PDF 이상", bad, "· 회차 사이 충돌", conflicts)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|