Files
Aislo/resources/tester/test_821_2_machine_coef.py
T
eomsangdonandClaude Opus 5.5 f8e6d0d14e feat(821-2): 기계 계수 계산 짝(파이썬 · TS) — 손료계수 넷 · 시간당 미리보기 · 거울 시험
- master_machine_coef.py · common_util_machine_coef.ts — 건설품셈 8-1-5 계수(정수 반올림 · 손료계수 = 합) · 시간당 미리보기(줄 0.1 원 버림 · 잡품 = 버린 주연료 × % · 비목 합 원 버림)
- 계수 전수 대조 600 줄 중 598 같음 · 다른 2 줄 = 점보드릴 원문 표 값(777 · 셈 777.8)
- 미리보기 = 서버 기계경비식 619 대 중 602 같음 · 견본 스무 대 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 13:35:23 +09:00

134 lines
4.9 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.
"""PLAN 821-2 기계 계수 계산 짝 — 지킴 시험.
① 계수 전수 대조: M01 기계표에서 품셈 입력 다섯 · 계수 넷이 다 있는 줄 = 파이썬 계수 셈과 같음
(다른 줄 = 원문 표 값 그대로인 점보드릴 2 줄만 · 원문 777 · 셈 777.8).
② 파이썬 · TS 거울: 같은 입력 → 같은 계수 · 같은 시간당 미리보기.
③ 미리보기 = 서버 기계경비식(`M02_Machine_Hourly.hourly`) 견본 스무 대 · 비목 셋 같음.
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "resources" / "master_data" / "scripts"))
import master_machine_coef as mc # noqa: E402
DATA = ROOT / "resources" / "master_data"
TS = ROOT / "common_util" / "common_util_machine_coef.ts"
# 원문 표 값 ≠ 식(정비비계수 0.7 ÷ 9,000 × 10^7 = 777.8 → 원문 777 · 계 2,515) — 식을 비틀지 않음
ORIGINAL_ODD = {"EQ000347", "EQ000348"}
SAMPLES = (
"EQ000001 EQ000006 EQ000022 EQ000026 EQ000040 EQ000051 EQ000073 EQ000076 EQ000126 EQ000151 "
"EQ000176 EQ000201 EQ000226 EQ000229 EQ000246 EQ000326 EQ000401 EQ000434 EQ000455 EQ000544"
).split()
def _load(name: str) -> list[dict]:
return json.loads((DATA / name).read_text(encoding="utf-8"))["줄"]
MACHINES = _load("기계.json")
FUEL = {r["원문번호"]: r["값"] for r in _load("재료_유가전력.json")}
WAGE = {r["원문번호"]: r["값"] for r in _load("인력.json")}
def _full(row: dict) -> bool:
v = row["값"]
return all(isinstance(v.get(k), (int, float)) for k in (*mc.INPUTS, *mc.COEFS))
def _prices(v: dict) -> tuple[float, float]:
"""전국평균 유가 · 노임(직종 비면 기계경비식 조종원직종 첫 고르기 1048)."""
return FUEL.get(f"{v.get('주연료종류')}:전국평균", 0), WAGE.get(
str(v.get("조종원직종") or "1048"), 0
)
def test_coef_matches_master_table():
rows = [r for r in MACHINES if _full(r)]
assert len(rows) >= 590
odd = {r["키"] for r in rows if mc.coef_of(r["값"]) != {k: r["값"][k] for k in mc.COEFS}}
assert odd == ORIGINAL_ODD
def test_coef_excavator_example():
got = mc.coef(10000, 1250, 0.9, 0.7, 0.1)
assert got == {"상각비계수": 900, "정비비계수": 700, "관리비계수": 485, "손료계수": 2085}
assert mc.coef(0, 1250, 0.9, 0.7, 0.1) is None
assert mc.coef_of({"내용시간": 10000}) is None
@pytest.mark.skipif(
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
reason="프론트엔드 의존성 미설치",
)
def test_ts_mirror(tmp_path):
coef_cases = [[r["값"][k] for k in mc.INPUTS] for r in MACHINES if _full(r)]
coef_cases += [[10000, 1250, 0.9, 0.7, 0.1], [0, 1, 1, 1, 1], [3600, 540, 0.9, 1.15, 0.1]]
hour_cases = []
for r in MACHINES:
fuel, wage = _prices(r["값"])
hour_cases.append([r["값"], fuel, wage, 114.4, 0.125])
hour_cases.append(
[
{"가격": 2680, "손료계수": 5313, "주연료": 1.0, "잡재료율": 10, "전력": 3.73},
2000,
0,
114.4,
0.125,
]
)
src = tmp_path / "in.json"
src.write_text(
json.dumps({"coef": coef_cases, "hour": hour_cases}, ensure_ascii=False), encoding="utf-8"
)
script = f"""
const fs = require("fs");
const ts = require({json.dumps(str(ROOT / "config" / "node_modules" / "typescript"))});
const opt = {{ module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }};
const src = fs.readFileSync({json.dumps(str(TS))}, "utf8");
const js = ts.transpileModule(src, {{ compilerOptions: opt }}).outputText;
const m = {{ exports: {{}} }};
new Function("exports", "module", js)(m.exports, m);
const inp = JSON.parse(fs.readFileSync({json.dumps(str(src))}, "utf8"));
process.stdout.write(JSON.stringify({{
coef: inp.coef.map((c) => m.exports.machineCoef(...c)),
hour: inp.hour.map((h) => m.exports.machineHourly(...h)),
}}));
"""
out = subprocess.run(
["node", "-e", script], capture_output=True, text=True, encoding="utf-8", check=True
)
got = json.loads(out.stdout)
want_coef = [mc.coef(*c) for c in coef_cases]
want_hour = [mc.preview(*h) for h in hour_cases]
assert got["coef"] == want_coef
assert len(got["hour"]) == len(want_hour)
for a, b in zip(got["hour"], want_hour):
assert a == pytest.approx(b, abs=1e-9)
def test_preview_equals_server_hourly():
from M02_MasterTemplete import M02_Machine_Hourly as mh
rows = {r["키"]: r for r in MACHINES}
for key in SAMPLES:
h = mh.hourly(key)
assert h["ok"], (key, h)
v = rows[key]["값"]
p = mc.preview(v, *_prices(v))
assert (p["노무비"], p["재료비"], p["경비"], p["계"]) == (
h["노무비"],
h["재료비"],
h["경비"],
h["계"],
), (key, h["로직"])