- GF000335 시멘트운반 · GF000336 철근운반(산림 10-1 · 10-2) — 운임 = 자재 구역화물(톤급 · 거리 줄) ÷ 1.1 ÷ 적재 톤 · 하차 품 = 입력(기본값 없음 · 옛 판 값은 설명 참고만) - GX000004 깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계) — 굴착기 1회 N개 · 건설 공통 8-2-3(GC001147) · 상차 · 소운반 덤프 8-2-8(GC001155) · 실무 봉화#3 Q 12.38 · 9.44 재현 - GF000284 입력 끝에 적재기계(굴착기 0.2~1.0 · 로더 타이어 1.34 · 1.72) · 덤프(15 · 8 · 24ton) — 첫째 = 원문 · 기존 셈 그대로(54 조합 전후 같음) - 빌더 build_로직_M01보강_794_2a2.py = M01 [저장] 길 · 지킴 시험 test_794_2a2_m01_add.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
227 lines
9.9 KiB
Python
227 lines
9.9 KiB
Python
"""794-2a2 M01 보강 지킴 — 자재 운반 GF 둘(산림 10-1 · 10-2) · 기계 채집 자체 GX · 덤프 조합 GF000284 넓힘.
|
||
|
||
① 새 줄 셋 엔진 셈 — 운임 = 자재 구역화물(톤급 · 거리 줄 하나) ÷ 1.1 ÷ 적재 톤 · 하차 품 = 입력(기본값 없음) ·
|
||
채집 = 실무 봉화#3 식(q = 4/16 · Q 12.38 · 소운반 Q 9.44) · 견본 자동값으로 멈추지 않음.
|
||
② GF000284 — 고르기 첫째(굴착기 0.7㎥ · 15ton) = 넓히기 전 식(원문 수 0.7 · 15 · 20 · 0.85 · 22초) 그대로 ·
|
||
다른 고름 = 건설 공통 8-2-3 싸이클 · 8-2-5 로더 cm(거창#27 1.8 × 8 + 6 + 14 = 34.4) · 덤프 T.
|
||
③ 근거 제안(793-3a) — 794-2a 「없음」 블록 견본이 새 줄을 기초로 잡음(철근 · 깬잡석 · 파쇄석).
|
||
마스터 = 지금 M01(읽기만) · 엑셀 = `helper_m02_hopyo_golden.FILES`(없으면 ③ skip).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import importlib
|
||
import itertools
|
||
from decimal import Decimal as D
|
||
from functools import lru_cache
|
||
|
||
import helper_m02_hopyo_golden as golden
|
||
import pytest
|
||
|
||
from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠
|
||
|
||
ma = importlib.import_module("master_auto")
|
||
mcut = importlib.import_module("master_cut")
|
||
mf = importlib.import_module("master_formula")
|
||
|
||
CEMENT, REBAR, GATHER, DUMP = "GF000335", "GF000336", "GX000004", "GF000284"
|
||
|
||
|
||
@lru_cache(maxsize=1)
|
||
def whole():
|
||
return m01.loaded()[1]
|
||
|
||
|
||
def run(key: str, given: dict) -> dict:
|
||
w = whole()
|
||
auto = {k: v for k, v in ma.values(w, w.logic(key)).items() if v is not None}
|
||
return mcut.engine(w, key, mf.run(w, key, {**auto, **given}))
|
||
|
||
|
||
def near(a: D, b: D) -> bool:
|
||
"""줄 단가 = 하위 로직 계(끝수 셈이 원 미만을 끊음)."""
|
||
return abs(a - b) < 1
|
||
|
||
|
||
def line(got: dict, name: str) -> dict:
|
||
return next(x for x in got["줄"] if x["이름"] == name)
|
||
|
||
|
||
# ── ① 새 줄 ────────────────────────────────────────────────────────────
|
||
def test_새_줄_셋_자리():
|
||
w = whole()
|
||
for key, no, unit in (
|
||
(CEMENT, "10-1", "원/포"),
|
||
(REBAR, "10-2", "원/ton"),
|
||
(GATHER, "13-2-4", "원/㎡"),
|
||
):
|
||
row = w.logic(key)
|
||
assert row["원문번호"] == no and row["결과단위"] == unit and row["소유"] == "공용"
|
||
got = run(key, {}) # 견본 자동값 — 멈추지 않음
|
||
assert got["계"] >= 0
|
||
|
||
|
||
def test_철근운반_실무_북부청임도36():
|
||
got = run(REBAR, {"톤급": "10.5톤", "거리": "20km", "하차품": D(2) * 11 / 450})
|
||
fr = line(got, "구역화물 운임")
|
||
assert fr["품목"]["규격"] == "10.5톤, 20km" and fr["비목"].keys() == {"경비"}
|
||
assert fr["수량"] == 1 / (D("1.1") * D("10.5"))
|
||
assert fr["금액"] == (fr["단가"] / D("1.1") / D("10.5")).quantize(
|
||
D("0.1"), "ROUND_DOWN"
|
||
) # 줄 0.1 원 버림
|
||
labor = line(got, "보통인부(하차)")
|
||
assert labor["수량"] == D(2) * 11 / 450 and labor["비목"].keys() == {"노무비"}
|
||
# 엑셀 「구역화물(10.5톤,20km) 111,240 ÷ 1.1 ÷ 10.5 = 9,631.1」 — 같은 단가면 같은 금액
|
||
if fr["단가"] == 111240:
|
||
assert round(fr["금액"], 1) == D("9631.1")
|
||
|
||
|
||
def test_시멘트운반_포당():
|
||
got = run(CEMENT, {"톤급": "5.0톤", "거리": "20km", "하차품": D("0.31")})
|
||
fr = line(got, "구역화물 운임")
|
||
assert fr["품목"]["규격"] == "5.0톤, 20km"
|
||
assert fr["수량"] == 1 / (D("1.1") * D("5.0") * 25) # 40kg 포대 · 25 포/ton(원문 주)
|
||
assert line(got, "보통인부(하차)")["수량"] == D("0.31") / 25
|
||
|
||
|
||
def test_구역화물_톤급_거리_줄_하나씩():
|
||
w = whole()
|
||
spec = {s["이름"]: s["고르기"] for s in w.logic(REBAR)["입력"] if "고르기" in s}
|
||
assert spec["톤급"][0] == "10.5톤" and len(spec["톤급"]) == 20 and len(spec["거리"]) == 24
|
||
for ton, km in itertools.product(spec["톤급"], spec["거리"]):
|
||
got = run(REBAR, {"톤급": ton, "거리": km, "하차품": D(0)})
|
||
fr = line(got, "구역화물 운임")
|
||
assert fr["품목"]["규격"] == f"{ton}, {km}", (ton, km)
|
||
assert fr["수량"] == 1 / (D("1.1") * D(ton.removesuffix("톤")))
|
||
|
||
|
||
def test_하차품_기본값_없음():
|
||
w = whole()
|
||
for key in (CEMENT, REBAR):
|
||
assert ma.defaults(w, w.logic(key))["하차품"] is None
|
||
assert line(run(key, {}), "보통인부(하차)")["금액"] == 0
|
||
|
||
|
||
def test_기계채집_실무_봉화3():
|
||
w = whole()
|
||
got = run(GATHER, w.logic(GATHER)["시험입력"])
|
||
mid = got["중간"]
|
||
assert mid["q"] == D("0.25") and mid["K"] == D("0.55") and mid["E"] == D("0.45")
|
||
assert mid["cm"] == 18 and mid["Q"] == D("12.375") # 엑셀 Q = 12.38
|
||
assert round(mid["Q2"], 2) == D("9.44") # 엑셀 소운반 Q = 9.44(덤프 2.5톤 · L 0.2)
|
||
names = [x["이름"] for x in got["줄"]]
|
||
assert names == ["굴착기(채집)", "굴착기(상차)", "덤프트럭(소운반)"]
|
||
assert got["줄"][0]["수량"] == got["줄"][1]["수량"] == 1 / mid["Q"]
|
||
off = run(GATHER, {**w.logic(GATHER)["시험입력"], "상차": "안 함", "운반거리": D(0)})
|
||
assert [x["수량"] for x in off["줄"][1:]] == [0, 0]
|
||
|
||
|
||
# ── ② GF000284 넓힘 ───────────────────────────────────────────────────
|
||
OLD = { # 넓히기 전 식 — 원문 수
|
||
"Q1": "반올림(3600 * 0.7 * K * (1 / L) * E0 / 찾기(QF000336, 구분='㎝(초)').값, 3)",
|
||
"q": "15 / 감마 * L",
|
||
"n": "q / (0.7 * K)",
|
||
"t1": "20 * n / (60 * 0.85)",
|
||
}
|
||
|
||
|
||
def _old(mid: dict, name: str) -> D:
|
||
return mf.evaluate(mf.parse(OLD[name]), dict(mid), whole(), 0)
|
||
|
||
|
||
@pytest.mark.parametrize("재료", ["토사", "암절취", "발파암"])
|
||
def test_덤프운반_기본_고름_옛_식_그대로(재료):
|
||
w = whole()
|
||
row = w.logic(DUMP)
|
||
assert [s["이름"] for s in row["입력"][:4]] == ["재료", "운반거리", "지역", "보정작업"]
|
||
assert (
|
||
ma.defaults(w, row)["적재기계"] == "0201-0070"
|
||
and ma.defaults(w, row)["덤프"] == "0602-0150"
|
||
)
|
||
for dist, rock in itertools.product(
|
||
(D("0.3"), D("23.877")), ("해당 없음", "암석작업(연암・보통암・경암)")
|
||
):
|
||
got = run(DUMP, {"재료": 재료, "운반거리": dist, "지역": "전국평균", "보정작업": rock})
|
||
mid = got["중간"]
|
||
assert (
|
||
mid["Q1"] == _old(mid, "Q1")
|
||
and mid["q"] == _old(mid, "q")
|
||
and mid["n"] == _old(mid, "n")
|
||
)
|
||
assert mid["Cms"] * mid["n"] / (60 * mid["Es"]) == _old(mid, "t1")
|
||
dig, truck = got["줄"]
|
||
assert [dig["이름"], truck["이름"]] == [
|
||
"굴착기(무한궤도) 0.7㎥(적재)",
|
||
"덤프트럭 15ton(운반)",
|
||
]
|
||
base = {"지역": "전국평균", "보정작업": rock}
|
||
assert near(dig["단가"], mf.run(w, "GC000267", {"기계": "0201-0070", **base})["계"])
|
||
assert near(truck["단가"], mf.run(w, "GC000269", {"기계": "0602-0150", **base})["계"])
|
||
|
||
|
||
def test_덤프운반_넓힌_고름():
|
||
g = {"재료": "토사", "운반거리": D(1), "지역": "전국평균", "보정작업": "해당 없음"}
|
||
small = run(DUMP, {**g, "적재기계": "0201-0020"})["중간"]
|
||
assert (small["q0"], small["Cm"], small["Cms"]) == (
|
||
D("0.2"),
|
||
20,
|
||
18,
|
||
) # 8-2-3 0.12∼0.4 · 180° · 135°
|
||
big = run(DUMP, {**g, "적재기계": "0201-0100"})["중간"]
|
||
assert (big["q0"], big["Cm"], big["Cms"]) == (D("1.0"), 23, 21)
|
||
loader = run(DUMP, {**g, "적재기계": "0302-0134"})
|
||
mid = loader["중간"]
|
||
assert (
|
||
mid["q0"] == D("1.34") and mid["Cms"] == D("34.4") == mid["Cm"]
|
||
) # 거창#27 1.8 × 8 + 6 + 14
|
||
assert mid["K"] == 1 and mid["Es"] == mid["E0"] == D("0.6") # 거창#27 K 1 · Es 0.6
|
||
rock = run(DUMP, {**g, "보정작업": "암석작업(연암・보통암・경암)", "적재기계": "0302-0134"})
|
||
plain = {"기계": "0302-0134", "지역": "전국평균", "보정작업": "해당 없음"}
|
||
assert near(
|
||
rock["줄"][0]["단가"], mf.run(whole(), "GC000267", plain)["계"]
|
||
) # 타이어 로더 = 손료 보정 없음
|
||
for dump, ton in (("0602-0080", 8), ("0602-0240", 24)):
|
||
mid = run(DUMP, {**g, "덤프": dump})["중간"]
|
||
assert mid["덤프T"] == ton and mid["q"] == ton / mid["감마"] * mid["L"]
|
||
|
||
|
||
# ── ③ 근거 제안 ───────────────────────────────────────────────────────
|
||
SAMPLES = { # 블록: (기초, 단)
|
||
"북부청임도#36": (REBAR, {"확정", "근접"}), # 철근운반 [산림사업표준품셈 10-2]
|
||
"봉화#117": (
|
||
CEMENT,
|
||
{"확정", "근접", "사람"},
|
||
), # 시멘트운반 — 제목 단독(톤급 「10.5ton」 못 읽음 = 794-2c)
|
||
"봉화#3": (GATHER, {"확정", "근접"}), # 깬잡석채집
|
||
"울진소광#1448": (GATHER, {"확정", "근접"}), # 파쇄석 채집
|
||
}
|
||
|
||
|
||
@lru_cache(maxsize=8)
|
||
def _book(tag: str):
|
||
from M02_MasterTemplete import M02_San_Basis_Store as store
|
||
from M02_MasterTemplete import M02_San_Book as san_book
|
||
|
||
path = golden.FILES[tag]
|
||
if not path.exists():
|
||
return None
|
||
book = golden.read(tag)
|
||
if book is None:
|
||
return None
|
||
sheets = san_book.sheets(path.name, path.read_bytes()) if book["꼴"] == "STmate" else {}
|
||
return {**book, "숨은식": store.hidden(book, sheets)}
|
||
|
||
|
||
@pytest.mark.parametrize("block", sorted(SAMPLES))
|
||
def test_근거제안_없음에서_새_기초(block):
|
||
from M02_MasterTemplete import M02_San_Basis_Store as store
|
||
|
||
tag, no = block.split("#")
|
||
book = _book(tag)
|
||
if book is None:
|
||
pytest.skip("실무 엑셀 없음")
|
||
got = store.one(book, int(no))
|
||
key, tiers = SAMPLES[block]
|
||
assert got["단"] in tiers, got["단"]
|
||
assert key in [c["기초"] for c in got["근거후보"]]
|