# -*- coding: utf-8 -*- """782-3 건설 공통 8-1-4 · 8-2 시공능력 · 기계설비 13-2-7 — 계수 표 146 · 로직 49 · 실무 엑셀 Q 재현 · check_master.""" import sys from decimal import Decimal as D 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 COEF = "계수_건설품셈_08장_건설기계.json" LOGIC = "로직_건설품셈_08장_건설기계.json" LOGIC13 = "로직_건설품셈_13장_플랜트설비공사.json" EXC, DUMP, ROLL_A, CHIP = "GC001147", "GC001155", "GC001158", "GC001189" K = { "0.9": "위의 토질보다 약간 단단한 토질로서 버킷에 거의 가득 채울 수 있는 모래, 보통토 및 조건이 좋은 점토인 경우", "0.7": "버킷에 가득 채우기가 어렵거나 가벼운 발파를 필요로 하는 것으로서 단단한 점토질, 점토, 역토질인 경우", "0.55": "버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우", } @pytest.fixture(scope="module") def whole(): return cm.master() def near(got, want, tol="0.005"): return abs(got / D(want) - 1) <= D(tol) def excavator(whole, q, k, f, e, angle, cycle=0): q = D(q) size = "0.12∼0.4" if q <= D("0.4") else "0.6∼0.8" if q <= D("0.8") else "1.0∼1.2" given = { "버킷용량": q, "규격구분": size, "선회각도": angle, "버킷조건": K[k], "현장조건": "흐트러진 상태", "등급": "보통", "토질명": "모래, 사질토", "E보정": "해당 없음", "협소현장": "해당 없음", "f": D(f), "E입력": D(e), "싸이클입력": cycle, } return mf.run(whole, EXC, given)["결과"] @pytest.mark.parametrize( "q, k, f, e, angle, want", [ ("1", "0.9", "0.7", "0.75", 135, "81"), # 북부청임도 단산4 적사 ("0.6", "0.55", "1", "0.35", 90, "23.1"), # 북부청임도 단산6 채집 ("0.6", "0.55", "1", "0.35", 135, "20.79"), # 북부청임도 단산6 상차 ("1", "0.7", "0.76923", "0.65", 90, "66.32"), # 북부청임도 단산27 ("1", "0.7", "1", "0.65", 135, "78"), # 북부청임도 단산52 ("1", "0.55", "1", "0.4", 135, "37.71"), # 북부청임도 단산53 ("0.7", "0.55", "0.67", "0.35", 180, "14.77"), # 북부청임도 단산75~80 집토 ("1", "0.55", "0.62", "0.4", 135, "23.38"), # 북부청임도 단산89 ("1", "0.55", "0.65", "0.4", 135, "24.51"), # 북부청사방 단산10 ("0.7", "0.9", "1", "0.75", 135, "85.05"), # 동부청영월 111 ("0.4", "0.55", "0.87", "0.45", 135, "17.23"), # 동부청영월 411 채집 ("0.4", "0.7", "0.87", "0.45", 135, "21.92"), # 동부청영월 411 상차 ], ) def test_굴착기_재현(whole, q, k, f, e, angle, want): assert near(excavator(whole, q, k, f, e, angle), want) def test_굴착기_엑셀_다른_싸이클(whole): # 영덕계류 1427 · 1438 — 엑셀이 0.12∼0.4㎥ 에 0.6∼0.8㎥ 줄 싸이클(135° 20초 · 90° 18초)을 씀 assert excavator(whole, "0.36", "0.55", "1", "0.35", 135) == D("13.860") # 표 18초 assert near(excavator(whole, "0.36", "0.55", "1", "0.35", 135, 20), "12.47") assert near(excavator(whole, "0.36", "0.55", "1", "0.45", 90, 18), "17.82") assert near(excavator(whole, "0.29", "0.55", "1", "0.45", 90, 18), "14.36") BASE_DUMP = { "f": 1, "도로상태": "교차가 힘든 산간지도로 및 제방 등의 도로", "적하토질": "모래, 역, 호박돌", "적하조건": "불량", "적재장소": "적재장소가 넓지는 않으나 목적장소에 불편없이 진입할 수 있을 때", "덮개": "해당 없음", "세륜": "안 함", "적재방법": "적재기계", "인력종류": "토사류", "적재시간입력": 0, "E입력": 0, } @pytest.mark.parametrize( "given, want", [ # 북부청임도 단산4 — 15t · γ 1.8 · L 1.25 · f 0.7 · 0.08㎞ · 굴착기 1.0㎥ 21초 ( { "적재용량": 15, "단위중량": D("1.8"), "체적변화율": D("1.25"), "f": D("0.7"), "운반거리": D("0.08"), "적재싸이클": 21, "적재버킷": 1, "버킷계수": D("0.9"), "적재효율": D("0.75"), }, "51.02", ), # 북부청임도 단산6 — 4.5t · γ 1.75 · 0.5㎞ ( { "적재용량": D("4.5"), "단위중량": D("1.75"), "체적변화율": 1, "운반거리": D("0.5"), "적재싸이클": 20, "적재버킷": D("0.6"), "버킷계수": D("0.55"), "적재효율": D("0.35"), }, "9.96", ), # 북부청임도 단산19 — 열악한 도로 7 · 8 · 좁은 적재장소 0.7분 ( { "적재용량": 15, "단위중량": D("1.9"), "체적변화율": D("1.3"), "f": D("0.77"), "운반거리": D("0.5"), "도로상태": "토취장 또는 토사장 등 열악한 조건의 도로", "적재장소": "적재장소가 좁아서 목적장소에 진입하는데 불편을 느낄 때", "적재싸이클": 21, "적재버킷": 1, "버킷계수": D("0.9"), "적재효율": D("0.85"), }, "29.36", ), # 동부청영월 411 — 적하 보통 0.8분 ( { "적재용량": D("4.5"), "단위중량": D("0.575"), "체적변화율": D("1.15"), "f": D("0.87"), "운반거리": D("0.5"), "적하조건": "보통", "적재싸이클": 18, "적재버킷": 1, "버킷계수": D("0.7"), "적재효율": D("0.45"), }, "28.59", ), ], ) def test_덤프트럭_재현(whole, given, want): assert near(mf.run(whole, DUMP, {**BASE_DUMP, **given})["결과"], want) def test_롤러_임목파쇄_브레이커(whole): roll = { "다짐기계": "진동롤러(자주식)", "규격": "10.0", "다짐층": "노체, 축제, 노상", "다짐횟수": 2, "E공종": "노체, 축제, 노상", "E기계": "불도저", "현장조건": "보통", "E입력": 0, } assert mf.run(whole, ROLL_A, roll)["결과"] == D("2280.000") # 북부청임도 단산27 assert ( mf.run(whole, CHIP, {"규격": "93.25㎾", "스크린": "75㎜", "작업조건": "보통"})["결과"] == 6 ) # 동부청영월 111 # 북부청임도 단산75~78 — 엑셀 Q = 구조물 헐기 표 범위 평균치(적용방법 ②) rows = {r["구분"]: r for r in whole.get("CC000065")["줄"]} mean = lambda v: (v[0] + v[1]) / 2 # noqa: E731 assert mean(rows["구조물의 평균두께 30㎝ 이상"]["무근구조물"]) == D("3.6") assert mean(rows["구조물의 평균두께 30㎝ 미만"]["무근구조물"]) == D("4.6") assert mean(rows["구조물의 평균두께 30㎝ 이상"]["철근구조물"]) == D("2.05") assert mean(rows["구조물의 평균두께 30㎝ 미만"]["철근구조물"]) == D("2.45") cost = mf.run( whole, "GC001166", { "작업능력": D("3.6"), "기계": "0201-0070", "브레이커": "0230-0007", "지역": "전국평균", "보정작업": "해당 없음", }, ) assert cost["줄"][0]["수량"] == 1 / D("3.6") def test_기본식_원가로직(whole): got = mf.run( whole, "GC001143", {"싸이클시간": 21, "시간단위": "초", "작업량": D("0.9"), "f": D("0.7"), "E": D("0.75")}, ) assert got["중간"]["n"] == D("171.43") and got["결과"] == D("81.001") rows = cm.load([LOGIC])[LOGIC]["줄"] cost = next(r for r in rows if r["키"] == "GC001148") got = mf.run(whole, "GC001148", cm.시험입력(cost)) assert got["줄"][0]["수량"] == 1 / D("81.000") def test_아세틸렌(whole): assert mf.run(whole, "GC001191", {"아세틸렌부피": 22400})["결과"] == 26 def test_check_master(): files, whole = cm.load([COEF, LOGIC, LOGIC13, "기계.json"]), cm.master() found = [x for name, data in files.items() for x in cm.check_form(name, data)] found += cm.check_body(files) + cm.check_logics(files, whole) assert found == [] new = [t["키"] for t in files[COEF]["표"] if "CC000349" <= t["키"] <= "CC000494"] assert len(new) == 146 logic = [r["키"] for r in files[LOGIC]["줄"] if r["키"] >= "GC001143"] assert logic == [f"GC00{n}" for n in range(1143, 1191)] assert files[LOGIC13]["줄"][-1]["키"] == "GC001191" for name in (LOGIC, LOGIC13): for row in files[name]["줄"]: if row["키"] >= "GC001143": mf.run(whole, row["키"], cm.시험입력(row)) # 시험 입력으로 멈춤 없음 GUARD = __import__("re").compile(r"만약\((\w+) > 0, \1,") def test_가드_칸_자동값(whole): # 분석 635 — 「0 이면 기본」 가드 칸은 견본 자동값 1 로 셈해도 터무니없지 않게: # 범위 [0, 원문 끝] 칸은 자동값 0(= 원문 기본) · 범위 없는 가드 칸은 1 이어도 기본의 10배 안 import master_auto as ma files = cm.load([LOGIC, LOGIC13]) rows = [r for f in files.values() for r in f["줄"] if r["키"] >= "GC001143"] loose = set() for row in rows: auto = ma.values(whole, row) got = mf.run(whole, row["키"], auto) value = got.get("결과", got.get("계")) assert value >= 0, row["키"] spec = {i["이름"]: i for i in row["입력"]} for name in GUARD.findall(" ".join(ma._texts(row))): if "범위" in spec.get(name, {}): assert auto[name] == 0, (row["키"], name) elif name in spec: loose.add(name) base = cm.시험입력(row) zero = mf.run(whole, row["키"], {**base, name: 0})["결과"] one = mf.run(whole, row["키"], {**base, name: 1})["결과"] assert one / zero < 10, (row["키"], name) assert loose == {"전진거리", "후진거리", "주행거리", "혼합횟수", "플랜트싸이클"}