Files
Aislo/resources/tester/test_842_3_machine_crew.py
T
eomsangdonandClaude Opus 5.5 2c6e49f92c fix(842-3): 범위 잡재료율 기계 둘 · 수식 빌더가 저장본 문서를 조용히 빼던 흠
- 원인: 옮김에서 펌프 준설선 EQ000552 · 553 잡재료율 「13∼18」 이 사라짐 → 옛 엔진 멈춤 · 새 계산기 셈(빈 잡품)
  → GC000268 옵션 「다름」 → 저장 밖 → 그 식을 부르는 문서 320 개가 저장본에서 지워짐(1,520 → 1,200)
- 범위 글 = 잡품 율 글 그대로(from_old · TS fromOld · check 허용) · 두 대 다시 옮김 → 까닭 「범위」 옛 칸과 같음
- 빌더 = 저장본 문서가 저장 밖으로 빠지면 멈춤 · 안 씀 · 키와 까닭 알림(lost) · 시험 802_2 에 lost 0
- 시험 822_2 범위 까닭 기대 되돌림 · 842_3 거울에 범위 값 더함
- 확인: 빌더 --check 저장 1,520 · 기본 다름 0 · 옵션 다름 3(채택) · 저장본 · 목록 같음 · 655 대 시간당(옛 자료 대 새 자료) 다름 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 19:25:43 +09:00

