- 원가계산서: 폐기물처리비를 경비 줄로(예정가격작성기준 제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
151 lines
6.3 KiB
Python
151 lines
6.3 KiB
Python
"""준비공 — 임목폐기물 처리 한 줄 (톤) · 2026-09-14 판정 「폐기물처리비」.
|
||
|
||
⭐ 수량 산식 — 한국건설기술연구원 「건설현장 임목폐기물의 재활용 및 자원화 기술개발」(2012) 3.2
|
||
지상부 WA = k × π × (B/2)² × h × (1+p) × W1 × N (kg)
|
||
뿌리부 WR = WA × 뿌리 분배비 ÷ 지상 분배비 = WA × 15/85 (일반 수목류, 같은 보고서 표)
|
||
k 수간 형상계수 0.5 · p 지엽 보합률(임목 0.3) — **산식 상수**라 칸이 없다.
|
||
B 평균 흉고직경 · h 평균 수고 · W1 수목 단위체적중량 · N 본수 —
|
||
**현장 조사값**이라 칸으로 받는다.
|
||
N = 표본지 1,000㎡ 실측 본수 × 대상 면적 ÷ 1,000 (실정보고 예 「134/1000 × 7,450」 모양).
|
||
대상 면적 = 벌목·지장목제거 면적(같은 나무를 벤 자리).
|
||
|
||
⚠ 금액은 **처리단가가 없어** 못 선다(KCRA 중간처리단가 미확보) — 수동 단가를 넣으면
|
||
금액이 서되 「미확정」으로 센다(빨간 테두리). 비목은 **경비**라 내역 줄이 아니고
|
||
원가계산서 폐기물처리비 줄로 간다(예정가격작성기준 제19조③18호).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Any
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_Preparation_Status import STATUS_PENDING, STATUS_READY
|
||
|
||
TREE_WASTE_ITEM = "임목폐기물 처리"
|
||
STEM_FORM_FACTOR = 0.5 # k
|
||
FOLIAGE_ALLOWANCE = 0.3 # p — 임목(고립목은 1.0)
|
||
ROOT_SHARE, ABOVE_SHARE = 15.0, 85.0 # 일반 수목류 분배비
|
||
|
||
#: 칸 이름 · 화면 이름 · 단위 — 모두 채워야 수량이 선다.
|
||
TREE_WASTE_INPUTS: tuple[tuple[str, str], ...] = (
|
||
("stems_per_1000m2", "표본지 1,000㎡당 본수"),
|
||
("dbh_cm", "평균 흉고직경(㎝)"),
|
||
("height_m", "평균 수고(m)"),
|
||
("unit_weight_kg_m3", "단위체적중량(㎏/㎥)"),
|
||
)
|
||
TREE_WASTE_BASIS = (
|
||
"한국건설기술연구원(2012) 3.2 임목 발생량 — k 0.5 · p 0.3(임목)"
|
||
" · 뿌리 15 : 지상 85(일반 수목류)"
|
||
" · N = 표본지 1,000㎡ 본수 × 벌목·지장목제거 면적"
|
||
)
|
||
#: 톤 경계 — 화면이 알릴 뿐 값을 바꾸지 않는다.
|
||
REPORT_THRESHOLD_TON = 5.0
|
||
SEPARATE_ORDER_THRESHOLD_TON = 100.0
|
||
EXPENSE_NOTE = (
|
||
"비목은 경비(예정가격작성기준 제19조③18호) — 내역 줄이 아니라 원가계산서 폐기물처리비로 감"
|
||
)
|
||
|
||
|
||
def _positive(value: Any) -> float | None:
|
||
try:
|
||
number = float(value)
|
||
except (TypeError, ValueError):
|
||
return None
|
||
return number if number > 0 else None
|
||
|
||
|
||
def tree_waste_tons(area_m2: float, inputs: dict[str, Any]) -> tuple[float, float] | None:
|
||
"""(지상부 톤, 뿌리부 톤). 칸이 하나라도 비거나 면적이 0 이면 `None`."""
|
||
values = {key: _positive(inputs.get(key)) for key, _ in TREE_WASTE_INPUTS}
|
||
if area_m2 <= 0 or any(value is None for value in values.values()):
|
||
return None
|
||
stems = values["stems_per_1000m2"] * area_m2 / 1000.0
|
||
diameter = values["dbh_cm"] / 100.0
|
||
above_kg = (
|
||
STEM_FORM_FACTOR
|
||
* math.pi
|
||
* (diameter / 2.0) ** 2
|
||
* values["height_m"]
|
||
* (1.0 + FOLIAGE_ALLOWANCE)
|
||
* values["unit_weight_kg_m3"]
|
||
* stems
|
||
)
|
||
return above_kg / 1000.0, above_kg * ROOT_SHARE / ABOVE_SHARE / 1000.0
|
||
|
||
|
||
def tree_waste_rows(
|
||
slope_totals: dict[str, float] | None,
|
||
inputs: dict[str, Any] | None,
|
||
unit_price_krw_per_ton: Any = None,
|
||
separate_order: Any = False,
|
||
) -> list[dict[str, Any]]:
|
||
"""임목폐기물 처리 한 줄 — 값이 없어도 줄은 선다(무엇을 넣으면 풀리는지 적음)."""
|
||
slope = slope_totals or {}
|
||
area = float(slope.get("tree_removal_fill", 0.0)) + float(slope.get("tree_removal_cut", 0.0))
|
||
inputs = inputs or {}
|
||
base = {
|
||
"group": "준비공",
|
||
"item": TREE_WASTE_ITEM,
|
||
"unit": "톤",
|
||
"work_item_code": None,
|
||
# 경비 — 내역에 세우지 않는다(인계가 이 칸을 본다).
|
||
"bill": False,
|
||
"bill_reason": EXPENSE_NOTE,
|
||
"separate_order": bool(separate_order),
|
||
}
|
||
tons = tree_waste_tons(area, inputs)
|
||
if tons is None:
|
||
missing = [label for key, label in TREE_WASTE_INPUTS if _positive(inputs.get(key)) is None]
|
||
why = (
|
||
f"현장 조사값이 비어 있음 — {' · '.join(missing)}"
|
||
if missing
|
||
else "벌목·지장목제거 면적이 0 ㎡ — 사면표가 아직 안 섬"
|
||
)
|
||
return [
|
||
{
|
||
**base,
|
||
"amount": None,
|
||
"status": STATUS_PENDING,
|
||
"reason": f"{why}. 산식: {TREE_WASTE_BASIS}",
|
||
"reference_amount": area,
|
||
}
|
||
]
|
||
above, root = tons
|
||
total = above + root
|
||
notes = [
|
||
f"지상부 WA {above:,.2f}톤 + 뿌리부 WR {root:,.2f}톤(WA × 15/85)",
|
||
TREE_WASTE_BASIS,
|
||
EXPENSE_NOTE,
|
||
]
|
||
if total >= SEPARATE_ORDER_THRESHOLD_TON:
|
||
notes.append(
|
||
"⚠ 100톤 이상 — 건설폐기물이면 분리발주 대상(업무처리지침). 다만 임목폐기물은"
|
||
" 건설폐기물이 아닌 사업장 일반폐기물로 분류됨(같은 지침) — 산출 조건에서 고를 것"
|
||
)
|
||
elif total >= REPORT_THRESHOLD_TON:
|
||
notes.append("5톤 이상 — 배출자 신고 대상 사업장(폐기물관리법 시행령 제2조8호)")
|
||
price = _positive(unit_price_krw_per_ton)
|
||
row = {
|
||
**base,
|
||
"amount": total,
|
||
"status": STATUS_READY,
|
||
"reference_amount": area,
|
||
"above_ground_ton": above,
|
||
"root_ton": root,
|
||
"unit_price_krw_per_ton": price,
|
||
"amount_krw": math.floor(total * price) if price else None,
|
||
# 수동 단가로 선 금액 — 화면 「미확정 N건」·빨간 테두리.
|
||
"unconfirmed": 1 if price else 0,
|
||
}
|
||
if price:
|
||
notes.append(
|
||
f"처리비 {total:,.2f}톤 × {price:,.0f}원 = {math.floor(total * price):,}원"
|
||
" (⚠ 수동 단가 — 미확정 · KCRA 중간처리단가 미확보)"
|
||
)
|
||
else:
|
||
notes.append(
|
||
"처리단가 없음(KCRA 중간처리단가 미확보) — 산출 조건에 수동으로 넣으면 금액이 섬"
|
||
)
|
||
row["reason"] = " · ".join(notes)
|
||
return [row]
|