- 가드 칸에 범위 [0, 원문 끝] — 견본 자동값 = 0(원문 기본): E입력 [0, 1](원문 E 표 모두 1 이하) · 굴착기 싸이클입력 [0, 30](원문 3 표 13∼30초) · 디젤 해머 α입력 [0, 4.0] · 워터제트 α입력 [0, 1.20] · 펌프준설 q입력 [0, 387] · 그래브 f입력 · k입력 [0, 1.00] · 콘크리트 운반 배출 [0, 4] · 대기 [0, 10] · 하역 [0, 2] - 범위 없는 가드 칸(트랜처 전진 · 후진거리 · 로더 주행거리 · 스테이빌라이저 혼합횟수 · 플랜트싸이클)은 1 이어도 기본의 3배 안 — 그대로 - 압쇄기 · 골재세척 q입력 뺌(원문 q 한 값 · 1 이면 60배 어긋남) · 스테이빌라이저 유효혼합폭 최대(로터폭 − 0.4, 0)(자동값 음수 막음) - 유압 파일 해머 강관 Ta 표 「16이하」 ↔ β 표 「16 이하」 글자 맞춤(16m 이하 줄 못 찾던 흠) · 그래브 N값 고르기 「해당 없음」 끝으로(첫 값 = 표 줄) - 지킴 시험 test_782_3 가드 칸 자동값 덧 · 기존 로직 · 표 · 키 그대로(로직_08장 새 줄만 바뀜) · check_master 전후 같음 · 계산전부 1513 멈춤 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
266 lines
11 KiB
Python
266 lines
11 KiB
Python
# -*- 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 == {"전진거리", "후진거리", "주행거리", "혼합횟수", "플랜트싸이클"}
|