209 lines
11 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.
"""842-3 기계 인력 · 잡품 줄 지킴 시험 — 두 길 같음 · 자료 꼴 · 손셈 · 거울 · 저장 꼴 · 쓰기 없음.
짝 = resources/master_data/scripts/master_machine_crew.py ↔ common_util/common_util_machine_crew.ts ·
`master_doc_cover` row_entry · merge · cover_doc(바탕) ↔ common_util_formula_doc_cover.ts rowEntry · mergeCover · coverDoc.
① 655 대 두 길 — 로직 엔진(목록 → 조종원 수 · 직종 · 잡재료율 읽기) = 문서 엔진(식 문서 + 목록 바탕 덮음) 노 · 재 · 경 · 계.
② M01 기계 자료 — 옛 칸(조종원 · 조종원직종 · 잡재료율) 0 · 목록 꼴 틀 0 · 선원류 칸 그대로.
③ 굴착기 1.0㎥ — 인력 둘(직종 다름) = 한 줄씩 셈 합 · 잡품 재료 줄 = 단가 × 수량 × 율 · 단가산출 덮음이 위에 덮임.
④ 거울 — 읽기 · 옛 꼴 바꿈 · 바탕 덮음 · 얹은 문서 파이썬 = TS.
⑤ 내 기계 저장 꼴 — 목록 받음 · 옛 칸 → 목록 · 틀린 줄 400.
"""
import copy
import importlib
import json
import os
import subprocess
from decimal import Decimal as D
import pytest
from M01_MasterData import M01_MasterData_Doc as m01doc
from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠
from M02_MasterTemplete import M02_Machine_Hourly as mh
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MyMachine as mym
from M02_MasterTemplete import M02_Sangeun_Copy as skc
mcrew = importlib.import_module("master_machine_crew")
mcv = importlib.import_module("master_doc_cover")
mc = importlib.import_module("master_doc_calc")
ml = importlib.import_module("master_doc_link")
mcut = importlib.import_module("master_cut")
HERE = os.path.dirname(os.path.abspath(__file__))
COSTS = ("노무비", "재료비", "경비", "계")
DIG = "EQ000022" # 굴착기(무한궤도) 1.0㎥ 0201-0100 · GC000267
SEA = ("선원", "준설선선장", "준설선기관사", "고급선원")
def _plain(v):
return json.loads(json.dumps(v, ensure_ascii=False, default=float))
@pytest.fixture(scope="module")
def world():
with store.at(store.FOLDER):
index = m01.loaded()[1]
yield {"index": index, "docs": m01doc.docs(), "rows": list(index.index["EQ"].values())}
def _logic(index, row, logic):
given = {i["이름"]: (i.get("고르기") or [""])[0] for i in logic.get("입력") or []}
if mh.takes_machine(logic):
given["기계"] = str(row.get("원문번호"))
mf = m01.cm.mf
got = mcut.engine(index, logic["키"], mf.run(index, logic["키"], mf.fill_empty(logic, given)))
return {k: round(float(got[k]), 4) for k in COSTS}
# ── ① 두 길 ────────────────────────────────────────────────────────────
def test_655대_로직_엔진_문서_엔진_같음(world):
index, same = world["index"], 0
with store.at(store.FOLDER):
for row in world["rows"]:
logic = mh.pick(row)
if logic is None or mh.why(row, logic):
continue
doc = mh.doc_sums(row, logic, index)
if doc is None or not doc["ok"]:
continue
assert {k: round(v, 4) for k, v in doc["합"].items()} == _logic(index, row, logic), row[
"키"
]
same += 1
assert len(world["rows"]) == 655 and same >= 600
# ── ② 자료 꼴 ───────────────────────────────────────────────────────────
def test_M01_기계_옛_칸_없음_목록_꼴(world):
crew = misc = 0
for row in world["rows"]:
vals = row.get("값") or {}
assert not any(k in vals for k in mcrew.OLD), row["키"]
assert mcrew.check(vals) == [], row["키"]
for part in ("단위", "출처"):
assert set(row.get(part) or {}) == set(vals), (row["키"], part)
crew += bool(vals.get("인력"))
misc += bool(vals.get("잡품"))
assert crew >= 295 and misc >= 320
assert sum(any(k in (r.get("값") or {}) for k in SEA) for r in world["rows"]) == 79
# ── ③ 손셈 ─────────────────────────────────────────────────────────────
def _hour(world, vals, cover=None):
"""굴착기 줄 값을 바꾼 사본 색인으로 시간당(문서 엔진 · 단가산출 덮음 얹음)."""
row = copy.deepcopy(world["index"].get(DIG))
row["값"] = {**mcrew.from_old(row["값"]), **vals}
index = skc.Copy(world["index"], [row], ("시험", id(vals)))
doc, given = world["docs"]["GC000267"], {"기계": "0201-0100"}
shaped = mcv.cover_doc(doc, given, cover, mcv.row_entry(doc, row["값"]))
got = mc.calc(shaped, ml.Links(index, world["docs"]), given)
assert got["셈됨"], got["표지"]
return got
def test_굴착기_인력_둘_잡품_재료_손셈(world):
with store.at(store.FOLDER):
one = _hour(world, {"인력": [{"키": "LB:1048", "인": D(1)}]})
two = _hour(world, {"인력": [{"키": "LB:1002", "인": D(2)}]})
both = _hour(
world, {"인력": [{"키": "LB:1048", "인": D(1)}, {"키": "LB:1002", "인": D(2)}]}
)
assert abs(both["노무비"] - one["노무비"] - two["노무비"]) <= 1
assert both["재료비"] == one["재료비"] and both["경비"] == one["경비"]
crew = [x for x in both["줄"] if x.get("이름") == "조종원"]
assert len(crew) == 2
none = _hour(world, {"인력": []})
assert none["노무비"] == 0
# 잡품 재료 줄 — 주연료 22 % + 재료 2 × 50 %
base = _hour(world, {})
mat = _hour(world, {"잡품": [{"줄": "주연료", "율": D(22)},
{"키": "MO:휘발유:전국평균", "수량": D(2), "율": D(50)}]}) # fmt: skip
price = world["index"].get("MO:휘발유:전국평균")["값"]
assert abs(mat["재료비"] - base["재료비"] - D(str(price)) * 2 * D("0.5")) <= 1
# 단가산출 덮음 = 목록 통째 위에(정본 2 줄 → 덮음 1 줄)
top = _hour(world, {"인력": [{"키": "LB:1048", "인": D(1)}, {"키": "LB:1002", "인": D(2)}]},
{"0201-0100": {"조종원": [{"키": "LB:1048", "인": D(1)}]}}) # fmt: skip
assert top["노무비"] == one["노무비"]
def test_빈_직종_줄은_식_입력_직종(world):
doc = world["docs"]["GC000275"]
parts = mcv.parts_of(doc)
assert mcv.row_entry(doc, {"인력": [{"키": "", "인": 1}]}) is None # 한 줄 = 식 그대로
got = mcv.cover_doc(doc, {"기계": "M"}, None, {"조종원": [{"키": "", "인": 2}]})
crew = [x for x in got["금액"] if x.get("비목") == "노무비"]
assert len(crew) == 1 and crew[0]["단가"] == doc["금액"][parts["crew"][0]]["단가"]
# ── ④ 거울 ─────────────────────────────────────────────────────────────
VALUES = [
{"가격": 1, "주연료": 19.5, "조종원": 1, "조종원직종": "1048", "잡재료율": 22},
{"가격": 1, "조종원": 1, "조종원직종": None, "잡재료율": 0, "주연료": 3},
{"가격": 1, "전력": 5, "주연료": 2, "잡재료율": 7, "조종원": 0},
{"가격": 1, "인력": [{"키": "LB:1048", "인": 1}, {"키": "LB:1002", "인": 2}], "잡품": []},
{"가격": 1, "인력": [{"키": "", "인": 1}], "잡품": [{"키": "MA:1", "수량": 2, "율": 5}]},
{"가격": 1},
{"가격": 1, "주연료": 2, "잡재료율": "13∼18", "조종원": None},
]
TARGETS = [
("GC000268", {"인력": [{"키": "LB:1048", "인": 1}], "잡품": [{"줄": "주연료", "율": 10}]}, None),
("GC000268", {"인력": [{"키": "LB:1048", "인": 1}, {"키": "LB:1002", "인": 1}]}, None),
("GC000268", {"인력": [], "잡품": [{"줄": "주연료", "율": 10}, {"키": "MA:1", "율": 5}]}, None),
("GC000272", {"인력": [{"키": "LB:1048", "인": 1}], "잡품": [{"줄": "주연료", "율": 3, "수량": 1}]},
{"값": {"가격": 9}, "잡품": [{"줄": "주연료", "율": 4}]}),
("GC000273", {"인력": [{"키": "LB:1048", "인": 1}], "잡품": [{"키": "MA:1", "율": 5}]}, None),
("GC000275", {"인력": [{"키": "", "인": 2}, {"키": "LB:1050", "인": 1}]}, None),
("GC001200", {"잡품": [{"줄": "주연료", "율": 7}, {"줄": "전력", "율": 7}]}, None),
] # fmt: skip
def test_거울_파이썬_TS_같은_답(world):
docs = {k: _plain(world["docs"][k]) for k, _, _ in TARGETS}
ask = {"값들": VALUES, "문서들": docs,
"대상": [{"식": k, "값": v, **({"덮음": c} if c else {})} for k, v, c in TARGETS]} # fmt: skip
proc = subprocess.run(
["node", os.path.join(HERE, "helper_842_3_machine_crew.cjs")],
input=json.dumps(ask, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
got = json.loads(proc.stdout)
assert got["읽기"] == _plain([mcrew.view(v) for v in VALUES])
assert got["옛"] == _plain([mcrew.from_old(v) for v in VALUES])
base = [mcv.row_entry(docs[k], v) for k, v, _ in TARGETS]
assert got["바탕"] == _plain(base)
assert [b is None for b in base] == [True, False, False, False, True, False, True]
want = [
mcv.cover_doc(docs[k], {"기계": "M"}, {"M": c} if c else None, b)
for (k, _, c), b in zip(TARGETS, base)
]
assert got["얹음"] == _plain(want)
# ── ⑤ 내 기계 저장 꼴 ─────────────────────────────────────────────────
def test_내기계_저장_목록_받음(world):
with store.at(store.FOLDER):
got = mym.clean({"이름": "sub3임시", "방식": "바로", "값": {
"가격": 1000, "손료계수": 2000, "주연료": 5, "주연료종류": "경유",
"인력": [{"키": "LB:1048", "인": 1}, {"키": "LB:1002", "인": 2}],
"잡품": [{"줄": "주연료", "율": 10}, {"키": "MO:휘발유:전국평균", "수량": 1, "율": 50}]}}) # fmt: skip
assert len(got["값"]["인력"]) == 2 and len(got["값"]["잡품"]) == 2
assert got["단위"]["인력"] == "인/일" and got["출처"]["잡품"] == "개인"
old = mym.clean({"이름": "sub3임시", "방식": "바로", "값": {
"가격": 1000, "손료계수": 2000, "주연료": 5, "주연료종류": "경유",
"조종원": 1, "조종원직종": "1048", "잡재료율": 10}}) # fmt: skip
assert old["값"]["인력"] == [{"키": "LB:1048", "인": 1}]
assert old["값"]["잡품"] == [{"줄": "주연료", "율": 10}]
assert not any(k in old["값"] for k in mcrew.OLD)
for bad in ({"인력": [{"키": "LB:없음", "인": 1}]}, {"인력": [{"키": "LB:1048", "인": -1}]},
{"잡품": [{"줄": "없는줄", "율": 1}]}, {"잡품": [{"키": "MA:없음", "율": 1}]}): # fmt: skip
with pytest.raises(store.StoreError) as e:
mym.clean(
{"이름": "sub3임시", "방식": "바로", "값": {"가격": 1, "손료계수": 1, **bad}}
)
assert e.value.status == 400, bad