- 로직_산림품셈_10장: 17줄(골재운반 · 중기운반 트레일러 · 모노레일 · 케이블 크레인 · 1톤 덤프 · 헬리콥터 · 불도저 운반) - 로직_산림품셈_11장: 5줄(콘테이너 · 규준틀 둘 · 쇄석 부설 작업량+돈) - 로직_산림품셈_14장: 2줄(입목뿌리 밑막이 · 근주이식) - 로직_건설품셈_공통8장: 고르기 넓힘(덤프 15ton · 트레일러 20ton · 불도저 · 집게 조합 0.8㎥) - 시험 둘 더함(16 통과) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
187 lines
7.0 KiB
Python
187 lines
7.0 KiB
Python
"""마스터 데이터 검사 도구 — 견본 셋 손 계산 · 견본 파일 검사 통과 (`resources/master_data/scripts/check_master.py`)."""
|
||
|
||
import sys
|
||
from decimal import ROUND_HALF_UP, Decimal
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "master_data/scripts"))
|
||
|
||
import check_master as cm # noqa: E402
|
||
import master_formula as mf # noqa: E402
|
||
|
||
SAMPLES = [
|
||
"계수_건설품셈_공통8장.json",
|
||
"계수_산림품셈_9장.json",
|
||
"기계_건설품셈.json",
|
||
"로직_건설품셈_공통8장.json",
|
||
"로직_산림품셈_10장.json",
|
||
"로직_산림품셈_11장.json",
|
||
"로직_산림품셈_13장.json",
|
||
"로직_산림품셈_14장.json",
|
||
"로직_산림품셈_9장.json",
|
||
"소요량_산림품셈_13장.json",
|
||
]
|
||
EXCAVATOR = "건설품셈:8 굴착기(무한궤도) 시간당 경비"
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def whole():
|
||
return cm.master()
|
||
|
||
|
||
def cents(x) -> str:
|
||
return str(Decimal(x).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
|
||
|
||
|
||
def test_메쌓기(whole):
|
||
got = mf.run(
|
||
whole,
|
||
"산림품셈:13-4-1 메쌓기",
|
||
{"뒷길이": 35, "돌": "견치돌", "쌓기": "골쌓기", "높이": 2, "초과증가율": 80},
|
||
)
|
||
assert cents(got["계"]) == "203281.20"
|
||
|
||
|
||
def test_메쌓기_높이_증가율(whole):
|
||
got = mf.run(
|
||
whole,
|
||
"산림품셈:13-4-1 메쌓기",
|
||
{"뒷길이": 35, "돌": "견치돌", "쌓기": "골쌓기", "높이": 5, "초과증가율": 80},
|
||
)
|
||
assert [line["수량"] for line in got["줄"]] == [Decimal("0.70"), Decimal("0.56")]
|
||
|
||
|
||
def test_굴착기(whole):
|
||
got = mf.run(
|
||
whole, EXCAVATOR, {"기계": "0201-0070", "지역": "전국평균", "보정작업": "해당 없음"}
|
||
)
|
||
assert [cents(got[k]) for k in ("노무비", "재료비", "경비", "계")] == [
|
||
"35412.13",
|
||
"26130.11",
|
||
"24554.00",
|
||
"86096.24",
|
||
]
|
||
|
||
|
||
def test_굴착기_암석_보정(whole):
|
||
got = mf.run(
|
||
whole,
|
||
EXCAVATOR,
|
||
{"기계": "0201-0070", "지역": "전국평균", "보정작업": "암석작업(연암・보통암・경암)"},
|
||
)
|
||
assert got["중간"]["손료"] == 28322
|
||
assert cents(got["계"]) == "89864.24"
|
||
|
||
|
||
def test_노체포설(whole):
|
||
assert mf.run(whole, "산림품셈:9-16-1 노체포설 작업량", {})["결과"] == Decimal("24.255")
|
||
got = mf.run(whole, "산림품셈:9-16-1 노체포설", {"지역": "전국평균", "보정작업": "해당 없음"})
|
||
assert [cents(got[k]) for k in ("노무비", "재료비", "경비", "계")] == [
|
||
"1459.99",
|
||
"1077.31",
|
||
"1012.33",
|
||
"3549.63",
|
||
]
|
||
|
||
|
||
def test_범위규칙_이상_미만(whole):
|
||
"""9-2 횡단경사 「60% 미만 · 60% 이상」 — 60 은 윗줄."""
|
||
qty = [
|
||
mf.run(whole, "산림품셈:9-2 노선 굴진 보조원", {"횡단경사": x})["줄"][0]["수량"]
|
||
for x in (59.9, 60)
|
||
]
|
||
assert qty == [5, 10]
|
||
|
||
|
||
def test_분수_글자_값(whole):
|
||
"""9-4-2 f 원문 「1/1.40」 — 3600×0.7×0.55×(1/1.40)×0.35/20."""
|
||
assert mf.run(whole, "산림품셈:9-4-2 암절취 집토 작업량", {})["결과"] == Decimal("17.325")
|
||
|
||
|
||
def test_범위규칙_이하_위끝만(whole):
|
||
"""13-4-2 뒷길이 「35cm 이하 · 55cm 이하」 — 36 은 55 칸."""
|
||
region = {"지역": "전국평균", "보정작업": "해당 없음"}
|
||
qty = [
|
||
mf.run(whole, "산림품셈:13-4-2 메쌓기(장비)", {"뒷길이": x, **region})["줄"][0]["수량"]
|
||
for x in (35, 36, 75)
|
||
]
|
||
assert qty == [Decimal("0.1"), Decimal("0.09"), Decimal("0.08")]
|
||
|
||
|
||
def test_범위규칙_마지막칸_이하_포함(whole):
|
||
"""13-6-1 직경 「80㎝이상∼100㎝이하」 — 100 은 맞고 101 은 막힘."""
|
||
given = {"직경": 100, "지역": "전국평균", "보정작업": "해당 없음"}
|
||
assert mf.run(whole, "산림품셈:13-6-1 큰돌 메쌓기", given)["줄"][0]["수량"] == Decimal("0.68")
|
||
with pytest.raises(mf.FormulaError):
|
||
mf.run(whole, "산림품셈:13-6-1 큰돌 메쌓기", {**given, "직경": 101})
|
||
|
||
|
||
def test_굴착기_부착용_집게(whole):
|
||
"""잡재료 16%(8-4-8 주⑤) · 집게 손료 7,783천원×7435×10⁻⁷ 버림."""
|
||
got = mf.run(
|
||
whole,
|
||
"건설품셈:8 굴착기+부착용 집게 시간당 경비",
|
||
{"기계": "0201-0060", "집게": "7206-0070", "지역": "전국평균", "보정작업": "해당 없음"},
|
||
)
|
||
assert got["중간"]["집게손료"] == 5786
|
||
assert cents(got["계"]) == "86467.61"
|
||
|
||
|
||
def test_불도저_운반_작업량(whole):
|
||
"""10-11 — q=3.2×0.96 · ㎝=20/55+20/70+0.25 · Q=60×q×(1/1.30)×0.55/㎝."""
|
||
got = mf.run(whole, "산림품셈:10-11 불도저 운반 작업량", {"재료": "토사"})
|
||
assert got["결과"] == Decimal("86.709")
|
||
|
||
|
||
def test_중기운반_트레일러_횟수(whole):
|
||
"""10-4 — ㎝=20+30+20+0.42 · N=60×0.9/㎝ · 1회 = 1/N 시간."""
|
||
got = mf.run(whole, "산림품셈:10-4 중기운반 트레일러", {"t2": 30, "지역": "전국평균"})
|
||
assert got["줄"][0]["수량"].quantize(Decimal("0.0001")) == Decimal("1.3041")
|
||
|
||
|
||
def test_범위_입력은_막음(whole):
|
||
with pytest.raises(mf.FormulaError):
|
||
mf.run(
|
||
whole,
|
||
"산림품셈:13-4-1 메쌓기",
|
||
{"뒷길이": 35, "돌": "견치돌", "쌓기": "골쌓기", "높이": 8, "초과증가율": 120},
|
||
)
|
||
|
||
|
||
def test_견본_파일_검사_통과(whole):
|
||
files = cm.load(SAMPLES)
|
||
assert len(files) == len(SAMPLES)
|
||
assert [x for name, data in files.items() for x in cm.check_form(name, data)] == []
|
||
# 9장 계수 표는 같은 절의 소요량 표와 함께 대조 — 소요량 파일 자체의 알림은 그 창 몫
|
||
with_09 = files | cm.load(["소요량_산림품셈_09장.json"])
|
||
# 절 통째를 출처로 둔 다른 창 표(9-13 · 9-20)의 결손도 그 창 몫
|
||
# 8-4-8 주⑤ 표는 절의 운전경비 수가 기계 요소(표 그룹 밖)에 있어 결손으로 뜸
|
||
others = ("9-13 관보호공 인력 가산", "9-20 대상 근주", "8-4-8 주⑤ 조합 잡재료율")
|
||
body = cm.check_body(with_09)
|
||
assert [
|
||
x for x in body if not x.startswith("소요량_산림품셈_09장") and not x.endswith(others)
|
||
] == []
|
||
assert cm.check_logics(files, whole) == []
|
||
|
||
|
||
def test_로직_검사가_틀린_열쇠를_잡음(whole):
|
||
row = {
|
||
"열쇠": "시험",
|
||
"입력": [{"이름": "지역", "고르기": ["전국평균", "없는곳"]}],
|
||
"중간": [{"이름": "x", "식": "찾기(계수:산림품셈:9-16-1 노체포설, 없는조건='K').적용"}],
|
||
"호표": [
|
||
{"종류": "재료", "요소": "재료:오피넷유가:경유:{지역}", "수량": "y", "비목": "재료비"}
|
||
],
|
||
"덧줄": [],
|
||
}
|
||
found, _ = mf.check_logic(whole, "산림품셈", row)
|
||
assert any("없는조건" in x for x in found)
|
||
assert any("경유:없는곳" in x for x in found)
|
||
assert any("「y」" in x for x in found)
|
||
|
||
|
||
def test_돌고_도는_참조():
|
||
assert mf.find_loops({"가": {"나"}, "나": {"가"}}) == [["가", "나", "가"]]
|