"""각종 중기경비계산서 — 기종마다 한 장 (2026-09-09). 별표2 (5)(가) 가 설계서에 「각종 중기경비계산서」를 넣으라 한다. 중기목록표는 「얼마」 한 줄 이고, 이 장은 **그 값이 나온 과정**이다. ⚠ 겨누는 것 다섯 ① **내역에 선 기종만** 낸다 — 카탈로그 613 을 다 뿌리지 않는다 ② 손료 = 취득가(천원) × 1,000 × 계수 — 천원 단위를 놓치면 1,000배 틀린다 ③ 계산서의 시간당 합계가 **일위대가가 실제로 쓰는 값과 같다**(두 벌이면 안 된다) ④ 부착 장비(브레이커·콤팩터·집게)는 **손료만 드는 것이 정상** — 결함으로 세지 않는다 ⑤ 수송비는 이 장에 안 붙는다 — 「회당」으로 서는 별개 공종이라 자리만 가리킨다 """ from __future__ import annotations import sys from decimal import Decimal from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from B09_Estimation.B09_Estimation_MachineExpenseSheet import ( # noqa: E402 machine_expense_report, ) from B09_Estimation.B09_Estimation_PriceBook import PriceKind # noqa: E402 from B09_Estimation.B09_Estimation_UnitPrice import cached_build # noqa: E402 def _report(): return machine_expense_report(cached_build()) def test_내역에_선_기종만_낸다() -> None: build = cached_build() hourly = {c for c, t in build.book.titles.items() if t.kind is PriceKind.MACHINE_HOURLY} sheets = _report()["sheets"] assert sheets, "계산서가 한 장도 없습니다" assert len(sheets) <= len(hourly) assert {sheet["code"] for sheet in sheets} <= hourly def test_손료가_취득가와_계수로_맞는다() -> None: """② 천원 단위를 놓치면 1,000배 틀린다.""" for sheet in _report()["sheets"]: if sheet["loss_krw_per_hour"] is None or sheet["loss_coefficient"] is None: continue price = Decimal(sheet["price_thousand_krw"]) * Decimal(1000) coefficient = Decimal(sheet["loss_coefficient"]) / Decimal(10**7) assert abs(Decimal(sheet["loss_krw_per_hour"]) - price * coefficient) < Decimal("0.01") def test_계산서_합계가_일위대가와_같은_값이다() -> None: """③ 두 벌로 세면 화면과 금액이 어긋난다.""" build = cached_build() for sheet in machine_expense_report(build)["sheets"]: used = build.book.resolve(sheet["code"]).total assert Decimal(sheet["total_krw"]) == used, sheet["code"] def test_부착_장비는_결함이_아니다() -> None: """④ 제 엔진이 없어 연료·조종원이 본체에 든다(품셈 제8장 [주]⑤).""" sheets = _report()["sheets"] attachments = [sheet for sheet in sheets if sheet["attachment"]] assert attachments, "부착 장비가 한 대도 안 잡혔습니다" for sheet in attachments: assert sheet["gaps"] == [] assert sheet["attachment_note"] assert sheet["loss_krw_per_hour"] is not None # 손료는 있어야 한다 def test_수송비는_이_장에_안_붙는다() -> None: """⑤ 「회당」으로 서는 별개 공종이라 자리만 가리킨다.""" report = _report() assert any("수송비" in line and "회당" in line for line in report["notes"]) for sheet in report["sheets"]: assert "transport" not in sheet def test_같은_기종의_두_장은_갈래로_갈린다() -> None: """⚠ 조합 사용이면 잡재료가 16% 로 줄어 재료비가 달라진다 — 갈래를 안 적으면 똑같은 장이 두 번 나온 것처럼 읽힌다(건설품셈 제8장 [주]⑤).""" sheets = {sheet["code"]: sheet for sheet in _report()["sheets"]} plain = sheets.get("X-0201-0070") combined = sheets.get("X-0201-0070#조합") assert plain is not None and combined is not None assert plain["variant"] == "" and combined["variant"] == "조합" assert combined["misc_material_percent"] == "16" # 조합 쪽이 잡재료가 줄어 재료비가 더 싸야 한다. assert Decimal(combined["material_krw"]) < Decimal(plain["material_krw"])