"""폐기물처리비 자리 — 경비 비목 · 일반관리비·이윤 밑수에 듦 (2026-09-14 판정). ⚠ 겨누는 것은 **구조**다 — 내역 금액을 박지 않는다(다른 창 절사로 몇 원 움직여도 안 깨지게). ① 분리발주 아님(기본): 경비 한 줄 → 순공사원가·일반관리비 밑수·이윤 밑수가 **그만큼** 는다 ② 법정경비 밑수에는 안 섞인다 — 법정경비 줄은 그대로 ③ 분리발주: 총원가 밖 · 총공사비에만 더한다 근거 — 예정가격작성기준 제19조③18호(경비 세비목 「폐기물처리비」). """ 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_Engine_Cost import CostInput, calculate_cost # noqa: E402 WASTE = Decimal(1_000_000) #: 규모 구간을 못 박는다 — 폐기물 몫으로 구간이 바뀌면 율이 달라져 구조를 못 잰다. BASE = dict( direct_material_krw=Decimal(30_000_000), direct_labor_krw=Decimal(40_000_000), direct_expense_krw=Decimal(10_000_000), estimated_price_krw=Decimal(100_000_000), ) #: 폐기물이 들면 움직여야 하는 줄 — 나머지(법정경비 등)는 한 푼도 안 움직여야 함. MOVES = { "waste_disposal", "expense", "net_construction_cost", "general_overhead", "profit_before_adjustment", "profit", "total_cost", "vat", "contract_amount", "grand_total", } def _run(waste: Decimal, separate: bool = False): # ⚠ 절사는 꺼서 본다 — 총공사비가 바뀌면 자동보정액도 따라 바뀌어 **구조**를 못 잰다 # (2026-09-14 절사 기본 켜짐, 산림청고시 제2025-82호). return calculate_cost( CostInput( **BASE, waste_disposal_krw=waste, waste_separate_order=separate, cut_basis="none", ) ) def test_기본은_경비로_들어_일반관리비_이윤_밑수가_는다() -> None: before, after = _run(Decimal(0)), _run(WASTE) assert after.line("waste_disposal").amount_krw == WASTE assert after.amount("expense") - before.amount("expense") == WASTE assert after.amount("net_construction_cost") - before.amount("net_construction_cost") == WASTE over = ( after.line("general_overhead").base_amount_krw - before.line("general_overhead").base_amount_krw ) assert over == WASTE overhead_gain = after.amount("general_overhead") - before.amount("general_overhead") assert overhead_gain > 0 profit_base_gain = ( after.line("profit_before_adjustment").base_amount_krw - before.line("profit_before_adjustment").base_amount_krw ) assert profit_base_gain == WASTE + overhead_gain assert "폐기물처리비" in after.line("expense").base_label def test_법정경비_줄은_안_움직인다() -> None: before, after = _run(Decimal(0)), _run(WASTE) for line in before.lines: if line.key not in MOVES: assert after.amount(line.key) == line.amount_krw, line.key def test_경비_줄보다_앞에_선다() -> None: keys = [line.key for line in _run(WASTE).lines] assert keys.index("waste_disposal") < keys.index("expense") < keys.index("general_overhead") def test_분리발주면_총원가_밖_총공사비에만() -> None: none, separate = _run(Decimal(0)), _run(WASTE, separate=True) assert separate.amount("total_cost") == none.amount("total_cost") assert separate.amount("grand_total") - none.amount("grand_total") == WASTE keys = [line.key for line in separate.lines] assert keys.index("waste_disposal") > keys.index("contract_amount") assert separate.totals["waste_disposal"] == WASTE def test_금액이_없으면_줄이_안_선다() -> None: assert not _run(Decimal(0)).has("waste_disposal")