"""801-3 수식 탭 소계 묶음(요구 662-3) 지킴 — 금액 셈은 그대로 · 묶음만 다시. ① 묶음 = 부른 단가산출(갈래 블록) 하나씩 + 직접 줄 한 묶음(갈래 직접) · 돈 없는 줄 = 묶음 아님 · 안쪽 말 0 ② M01 전수 — 묶음 합 + 끝수 = 합계(엔진 비목 합 그대로) · 직접 묶음 하나뿐 ③ 부품 — 묶음 하나 = 머리 없음 · 계 / 둘 이상 = 머리 · 소계 / 단가산출 둘 이상 = 합계 · 안쪽 말 글 0 """ from __future__ import annotations import importlib from decimal import Decimal as D from pathlib import Path from M01_MasterData import M01_MasterData_Store as store mf = importlib.import_module("master_formula") meq = importlib.import_module("master_equation") grp = importlib.import_module("master_equation_group") COSTS = ("노무비", "재료비", "경비", "계") BAD = ("자기", "자원", "덧줄", "블록") # 코드 안쪽 말 — 화면 글에 0 def _w(): return store.loaded()[1] def _export(key: str) -> dict: w = _w() return meq.export(w, key, meq.given_of(w, w.logic(key), None)) def _row(kind, name, cost=(0, 0, 0), **more): got = dict(zip(COSTS[:3], (D(x) for x in cost), strict=True)) return {"갈래": kind, "이름": name, **got, "계": sum(got.values(), D(0))} | more def test_묶음_함수_하나뿐_직접_한_묶음(): got = grp.bundles( [ _row("블록", "굴착기", (1, 2, 3)), _row("블록", "쉬는 단가산출"), # 돈 0 = 묶음 아님 _row("자원", "보통인부", (5, 0, 0), 종류="인력"), _row("자원", "시멘트", (0, 7, 0), 종류="재료"), _row("덧줄", "공구손료", (0, 0, 1)), ] ) assert [r["갈래"] for r in got["줄"]] == ["블록", "직접"] assert got["줄"][0]["이름"] == "굴착기" and got["줄"][1]["이름"] == "인력 · 재료" assert (got["줄"][1]["노무비"], got["줄"][1]["재료비"], got["줄"][1]["경비"]) == (5, 7, 1) only = grp.bundles([_row("덧줄", "공구손료", (0, 0, 1))])["줄"] assert [r["이름"] for r in only] == ["경비"] and not grp.bundles([_row("블록", "빈")])["줄"] def test_M02_상태_줄(): got = grp.bundles( [ _row("자원", "보통인부", (5, 0, 0), 상태="더함", 종류="인력"), _row("자원", "형틀목공", (4, 0, 0), 상태="덮음", 원래=D(9), 종류="인력"), _row("자원", "지운 줄", 상태="뺌", 종류="인력"), _row("자원", "밖 줄", 합밖=True, 밖금액=D(8)), ] ) assert [x["이름"] for x in got["줄"][0]["낱줄"]] == ["보통인부", "형틀목공"] assert got["밖"] == [{"이름": "밖 줄", "밖금액": D(8)}] and got["줄"][0]["계"] == 9 def test_거푸집_한_묶음_굴착기_한_묶음(): eq = _export("GC000211") assert [r["갈래"] for r in eq["소계"]["줄"]] == ["직접"] assert eq["소계"]["줄"][0]["이름"] == "인력 · 재료" eq = _export("GF000089") rows = eq["소계"]["줄"] assert [(r["갈래"], r["이름"]) for r in rows] == [("블록", "굴착기(무한궤도) 0.7㎥")] assert eq["합계"]["계"] == D("2190.0") and eq["소계"]["끝수"]["계"] == D("-1.0") def test_M01_전수_묶음_합_끝수_합계_안쪽_말_0(): w = _w() many = seen = 0 for tid, rows in sorted(w.index.items()): if mf.mk.GROUP_OF.get(tid) != "로직": continue for logic in rows.values(): if not logic.get("호표"): continue try: eq = _export(logic["키"]) except mf.FormulaError: continue sub = eq.get("소계") if not sub: continue seen += 1 lines = sub["줄"] assert sum(r["갈래"] == "직접" for r in lines) <= 1, logic["키"] assert all(r["계"] != 0 for r in lines), logic["키"] for r in lines: assert not any(b in r["이름"] for b in BAD), (logic["키"], r["이름"]) for k in COSTS: got = sum((r[k] for r in lines), D(0)) + sub["끝수"][k] assert got == eq["합계"][k], (logic["키"], k) many += sum(r["갈래"] == "블록" for r in lines) >= 2 assert seen > 400 and many >= 10, (seen, many) def test_부품_머리_소계_합계_규칙_안쪽_말_0(): ts = Path("ui_template/ui_template_equation_sum.ts").read_text(encoding="utf-8") assert "sub.줄.length >= 2" in ts # 묶음 하나 = 머리 · 소계 없음 assert '"합계" : "계"' in ts and '=== "블록").length >= 2' in ts # 합계 = 단가산출 둘 이상 ui = "".join( line for line in ts.splitlines(keepends=True) if not line.lstrip().startswith(("*", "/*", "//", "/**")) ) for word in ("자기 자원", "덧줄", "KIND"): assert word not in ui, word