- 값 = 마지막 공표 조사월 값 × 조사월별 전체 평균 증가율을 이어 곱하되 곱할 때마다 원 미만 버림(건설노임 4-라 방식 준용 · 제조 원문에 규정 없음) - 상태 「미공표」 → 「산정」 · 비고 「산정: <조사월> <값>원 × 평균 증가율(원 미만 버림) — 건설노임 4-라 방식 준용 · 제조 원문에 규정 없음」 - 빌더 build_인력_노임.py 에 estimate_make 추가 — 다시 돌려도 같은 값 · 추적 md 5절 버림 열과 기계 대조 - 구분 머리 제조업 「원천」 에 옛 회차 11 개 「3. 직종별 조사노임.md」 와 미공표직종_추적.md 를 역할 「산정근거」 로 더함 - kbiz_track_unpublished.py 에 「단계마다 버림(채택)」 열 추가 · cak_track_unpublished.py 도 같은 말로 맞춤(건설 md 는 그대로 다시 나옴) - 시험 — 인력 상태 집합에서 「미공표」 빠짐 · 제조 산정 값 한 줄 대조 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
228 lines
14 KiB
Python
228 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""제조노임 미공표 직종 추적표 · 직종코드 변천표 생성.
|
||
|
||
입력: 노임단가_제조업_중소기업중앙회/<적용일>/ 회차 md 의 「3. 직종별 조사노임」 표(값은 md 에서만 읽음) + 기본 폴더(2026-07-01).
|
||
출력: 노임단가_제조업_중소기업중앙회/미공표직종_추적.md · 직종코드_변천_대조표.md
|
||
사용: python kbiz_track_unpublished.py
|
||
- 미공표 직종 = 가장 최근 회차(2026-07-01 적용)에서 조사노임 칸이 「*」 인 직종.
|
||
- 산정값 = 마지막 공표값 × (최근 조사월 전체평균 ÷ 그 공표 조사월 전체평균) — 공표 조사월 단계별 증가율을 이어 곱한 값과 같음.
|
||
"""
|
||
|
||
import re
|
||
from decimal import ROUND_FLOOR, Decimal
|
||
from pathlib import Path
|
||
|
||
from kbiz_verify import BASE, load_rounds
|
||
|
||
TARGET_ROUND = "2026-07-01"
|
||
MONTH_KEY = lambda s: (int(s.split(".")[0]), int(s.split(".")[1])) # noqa: E731
|
||
|
||
|
||
def num(s):
|
||
return int(s.replace("*", "").replace(",", ""))
|
||
|
||
|
||
def series(rs):
|
||
"""(직종명 → {조사월: 값문자}) · 조사월 순서 · 조사월 → 전체평균 문자 · 조사월 → (전체평균을 실은 회차들)."""
|
||
val, avg, avg_src = {}, {}, {}
|
||
own = {}
|
||
for lab, r in rs.items():
|
||
for name, (code, vs) in r["rows"].items():
|
||
for k, (m, v) in enumerate(zip(r["months"], vs)):
|
||
if k == 0:
|
||
own[(name, m)] = v # 그 조사월을 처음 낸 회차의 값(표기 그대로)
|
||
val.setdefault(name, {}).setdefault(m, v)
|
||
a = [x for x in (r["avg"] or []) if x != "-"]
|
||
for m, x in zip(r["months"], a):
|
||
avg.setdefault(m, x)
|
||
avg_src.setdefault(m, []).append((lab, x))
|
||
for (name, m), v in own.items():
|
||
val[name][m] = v
|
||
months = sorted({m for n in val for m in val[n]}, key=MONTH_KEY)
|
||
return val, months, avg, avg_src
|
||
|
||
|
||
def mlabel(m):
|
||
y, mm = m.split(".")
|
||
return f"{y}.{int(mm)}"
|
||
|
||
|
||
def build_track(rs):
|
||
val, months, avg, avg_src = series(rs)
|
||
last = rs[TARGET_ROUND]
|
||
latest_month = last["months"][0]
|
||
un = [n for n, (c, v) in last["rows"].items() if v[0].replace("*", "").strip() in ("", "-")]
|
||
un.sort(key=lambda n: last["rows"][n][0])
|
||
lines = []
|
||
A = lines.append
|
||
A("# 제조노임 미공표 직종 추적 — 마지막 공표값 · 전체 평균 증가율 이음 (참고 계산)\n")
|
||
A(
|
||
"> 지식DB 근거 문서 — 값을 확정하지 않음. 원 미만 처리 · 어느 시기 값으로 이을지는 사용자 협의로 정함. "
|
||
"마스터 인력 「값」 은 5절 「단계마다 버림(채택)」 열(사용자 확정 · 건설노임과 같은 방식).\n"
|
||
"> 생성: `original/_pipeline/kbiz_track_unpublished.py` (회차 md 에서 값을 읽음)."
|
||
)
|
||
A("\n## 1. 원문 규칙\n")
|
||
A(
|
||
"- **중소기업중앙회 「중소제조업 직종별 임금조사 보고서」 에는 조사되지 않은(`*`) 직종의 노임을 어떻게 적용하라는 규정이 없음.** "
|
||
"2020 상반기 ~ 2026 상반기 12 회차 원문(`<적용일>/2. 조사결과 요약.md` 「조사노임 적용시 유의사항」 · `3. 직종별 조사노임.md` 표 밑 「주」)을 모두 찾아 확인함."
|
||
)
|
||
A("- 원문에 있는 것은 기호 뜻 뿐 (문장 그대로):")
|
||
A(
|
||
" - 2020.7 ~ 2023.7 적용 회차 「조사노임 적용시 유의사항」: 「‘*’ 표시는 조사업체가 3개 미만이거나 표본수가 적고 변동계수가 커 신뢰성을 확보하기 어려운 일부 직종으로 이번 조사결과 공표에서는 제외하며, ‘**’ 표시는 5개 미만인 직종으로 활용시 주의 필요함」"
|
||
)
|
||
A(
|
||
" - 2024.1 ~ 2026.7 적용 회차 「특수기호의 정의」: 「‘*’ 표시는 조사업체가 3개 미만이거나 표본수가 적은 경우, 또는 변동계수가 커 신뢰성을 확보하기 어려운 직종으로 ‘공표 제외’」 · 「‘**’ 표시는 응답기업 5개 미만인 직종으로 활용시 주의가 필요함」"
|
||
)
|
||
A(
|
||
" - 조사노임 표 밑 「주2」 (전 회차): 「‘-’표시는 조사되지 않음, ‘*’표시는 조사업체가 3개 미만이거나 표본수가 적고 변동계수가 커지는 등 신뢰성을 확인하기 어려운 직종, ‘**’표시는 5개 미만을 나타내는 것으로 활용시 주의 필요함」"
|
||
)
|
||
A(
|
||
" - 「주3」: 「(5~7)식품가공원, (20~21)일반화학원, (23~25)배합원, (125~126)특수차운전원의 통합단가는 2019년까지만 발표하고 2020년부터는 발표하지 아니함」"
|
||
)
|
||
A(
|
||
"- 건설업 임금실태조사(대한건설협회) 임금적용요령 4-라 의 「전후 시기 값 × 전체 평균 증가율」 같은 규정은 이 보고서에 **없음**. "
|
||
"아래 산정값은 건설 쪽 방식을 같은 모양으로 대입해 본 계산이며 원문 근거가 없음 — 사용자 확정으로 마스터에 반영(5절 버림 열)."
|
||
)
|
||
A(
|
||
"- 산정식(마지막 공표 조사월 L → "
|
||
f"{mlabel(latest_month)}): **산정값 = L 의 공표값 × Π(조사월마다 전체 평균 ÷ 앞 조사월 평균)** = "
|
||
f"{mlabel(latest_month)} 전체 평균 ÷ L 전체 평균 (가운데 항이 지워짐). 「원 미만 처리」 규정 없음 → 처리 없는 값과 조사월 단계마다 버린 값을 5절에 함께 적음."
|
||
)
|
||
A(
|
||
"- 「전체 평균 조사노임」 = 「전체평균」 줄(조사노임표 맨 끝) · 조사결과 요약 「전체 조사직종(129개)의 평균 조사노임(일급)」 과 같음. "
|
||
"원문 주: 각 직종별 평균은 단순평균값, 전체 조사직종의 평균은 가중평균."
|
||
)
|
||
A("\n## 2. 조사월별 전체 조사직종 평균 조사노임 (원문 인용)\n")
|
||
A("| 조사월 | 전체 평균(원) | 실린 회차(적용일) | 회차 사이 일치 |")
|
||
A("|---|---|---|---|")
|
||
for m in months:
|
||
srcs = avg_src.get(m, [])
|
||
same = len({x for _, x in srcs}) == 1
|
||
A(
|
||
f"| {mlabel(m)} | {avg[m]} | {' · '.join(l for l, _ in srcs)} | {'같음' if same else '다름 ' + str(srcs)} |"
|
||
)
|
||
A(
|
||
"\n- 표는 각 회차 「3. 직종별 조사노임」 표 맨 끝 「전체평균」 줄(회차마다 조사월 3 칸). "
|
||
"같은 조사월이 여러 회차에 실려 있어 회차 사이 일치를 확인함. 직종수: 2020.3 ~ 2020.8 조사는 130 직종, 2021.3 조사부터 129 직종(90.자동차정비원 제외) — 이음 계열이 바뀌는 자리에서도 회차 사이 같은 조사월 평균은 같은 값으로 실려 있음."
|
||
)
|
||
A(f"\n## 3. {len(un)} 직종 공표 이력 (원문 md 인용 · `*` = 미공표 · `-` = 조사되지 않음)\n")
|
||
hdr = "| 직종 (최근 판 코드) | " + " | ".join(mlabel(m) for m in months) + " |"
|
||
A(hdr)
|
||
A("|---|" + "---|" * len(months))
|
||
for n in un:
|
||
code = last["rows"][n][0]
|
||
A(f"| {code} {n} | " + " | ".join(val[n].get(m, "") for m in months) + " |")
|
||
A(
|
||
"\n- 값 출처: 각 조사월을 처음 낸 회차 폴더의 「3. 직종별 조사노임.md」 (표기 `**` 는 그 회차 표기 그대로). 직종 코드는 2024.10 적용 회차에서 재정렬됨 — 「직종코드_변천_대조표.md」 참조. "
|
||
"같은 조사월 값이 여러 회차에 실려 있으면 숫자는 모두 같음(`**` 표기만 회차에 따라 다를 수 있음) — 회차 사이 대조로 확인함."
|
||
)
|
||
A("\n## 4. 이음 단계 (조사월 전체 평균 ÷ 앞 조사월 평균)\n")
|
||
A("| 앞 → 뒤 | 분자(뒤) | 분모(앞) | 증가율 |")
|
||
A("|---|---|---|---|")
|
||
step = {}
|
||
for a, b in zip(months, months[1:]):
|
||
r = num(avg[b]) / num(avg[a])
|
||
step[b] = r
|
||
A(f"| {mlabel(a)} → {mlabel(b)} | {avg[b]} | {avg[a]} | {r:.6f} |")
|
||
A(
|
||
"\n- 조사월 간격: 반기(3월 · 8월 조사) → 2024년은 연 1회(2024.6) → 2025.3 부터 다시 반기. "
|
||
"원문 연혁: 「2024. 5 작성주기 변경(연2회 → 연1회)」 · 「2025. 1 작성주기 변경(연1회 → 연2회)」. "
|
||
"이음은 실린 조사월끼리 곱함(2023.8 → 2024.6 은 두 조사월 평균의 비)."
|
||
)
|
||
A(f"\n## 5. {mlabel(latest_month)} 조사(2026-07-01 적용) 산정값\n")
|
||
A(
|
||
"| 직종 | 마지막 공표 조사월 | 그 값(원) | 이어 곱한 증가율 | 산정값(처리 없음) | 단계마다 버림(채택) | 비고 |"
|
||
)
|
||
A("|---|---|---|---|---|---|---|")
|
||
rows = []
|
||
for n in un:
|
||
code = last["rows"][n][0]
|
||
pub = [(m, val[n][m]) for m in months if val[n].get(m, "*") not in ("*", "-", "")]
|
||
m, v = pub[-1]
|
||
i = months.index(m)
|
||
mult, floor = 1.0, Decimal(num(v))
|
||
for a, b in zip(months[i:], months[i + 1 :]):
|
||
mult *= step[b]
|
||
floor = (floor * num(avg[b]) / num(avg[a])).to_integral_value(rounding=ROUND_FLOOR)
|
||
est = num(v) * mult
|
||
ratio = num(avg[latest_month]) / num(avg[m])
|
||
assert abs(mult - ratio) < 1e-9
|
||
note = "값에 `**`(5개 미만) 표기" if "**" in v else ""
|
||
rows.append((code, n, m, v, mult, est, int(floor), note))
|
||
A(f"| {code} {n} | {mlabel(m)} | {v} | {mult:.6f} | {est:,.2f} | {int(floor):,} | {note} |")
|
||
A(
|
||
"\n- 「단계마다 버림(채택)」 = 조사월 단계마다 원 미만을 버리며 이은 값(건설노임과 같은 방식) — 마스터 인력 「값」 은 이 열의 원 단위 정수."
|
||
"\n- 「산정값(처리 없음)」 = 버림 없이 이은 값 — 견주기용으로만 둠."
|
||
"\n- 「마지막 공표」 는 `**`(응답업체 5개 미만 · 활용시 주의) 표기가 붙은 값도 공표값으로 보아 셈 — `*` · `-` 만 값 없음."
|
||
"\n- 「그 값」 은 마지막으로 값이 실린 조사월의 공표 표기 그대로(`**` 포함). 산정값은 `**` 를 뗀 숫자에 곱함."
|
||
"\n- 마지막 공표값이 2020 이전인 직종은 없음 — 전 직종의 마지막 공표값이 2020.8 이후 회차에서 나와 2019 이전 회차는 받지 않음."
|
||
"\n- 최근 조사월 전체 평균 "
|
||
f"{avg[latest_month]}원 = 「조사결과 요약」 의 전체 조사직종(129개) 평균 조사노임."
|
||
)
|
||
return "\n".join(lines) + "\n", rows, un, months, val
|
||
|
||
|
||
def build_codes(rs):
|
||
labs = list(rs)
|
||
names = {}
|
||
for lab in labs:
|
||
for n, (c, v) in rs[lab]["rows"].items():
|
||
names.setdefault(n, {})[lab] = c
|
||
o = []
|
||
A = o.append
|
||
A("# 직종코드 변천·대조표 — 회차별 직종코드 (조사노임표 원문 그대로)\n")
|
||
A(
|
||
"> 지식DB 근거 문서. 생성: `original/_pipeline/kbiz_track_unpublished.py` — 각 회차 「3. 직종별 조사노임」 표의 「직종코드 및 직종명」 칸에서 읽음."
|
||
)
|
||
A(
|
||
"\n## 1. 원문에 실린 대조표 (각 회차 `3. 직종별 조사노임.md` 안 「5. 이전 직종코드·직종명으로 새 직종명 찾기」)\n"
|
||
)
|
||
A(
|
||
"- 모든 회차(2020.7 ~ 2026.7 적용)가 「기존 직종코드 및 직종명(~2018)」 → 「새로운 직종코드 및 직종명(2019~)」 표(124 줄)를 싣고 있음. "
|
||
"2020.7 ~ 2024.1 적용 회차 8 개는 표가 같고, 2024.10 · 2025.7 · 2026.1 · 2026.7 적용 회차 4 개는 「새로운 직종코드」 칸이 재정렬 뒤 코드로 바뀐 판(두 판의 다른 줄 144 · 뒤 4 회차끼리는 같음 — 회차 사이 대조로 확인). "
|
||
"2020.7 · 2021.1 적용 회차 게시글에는 같은 표의 HWP(`이전_직종명으로_새_직종명_찾기.hwp` → 같은 이름 md) 가 첨부돼 있음."
|
||
)
|
||
A(
|
||
"- 이 HWP 첨부의 「34.유리절단및재단원」 줄은 「조사보류」 로 적혀 있고 PDF 본문 표는 「130.유리절단및재단원」 — 원문 그대로 둘 다 옮김."
|
||
)
|
||
A("\n## 2. 회차 사이 직종코드가 바뀐 자리\n")
|
||
prev = None
|
||
for lab in labs:
|
||
if prev:
|
||
ch = [
|
||
n
|
||
for n in rs[lab]["rows"]
|
||
if n in rs[prev]["rows"] and rs[lab]["rows"][n][0] != rs[prev]["rows"][n][0]
|
||
]
|
||
drop = [n for n in rs[prev]["rows"] if n not in rs[lab]["rows"]]
|
||
add = [n for n in rs[lab]["rows"] if n not in rs[prev]["rows"]]
|
||
if ch or drop or add:
|
||
A(
|
||
f"- {prev} → {lab}: 코드 바뀐 직종 {len(ch)} · 빠진 직종 {len(drop)}{(' (' + ', '.join(f'{rs[prev]["rows"][n][0]}.{n}' for n in drop) + ')') if drop else ''} · 새 직종 {len(add)}"
|
||
)
|
||
prev = lab
|
||
A(
|
||
"- 코드가 바뀐 것은 2024.1 → 2024.10 적용 회차(원문 연혁 「2024. 5 … 조사직종 재정렬」) 한 번 뿐. 재정렬 전 코드 → 뒤 코드는 2024.10 적용 회차부터 1 절의 표에 반영돼 있고, 아래 3절은 회차별 조사노임표에서 이름으로 맞춘 대조."
|
||
)
|
||
A("\n## 3. 직종명 × 회차별 코드\n")
|
||
A("| 직종명 | " + " | ".join(labs) + " |")
|
||
A("|---|" + "---|" * len(labs))
|
||
key = lambda n: rs[labs[-1]]["rows"].get(n, rs[labs[0]]["rows"].get(n, (9999,)))[0] # noqa: E731
|
||
for n in sorted(names, key=key):
|
||
A(f"| {n} | " + " | ".join(str(names[n].get(l, "")) for l in labs) + " |")
|
||
A(
|
||
"\n- 빈 칸 = 그 회차 조사노임표에 없음(90.자동차정비원은 2021.7 적용 회차부터 조사 대상에서 제외 — 원문 각주 「2년 연속 무응답으로 2021년 상반기 조사부터 조사 대상에서 제외」). "
|
||
"2024.1 적용 회차까지 130.유리절단및재단원 · 2024.10 적용 회차부터 17."
|
||
)
|
||
return "\n".join(o) + "\n"
|
||
|
||
|
||
if __name__ == "__main__":
|
||
rs = load_rounds()
|
||
md, rows, un, months, val = build_track(rs)
|
||
(BASE / "미공표직종_추적.md").write_text(md, encoding="utf-8")
|
||
(BASE / "직종코드_변천_대조표.md").write_text(build_codes(rs), encoding="utf-8")
|
||
print("미공표", len(un), "직종 · 조사월", len(months))
|
||
for r in rows:
|
||
print(r[0], r[1], r[2], r[3], f"{r[4]:.6f}", f"{r[5]:,.2f}")
|