feat(master_data): 노임 마스터화 — 원문 md 다섯 갈래 노임 · 준용대상 짝짓기·통합 · 미공표 산정 · ESTX 항목 비교

- 새 요소 파일(우리 md 에서만 · 판 2026-01-01): 엔지니어링노임 56 · 측량노임 20 · 건설사업관리노임 4 · SW노임 17 · 산림노임 2 — 빌더 build_인력_노임.py · 줄 수·값 md 기계 대조
- 준용대상 49 → 48: 측량중급기술자 옮김(로직 참조 8) · 원문 통합 목록 5 직종 통합 칸(목도 · 특수비계공 · 함석공 · 갱부 · 스레이트공) · 절단공 갈림 · 비슷한 이름 21 후보
- 건설노임 미공표 14 산정{값 · 근거 · 마지막공표} — 원문 회차 평균 그대로 이음 · 석조각공·드잡이공편수 다산소프트와 이음 계열 다름 비고 · 2026-09-01 적용분 확보됨 비고
- 엔진 차례 값 → 준용 → 통합 → 산정 · 호표 출처 · M01 준용 후보 7 노임 파일 · 통합·산정·후보 칸 읽기 전용
- ref/_비교_estx.md 10절: ESTX 378 줄 중 371 짝 · 짝 없는 직종 42 · 일괄 시험 계산 통과 1104 → 1115

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-20 01:34:24 +09:00
co-authored by Claude Opus 5
parent ab0f683d2b
commit 3f4fd66d21
19 changed files with 988 additions and 74 deletions
+34 -4
View File
@@ -609,7 +609,7 @@ def test_범위규칙_이상이하():
def test_미공표_준용():
"""값 없는 직종에 준용 열쇠 — 그 직종 값으로 셈 · 호표 출처에 「준용: 이름」 · 준용 없으면 멈춤."""
"""값 없는 직종 — 차례 값 → 준용 → 통합 → 산정 · 호표 출처에 「준용: 이름」·「통합: 이름」·「산정: …」 · 다 없으면 멈춤."""
files = cm.load()
lines = [
{
@@ -621,11 +621,15 @@ def test_미공표_준용():
}
]
files["_시험"] = {"그룹": "로직", "원문": "시험", "줄": [{"열쇠": "준용", "호표": lines}]}
row = next(r for r in files["인력_건설노임.json"]["줄"] if r["열쇠"] == "1032")
assert row["값"] is None and "준용" in row and row["산정"]["값"] == Decimal("211645.83")
got = mf.run(mf.Master(files), "시험:준용", {})
assert got["줄"][0]["출처"] == "산정: 원문 4-라 · 마지막 공표 2025.9.1"
guess = row.pop("산정")
with pytest.raises(mf.FormulaError):
mf.run(mf.Master(files), "시험:준용", {})
row = next(r for r in files["인력_건설노임.json"]["줄"] if r["열쇠"] == "1032")
assert row["값"] is None and "준용" in row
row["준용"] = Decimal(1002) # M01 이 수로 적어도 열쇠로 읽음
row["산정"] = guess
row["준용"] = Decimal(1002) # M01 이 수로 적어도 열쇠로 읽음 · 산정보다 앞
got = mf.run(mf.Master(files), "시험:준용", {})
assert got["줄"][0]["출처"] == "준용: 보통인부"
assert got["노무비"] == 2 * Decimal(172068)
@@ -636,6 +640,32 @@ def test_미공표_준용():
mf.run(mf.Master(files), "시험:준용", {})
alias["준용"] = "인력:건설노임:1002"
assert mf.run(mf.Master(files), "시험:준용", {})["줄"][0]["출처"] == "준용: 보통인부"
lines[0]["요소"] = "인력:준용대상:갱부" # 원문 통합 목록의 옛 직종 — 새 직종 값
got = mf.run(mf.Master(files), "시험:준용", {})
assert got["줄"][0]["출처"] == "통합: 특별인부"
def test_노임_갈래_파일_원문_대조():
"""엔지니어링·측량·건설사업관리·SW·산림 노임 — 줄 수 · 이름 표본 · 준용대상에서 측량중급기술자 옮김."""
files = cm.load()
count = {
b: len(files[f"인력_{b}.json"]["줄"])
for b in ("엔지니어링노임", "측량노임", "건설사업관리노임", "SW노임", "산림노임")
}
assert count == {
"엔지니어링노임": 56,
"측량노임": 20,
"건설사업관리노임": 4,
"SW노임": 17,
"산림노임": 2,
}
whole = mf.Master(files)
assert whole.value("인력:엔지니어링노임:기술사(건설)")[0] == 467217
assert whole.value("인력:측량노임:측량중급기술자")[0] == 251596
assert whole.value("인력:건설사업관리노임:특급건설사업관리기술인")[0] == 399247
assert whole.value("인력:SW노임:IT PM")[0] == 492039
assert whole.value("인력:산림노임:나무의사")[0] == 326359
assert "측량중급기술자" not in {r["열쇠"] for r in files["인력_준용대상.json"]["줄"]}
def test_품셈재료_연결_낮은_값과_출처(whole):