- 끝수 한 곳 `master_cut.py`(B09 `rounddown` 재사용) — 엔진 답에 입힘: 줄 비목마다 0.1 원 버림 · 하위 로직 줄 = 하위 끝수 계 × 수량 · 덧줄 = 버린 줄 합으로 다시 셈 · 비목 합 1 원 버림 · 중간 칸도 버린 값 - 쓰는 곳: M01 시험 계산(`check_master.calc`) · 읽는 식(`master_text.lines`) · 옛 조합 미리보기(`master_combo`) · M02 `run_line`(단가산출 · 일위대가 · 펼침) · 별도계상 뺀 기준 · 켜쌓기율 곱 뒤 · 일위대가 반영 · 반영합 · 비율 줄 · 사전조합 안 줄 - 일위대가 반영 TS 짝(`Labor_Calc.ts`) 같은 끝수 · 거울 헬퍼가 상대 경로 .ts 가져옴 - B09 = `run_line(raw=True)` 엔진 날 값(제 단가로 다시 버림 · % 덧줄 율 비 그대로) — 검증_sub3 B09 247 칸 전후 같음 - `master_text` 꼴 셋 = `common_util_money` 로 갈음(줄 금액 `fmt_line_money` · 식 안 금액 = 버린 0.1 원) - 화면: 상세 줄 금액 · 시험 계산 줄 · 중간 돈 = `formatLineMoney` · 일위대가 반영 칸 0.1 원 · 상세 바닥 · 박스 · 인력재료경비 비목 소계 = 줄 합 1 원 버림 - 테스트1 네 보기 · 서고 목록 = 124,419(옛 124,420 / 124,419.85) · 재료 줄 11,694.6 + 23,425.6 + 1,404.8 = 소계 36,525 - 새 지킴 시험 test_751_3_money_cut - 옛 날 금액(끝수 없음)을 대조하던 시험 24 파일 고침 — 까닭 = 금액 규칙이 줄 0.1 원 · 소계 1 원 버림으로 바뀜(고정 값 → 새 값 · 줄 합 = 계 → 줄 합 아래 비목마다 1 원 안 · 곱 비 = 끝수 폭 안) · 실무 xlsx 영월 엑셀 같음 22 → 23 · 이어 붙이는 TS 시험(704_3 · 717_2b)에 `rounddown` 같은 식 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
6.2 KiB
Python
141 lines
6.2 KiB
Python
"""751-3 셈 끝수 = 품셈 1-2-2(줄 0.1 원 · 소계 1 원 미만버림) — `master_cut` 지킴 시험.
|
||
|
||
M01 시험 계산 · 읽는 식 · M02 단가산출(SK) · 일위대가 반영이 B09 끝수(`rounddown`)와 같은 값 ·
|
||
줄 합 = 소계(비목마다 ROUNDDOWN(Σ 줄, 0)) · B09 는 엔진 날 값(`raw`)을 받아 제 단가로 다시 버림.
|
||
"""
|
||
|
||
from decimal import Decimal
|
||
|
||
from B09_Estimation import B09_Estimation_Sangeun as sg
|
||
from M01_MasterData import M01_MasterData_Store as m01
|
||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||
|
||
mcut, mf = labor.mcut, labor.mf # scripts 길은 `m01` 이 둠
|
||
KEY = "GC000211" # 합판거푸집(테스트1 바탕) — 인력 · 재료 · % 덧줄 둘
|
||
COSTS = ("노무비", "재료비", "경비")
|
||
|
||
|
||
def _whole():
|
||
return m01.loaded()[1]
|
||
|
||
|
||
def _tenth(v) -> bool:
|
||
return v is None or Decimal(str(v)) == Decimal(str(v)).quantize(Decimal("0.1"))
|
||
|
||
|
||
def _sums_of_lines(lines) -> dict:
|
||
"""비목마다 ROUNDDOWN(Σ 줄, 0) — B09 `block_totals` 규칙."""
|
||
total = dict.fromkeys(COSTS, Decimal(0))
|
||
for x in lines:
|
||
for k, v in (x.get("비목") or {}).items():
|
||
total[k] += Decimal(str(v or 0))
|
||
return {k: mcut.down(v, 0) for k, v in total.items()}
|
||
|
||
|
||
def test_줄은_0점1원_소계는_1원_버림_줄합이_소계():
|
||
got = labor.run_line(_whole(), KEY, {})
|
||
base = got["기준"]
|
||
assert all(_tenth(x.get("금액")) for x in got["줄"])
|
||
assert all(Decimal(str(base[k])) == int(base[k]) for k in COSTS)
|
||
assert {k: Decimal(str(base[k])) for k in COSTS} == _sums_of_lines(got["줄"])
|
||
assert base["계"] == sum(base[k] for k in COSTS)
|
||
|
||
|
||
def test_B09_산근_끝수와_같음():
|
||
"""엔진 날 줄을 B09 규칙(줄 ROUNDDOWN 1 · % = 버린 밑 × 율 · 비목 ROUNDDOWN 0)으로 = 끝수 답."""
|
||
whole = _whole()
|
||
raw = labor.run_line(whole, KEY, {}, raw=True)
|
||
cut = labor.run_line(whole, KEY, {})
|
||
lines = []
|
||
for x in raw["줄"][: len(whole.logic(KEY)["호표"])]:
|
||
lines.append(
|
||
{k: sg._money(float(x["단가"] or 0), float(x["수량"] or 0)) for k in x["비목"]}
|
||
)
|
||
under = {k: sum(ln.get(k, 0) for ln in lines) for k in COSTS}
|
||
for x in raw["줄"][len(lines) :]: # % 덧줄 = 버린 밑 × 율 / 100 → ROUNDDOWN 1
|
||
(part,) = x["비목"]
|
||
base = under["재료비"] if "재료" in x["이름"] or "소모" in x["이름"] else under["노무비"]
|
||
lines.append({part: sg.rounddown(base * float(x["율"]) / 100, 1)})
|
||
b09 = sg.block_totals([{"labor": ln.get("노무비", 0), "material": ln.get("재료비", 0),
|
||
"expense": ln.get("경비", 0)} for ln in lines]) # fmt: skip
|
||
assert (b09["labor"], b09["material"], b09["expense"], b09["sum"]) == tuple(
|
||
float(cut["기준"][k]) for k in (*COSTS, "계")
|
||
)
|
||
assert raw["기준"]["계"] != cut["기준"]["계"] # 날 값은 버리기 전(B09 몫)
|
||
|
||
|
||
def test_화면_넷_같은_값_SK_시험계산_읽는식():
|
||
whole = _whole()
|
||
doc = sd.clean(
|
||
{"키": "SK-000000", "이름": "끝수", "단위": "㎡", "바탕": {"로직": KEY}}, None, whole
|
||
)
|
||
sk = calc.run(doc, whole)["기준"]
|
||
auto = {k: v for k, v in (m01.auto(KEY)["값"] or {}).items() if v is not None}
|
||
m1 = m01.calc(KEY, auto)
|
||
text = m01.text(KEY, auto)
|
||
assert float(sk["계"]) == m1["sums"]["계"] == float(text["계"])
|
||
assert {g["비목"]: float(g["소계"]) for g in text["groups"]} == {k: float(sk[k]) for k in COSTS}
|
||
line = next(x for g in text["groups"] for x in g["줄"] if x["이름"].startswith("형틀"))
|
||
assert line["글"].endswith(" = " + _line_text(line["금액"])) and line["글"].endswith(".9")
|
||
|
||
|
||
def _line_text(v) -> str:
|
||
from common_util.common_util_money import fmt_line_money
|
||
|
||
return fmt_line_money(Decimal(str(v)))
|
||
|
||
|
||
def test_하위_로직_줄은_하위_계를_버려_단가로():
|
||
"""기계 · 하위 로직 줄 단가 = 하위 로직 끝수 계(1 원) ·
|
||
비목마다 하위 비목 × 수량 0.1 원 버림."""
|
||
whole = _whole()
|
||
key = next(
|
||
str(r["키"])
|
||
for _, d in m01._logic_files(m01.cm.load(folder=m01.FOLDER))
|
||
for r in d.get("줄") or []
|
||
if any(i.get("종류") == "로직" for i in r.get("호표") or [])
|
||
and "결과" not in r
|
||
and not isinstance(r.get("흐름"), dict)
|
||
and labor.run_line(whole, str(r["키"]), {}).get("기준")
|
||
)
|
||
got = labor.run_line(whole, key, {})
|
||
for item, x in zip(whole.logic(key)["호표"], got["줄"]):
|
||
if item.get("종류") == "로직" and x.get("단가") is not None and x.get("수량") is not None:
|
||
assert Decimal(str(x["단가"])) == int(x["단가"])
|
||
parts = [
|
||
Decimal(str(v)) for v in x["비목"].values()
|
||
] # 비목마다 0.1 원 버림 · 금액 = 합
|
||
assert all(_tenth(v) for v in parts) and Decimal(str(x["금액"])) == sum(parts)
|
||
assert abs(Decimal(str(x["단가"])) * x["수량"] - sum(parts)) < Decimal("0.3")
|
||
|
||
|
||
def test_날_값은_엔진_그대로():
|
||
whole = _whole()
|
||
eng = mf.run(whole, KEY, {**m01.mau.values(whole, whole.logic(KEY))})
|
||
raw = labor.run_line(whole, KEY, {}, raw=True)
|
||
assert raw["기준"]["계"] == eng["계"]
|
||
|
||
|
||
def test_일위대가_반영_줄_0점1원_합_1원():
|
||
doc = labor.clean({"담은로직": [{"로직": KEY, "수량": {"손": 3.3}}]})
|
||
out = labor.calc(doc, {})
|
||
line = out["줄"][0]["반영"]
|
||
assert all(_tenth(line[k]) for k in COSTS) and line["계"] == sum(line[k] for k in COSTS)
|
||
total = out["반영합"]
|
||
assert all(float(total[k]) == int(total[k]) for k in COSTS)
|
||
assert {k: Decimal(str(total[k])) for k in COSTS} == {
|
||
k: mcut.down(Decimal(str(line[k])), 0) for k in COSTS
|
||
}
|
||
|
||
|
||
def test_별도계상_뺀_기준은_남은_줄_합_버림():
|
||
whole = _whole()
|
||
got = labor.run_line(whole, KEY, {})
|
||
marks = {str(m["차례"]): {"별도계상": "직접계산"} for m in got["재료"]}
|
||
marks[str(got["재료"][0]["차례"])] = {"별도계상": "별도계상"} # 첫 재료만 뺌
|
||
out = labor.drop_apart(got, marks)
|
||
assert len(out["줄"]) == len(got["줄"]) - 1
|
||
assert {k: Decimal(str(out["기준"][k])) for k in COSTS} == _sums_of_lines(out["줄"])
|