Files
Aislo/resources/tester/test_782_3_capacity_82.py
eomsangdonandClaude Opus 5.5 8ddb5bbda7 fix(782-3): 8-2 시공능력 로직 「0 이면 기본」 가드 칸 — 견본 자동값 1 로도 엉뚱한 값 안 나오게(분석 635)
- 가드 칸에 범위 [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>
2026-11-04 20:09:23 +09:00

266 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 == {"전진거리", "후진거리", "주행거리", "혼합횟수", "플랜트싸이클"}