"""마스터 데이터 검사 도구 — 견본 셋 손 계산 · 견본 파일 검사 통과 (`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", "로직_산림품셈_13장.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): 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"]) assert [x for x in cm.check_body(with_09) if not x.startswith("소요량_산림품셈_09장")] == [] 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({"가": {"나"}, "나": {"가"}}) == [["가", "나", "가"]]