- 표지 = 수식 문서 머리 · 옛 로직 줄 칸 「부품」 — 기계경비식 9 · 작업량 틀 60(기종 = 기계표 상세구분 · 규격 열쇠) · 계수식 4 · 변환기가 옮김 · 틀 문서 9 장 - 지움 = 작업량당 기계경비 6(GC001145 · 1148 · 1151 · 1156 · 1159 · 1166 · 부르는 곳 0 · 요구 734-4 · 735-4) — 수식 문서 · 옛 로직 · 키대장 폐기 칸 · 빌더 · 변수표 - 이름 꼬리 4 = 원문 제목(지게운반 · 인력운반(기계설비) · 벽돌운반 · 수중펌프 설치 및 해체) · 키 폐기 0 - 서버 = `M01_MasterData_Parts` — M01 `/logics` · M02 `/logic-lib/logics` 목록에서 부품 뺌(1,440) · 키로 열기 · 부르기 그대로 · `/m01/parts`(기종 계산식 · 틀 · 계수식) · `/m02/logic-lib/machine-parts` · 연결 찾기 과녁에서 부품 뺌(1,444 → 1,435 · 판정 코드 그대로) - 금액 전후 = 문서 1,513 · 셈 9,626(기본 + 옵션 표본) 모두 같음 · 791_1 지문 남은 1,513 같음 → 지문 다시 뜸 · 802_2 변환 대조 1,513 · 목록 xlsx 다시 씀(요약 수) - 낡은 시험 = 1,519 · 1,518 기대 → 1,513 · GC001148 견본 → 같은 꼴 GF000089 토사깍기(기계)(블록 Q 39.293 · 합계 2,190) · 지운 6 을 셈하던 줄 걷음 · 목록 수 1,440 · 묶음 328 · 부르는 수 507 - 새 지킴 시험 test_813_2_parts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
253 lines
10 KiB
Python
253 lines
10 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")
|
||
# 대형브레이커 작업량당 기계경비(GC001166) = 813-2 지움 — 단가산출에 기계 하나 넣어 만듦
|
||
|
||
|
||
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")
|
||
# 굴착기 작업량당 기계경비(GC001148) = 813-2 지움 — 기계 넣기 + 작업량 틀(test_813_2_parts)
|
||
|
||
|
||
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"]
|
||
gone = (1145, 1148, 1151, 1156, 1159, 1166) # 813-2 작업량당 기계경비 지움
|
||
assert logic == [f"GC00{n}" for n in range(1143, 1191) if n not in gone]
|
||
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 == {"전진거리", "후진거리", "주행거리", "혼합횟수", "플랜트싸이클"}
|