Files
Aislo/resources/tester/test_144_b09_base_calc.py
T

171 lines
7.4 KiB
Python
Raw 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.
"""144-2~4 B09 바탕 단가 셈 — 엑셀 검산 대조 · 거울(`B09_Estimation_Calc_Base.py` ↔ `.ts`).
견본 = 기번3 공통 엑셀(`2024년 간선임도사업(기번3).xlsx`) 같은 입력:
노무비목록표 E10~E12 · 환율및기초자료 G6~G8 · 경비목록표 E4 · E5 · E7 · E13 · E16 · E21 ·
자재단가대비표 5~12줄 · 중기사용료 1 · 2 · 4 · 11 · 14 · 16 호표(F11 126,000 …)
"""
import json
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_Calc_Base import ( # noqa: E402
compute_base,
round_half,
rounddown,
roundup,
)
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
FACTOR = {"num": 400, "den": 1920, "text": "1/8 × 16/12 × 25/20"}
LABOR = [
{"key": "LB000048", "code": "1048", "name": "건설기계운전사", "price": 267360},
{"key": "LB000049", "code": "1049", "name": "화물차운전사", "price": 226709},
{"key": "LB000050", "code": "1050", "name": "일반기계운전사", "price": 161142},
{"key": "LB000002", "code": "1002", "name": "보통인부", "price": None},
]
def _m(key, name, price, loss, fuel, lph, misc, crew_code):
return {"key": key, "name": name, "spec": "", "price": price, "loss": loss, "fuel": fuel,
"fuel_lph": lph, "misc_pct": misc, "crew": 1 if crew_code else None,
"crew_code": crew_code} # fmt: skip
MACHINES = [
_m("X00001", "불도우저19ton", 184499, 1811, "경유", 25, 16, "1048"),
_m("X00003", "굴삭기(0.2m3)", 62550, 2085, "경유", 5, 21, "1048"),
_m("X00005", "굴삭기(0.7m3)", 110926, 2085, "경유", 11.6, 22, "1048"),
_m("X00032", "덤프자동덮개", 1546, 2684, None, None, None, None),
_m("X00048", "콘크리트믹서0.45m3", 5409, 3039, "휘발유", 3.9, 2, "1050"),
_m("X00074", "덤프트럭2.5ton(토사)", 20793, 2967, "경유", 2.9, None, "1049"),
_m("X09999", "기준가 없는 기계", None, 2000, "중유", 3, 10, "1048"),
]
def _mt(key, name, **prices):
return {"key": key, "name": name, "spec": "", "unit": "", "origin": "M01", "prices": prices}
MATERIALS = [
_mt("MO1", "휘발유", 관급=1369, 물가정보=0, 물가자료=0),
_mt("MO2", "경유", 관급=1272),
_mt("M3", "각재", 관급=500661, 물가정보=829341, 물가자료=748503),
_mt("M4", "못", 관급=1503, 물가정보=1935, 물가자료=1665),
_mt("M8", "화강석", 관급=0, 물가정보=288000, 물가자료=288000),
_mt("M9", "사용자 값 줄", 물가자료=1000),
_mt("b08:sand", "골재"),
]
INPUTS = {
"labor": LABOR,
"machines": MACHINES,
"materials": MATERIALS,
"fuel_keys": {"경유": "MO2", "휘발유": "MO1"},
"driver_factor": FACTOR,
"fx": [
{"key": "FX000001", "name": "USD", "unit": "원/USD", "value": 1414.9, "date": "2026-08-14"}
],
}
USER = {"materials": {"M9": {"applied": 950, "supply": "관급"}, "M4": {"supply": "별도"},
"b08:sand": {"applied": None}}} # fmt: skip
def _run_ts(inputs, user):
calc = ROOT / "B09_Estimation" / "B09_Estimation_Calc_Base.ts"
body = (
f"\nconst i = {json.dumps(inputs, ensure_ascii=False)};"
f"\nconst u = {json.dumps(user, ensure_ascii=False)};"
"\nconsole.log(JSON.stringify(computeBase(i, u)));"
)
script = IMPORTS.sub("", calc.read_text(encoding="utf-8")) + body
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", script],
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def test_끝수_엑셀_자리():
assert rounddown(33412.7689, 1) == 33412.7 and rounddown(126000.9) == 126000
assert rounddown(-1.25, 1) == -1.2 # 0 쪽으로
assert roundup(69849380, -3) == 69850000 and roundup(69850000, -3) == 69850000
assert round_half(2.5) == 3 and round_half(0.125, 2) == 0.13 and round_half(-2.5) == -3
assert rounddown(0.1 + 0.2, 1) == 0.3 # 떠도는 끝수는 버리지 않음
def test_144_2_노무_경비_환율_기초자료():
s = compute_base(INPUTS, {})
assert [r["price"] for r in s["labor"]] == [267360, 226709, 161142, None]
assert ("labor", "LB000002") in {(g["tab"], g["key"]) for g in s["gaps"]}
hours = {d["key"]: d["hour"] for d in s["basis"]["drivers"]}
assert hours == {"1048": 55700, "1050": 33571, "1049": 47231} # 기초자료 G6 · G8 · G7
assert s["basis"]["drivers"][0]["factor"] == "1/8 × 16/12 × 25/20"
assert [r["price"] for r in s["expense"]][:3] == [
184499,
62550,
110926,
] # 경비목록표 E4 · E5 · E7
assert s["expense"][0]["unit"] == "천원"
assert s["basis"]["fx"][0]["value"] == 1414.9
def test_144_3_자재단가대비_최저_사용자_관급():
s = compute_base(INPUTS, USER)
rows = {r["key"]: r for r in s["compare"]}
# 엑셀 O 열 = 최저 · P 열 출처(0 은 뺌 · 같은 값이면 앞 칸 — 화강석 「물정」)
assert (rows["MO1"]["applied"], rows["MO1"]["applied_from"]) == (1369, "관급")
assert (rows["M3"]["applied"], rows["M3"]["lowest_from"]) == (500661, "관급")
assert rows["M4"]["applied"] == 1503 and rows["M4"]["supply"] == "별도"
assert (rows["M8"]["applied"], rows["M8"]["applied_from"]) == (288000, "물가정보")
assert (rows["M9"]["lowest"], rows["M9"]["applied"], rows["M9"]["applied_from"]) == (
1000,
950,
"사용자",
)
assert rows["M9"]["supply"] == "관급"
assert rows["b08:sand"]["applied"] is None # 출처 없음 · 사용자 빈칸 = 빈칸
material = {r["key"]: r["price"] for r in s["material"]}
assert material == {r["key"]: r["applied"] for r in s["compare"]} # 재료비목록표 = 적용 단가
gaps = [(g["tab"], g["key"]) for g in s["gaps"] if g["tab"] == "compare"]
assert gaps == [("compare", "b08:sand")]
def test_144_4_중기사용료_엑셀_검산():
s = compute_base(INPUTS, {})
got = {r["key"]: (r["sum"], r["labor"], r["material"], r["expense"]) for r in s["machine"]}
assert got["X00001"] == (126000, 55700, 36888, 33412) # 중기목록표 4줄
assert got["X00003"] == (76436, 55700, 7695, 13041)
assert got["X00005"] == (96829, 55700, 18001, 23128)
assert got["X00032"] == (414, 0, 0, 414) # 덮개 — 운전사 · 연료 없음
assert got["X00048"] == (40659, 33571, 5445, 1643) # 일반기계운전사 · 휘발유 · 잡유 2 %
assert got["X00074"][1:3] == (47231, 3688) # 화물차운전사 · 잡품 없음
assert got["X09999"] == (None, 55700, None, None) # 기준가 · 중유 단가 없음 = 그 비목 · 합 빈칸
block = s["machine_blocks"][0]
assert [ln["amount"] for ln in block["lines"]] == [
33412.7,
55700,
31800,
5088,
] # 중기사용료 F7~F10
assert block["lines"][0]["qty"] == 0.1811 and block["lines"][0]["ref"] == {
"tab": "expense",
"key": "X00001",
}
why = {g["why"] for g in s["gaps"] if g["key"] == "X09999"}
assert why == {"연료(중유) 단가 없음", "기계 기준가 없음", "손료 기준가 · 계수 없음"}
def test_거울_TS_가_같은_값():
for user in ({}, USER):
assert _run_ts(INPUTS, user) == compute_base(INPUTS, user)