- 원가계산서: 폐기물처리비를 경비 줄로(예정가격작성기준 제19조③18호) · 법정경비 뒤라 그 밑수엔 안 섞임 - 분리발주 칸(기본 아님) — 켜면 총원가 밖 · 총공사비에만 더함 - 준비공 「임목폐기물 처리」 톤: WA = 0.5·π·(B/2)²·h·1.3·W1·N · WR = WA × 15/85 (한국건설기술연구원 2012) - 조사값 넷(1,000㎡당 본수·흉고직경·수고·단위체적중량) 칸 · 기본값 없음 · 5톤·100톤 경계 알림 - 수동 처리단가 빨간 테두리 + 「미확정 N건」 · 내역엔 안 서고 제외 사유 「경비」 - 시험: 승률 구조(경비·일반관리비·이윤 밑수 증가 · 법정경비 불변 · 분리발주) · 실정보고 부피 대조 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""폐기물처리비 자리 — 경비 비목 · 일반관리비·이윤 밑수에 듦 (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):
|
|
return calculate_cost(
|
|
CostInput(**BASE, waste_disposal_krw=waste, waste_separate_order=separate)
|
|
)
|
|
|
|
|
|
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")
|