근거 — 산림청고시 제2025-82호 「금액의 단위표준」 「설계서의 총액 · 원 · 1,000 · 미만버림」.
실무 6건 원가계산서가 여섯 다 000 으로 끝나는 것이 증거.
· 기본 켬 — cut_basis="grand_total" · cut_unit_krw=1000
· 조용히 안 깎음(2026-09-08 합의의 정신) — 이윤 조정액 줄과 결과 메모에 고시 번호를 달고
기준 입력판 11 에서 끌 수 있음을 함께 적음 · 칸 밑 안내도 고쳐 씀
· 끄는 값을 none 으로 바꿈 — 빈 값은 저장이 안 돼 기본으로 되돌아가 끌 수가 없었음
· 진동 고침 — 걸음마다 ÷1.1(종전엔 첫 걸음만). 이윤 1원이 총공사비 1.1원이라
잔차를 그대로 빼면 경계를 지나침(울진 신설 654→999→900→910→909…)
· 안 앉으면 「끝자리 N원이 남음」을 메모로 보임
· 실무 6건 전수 000 판정을 새 벌로 지킴
곁 — tmp/tests/test_b09_cost_fixtures.py 를 resources/tester/ 로 옮김(픽스처 포함).
tmp 쪽 사본은 안 지움 · 겹침은 파일 머리에 적음.
기본이 바뀌어 깨진 남의 시험 6건은 「안 자른 값」 자리에 cut_basis="none" 을 넣어 고침.
전체 시험 1782 통과 · 28 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
100 lines
3.9 KiB
Python
100 lines
3.9 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):
|
|
# ⚠ 절사는 꺼서 본다 — 총공사비가 바뀌면 자동보정액도 따라 바뀌어 **구조**를 못 잰다
|
|
# (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")
|