feat(b08,b09): 폐기물처리비 — 경비 비목(일반관리비·이윤 밑수) · 임목폐기물 톤 · 분리발주 칸
- 원가계산서: 폐기물처리비를 경비 줄로(예정가격작성기준 제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
This commit is contained in:
@@ -48,6 +48,8 @@ def _preparation_rows(preparation_table: dict[str, Any]) -> list[dict[str, Any]]
|
||||
status = str(row.get("status") or "")
|
||||
amount = row.get("amount")
|
||||
ready = status == PREP_READY and amount is not None
|
||||
# 값은 서도 **내역 줄이 아닌 줄**(경비 — 임목폐기물 처리). 막힘이 아니라 가는 곳이 다름.
|
||||
billable = ready and row.get("bill") is not False
|
||||
rows.append(
|
||||
{
|
||||
"work_item_code": row.get("work_item_code"),
|
||||
@@ -88,8 +90,14 @@ def _preparation_rows(preparation_table: dict[str, Any]) -> list[dict[str, Any]]
|
||||
"spec_class_basis": str(row.get("reason") or ""),
|
||||
"composite_not_ready": None,
|
||||
# 값이 없는 줄은 **내역에 세우지 않는다** — 0 원 줄을 만들면 더 나쁘다.
|
||||
"in_bill": ready,
|
||||
"in_bill_reason": "" if ready else str(row.get("reason") or status),
|
||||
"in_bill": billable,
|
||||
"in_bill_reason": (
|
||||
""
|
||||
if billable
|
||||
else str(row.get("bill_reason") or "")
|
||||
if ready
|
||||
else str(row.get("reason") or status)
|
||||
),
|
||||
"origin": ORIGIN_PREPARATION,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -56,6 +56,7 @@ from B08_Quantity.B08_Quantity_Engine_Preparation_Status import ( # noqa: E402
|
||||
STATUS_PENDING,
|
||||
STATUS_READY,
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation_TreeWaste import tree_waste_rows # noqa: E402
|
||||
|
||||
__all__ = ["ANCILLARY_ITEMS", "ancillary_rows"] # 갈라 나간 뒤에도 여기서 읽을 수 있게
|
||||
|
||||
@@ -667,6 +668,9 @@ def build_table(
|
||||
chipping_volume_m3: Any = None,
|
||||
road_surface_area_m2: float | None = None,
|
||||
topsoil_road_only: bool = False,
|
||||
tree_waste: dict[str, Any] | None = None,
|
||||
tree_waste_unit_price_krw_per_ton: Any = None,
|
||||
waste_separate_order: Any = False,
|
||||
) -> dict[str, Any]:
|
||||
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
|
||||
rows = (
|
||||
@@ -681,6 +685,10 @@ def build_table(
|
||||
)
|
||||
+ erosion_rows(structures, names)
|
||||
+ chipping_rows(chipping_enabled, chipping_volume_m3)
|
||||
# 임목폐기물 — 톤 · 비목은 경비(내역 줄 아님 · 2026-09-14 판정).
|
||||
+ tree_waste_rows(
|
||||
slope_totals, tree_waste, tree_waste_unit_price_krw_per_ton, waste_separate_order
|
||||
)
|
||||
+ ancillary_rows(ancillary_counts)
|
||||
)
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""준비공 — 임목폐기물 처리 한 줄 (톤) · 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]
|
||||
@@ -174,6 +174,10 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
||||
road_surface_area(slopes),
|
||||
# 대상 — 기본 노면 + 절토(별표2 문언) · 노면만은 설계자가 고름(2026-09-14 판정).
|
||||
topsoil_target(settings) == "road_only",
|
||||
# 임목폐기물 — 현장 조사값 · 수동 처리단가 · 분리발주(2026-09-14 판정).
|
||||
settings.get("tree_waste") or {},
|
||||
settings.get("tree_waste_unit_price_krw_per_ton"),
|
||||
settings.get("waste_separate_order"),
|
||||
)
|
||||
method, method_is_default = concrete_placing_method(settings)
|
||||
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
||||
@@ -456,6 +460,10 @@ class QuantitySettingsBody(BaseModel):
|
||||
wood_chipping_volume_m3: float | None = None
|
||||
# 노체다짐 — 기본 꺼짐(9-16-2 [주]⑤ 조건부). 켜면 토공집계에 별도 줄.
|
||||
subgrade_compaction_enabled: bool | None = None
|
||||
# 임목폐기물 — 현장 조사값 넷(통째로 갈아 끼움) · 수동 처리단가 · 분리발주.
|
||||
tree_waste: dict[str, float | None] | None = None
|
||||
tree_waste_unit_price_krw_per_ton: float | None = None
|
||||
waste_separate_order: bool | None = None
|
||||
# 토량환산계수(다짐) — `{갈래: {"compacted": C, "reason": 사유}}`.
|
||||
# ⚠ **기본값을 복사해 넣지 않는다** — 안 고른 갈래는 키가 없어야 정본이 선다.
|
||||
# 빈 dict 는 「전부 기본값으로 되돌림」이라 통째로 갈아 끼운다.
|
||||
@@ -474,6 +482,7 @@ NULLABLE_SETTING_KEYS = (
|
||||
"topsoil_haul_distance_m",
|
||||
"wood_chipping_volume_m3",
|
||||
"dozer_haul_limit_m",
|
||||
"tree_waste_unit_price_krw_per_ton",
|
||||
)
|
||||
|
||||
|
||||
@@ -515,6 +524,13 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
method = values["concrete_placing_method"]
|
||||
# 「안 정함」으로 되돌릴 수 있어야 한다 — 빈 값이면 지운다(8-22 ② 와 같은 자리).
|
||||
values["concrete_placing_method"] = method if method in CONCRETE_PLACING_METHODS else None
|
||||
if "tree_waste" in values:
|
||||
# 양수만 남긴다 — 빈 칸은 키가 없어야 「안 넣음」으로 읽힌다.
|
||||
values["tree_waste"] = {
|
||||
key: float(value)
|
||||
for key, value in (values["tree_waste"] or {}).items()
|
||||
if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0
|
||||
}
|
||||
if "topsoil_target" in values:
|
||||
# 기본(노면 + 절토)은 저장하지 않는다 — 「안 정함」과 같게 둬 법 문언이 선다.
|
||||
target = values["topsoil_target"]
|
||||
@@ -554,6 +570,7 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
"material_supply",
|
||||
"concrete_placing_method",
|
||||
"topsoil_target",
|
||||
"tree_waste",
|
||||
"ancillary_counts",
|
||||
# 고른 계수를 **기본값으로 되돌릴 길**이 있어야 한다 — 병합이면 못 지운다.
|
||||
"conversion_factors_override",
|
||||
|
||||
@@ -108,6 +108,12 @@ export interface QuantitySettings {
|
||||
wood_chipping_volume_m3?: number | null;
|
||||
/** 노체다짐 — 기본 꺼짐(9-16-2 [주]⑤ 조건부). 켜면 토공집계에 별도 줄. */
|
||||
subgrade_compaction_enabled?: boolean | null;
|
||||
/** 임목폐기물 조사값 넷 — 비면 톤이 안 선다. */
|
||||
tree_waste?: Record<string, number | null>;
|
||||
/** 처리단가(원/톤) — 수동 · 미확정. */
|
||||
tree_waste_unit_price_krw_per_ton?: number | null;
|
||||
/** 폐기물 분리발주 — 기본 아님. */
|
||||
waste_separate_order?: boolean | null;
|
||||
}
|
||||
|
||||
/** 갈래 하나의 「무엇을 골랐나」. 서버 `earthwork_conversion_choices` 와 짝이다. */
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from "./B08_Quantity_UI_MaterialGrid";
|
||||
import { renderStructureSheets } from "./B08_Quantity_UI_StructureSheet";
|
||||
import { renderStructureSummary } from "./B08_Quantity_UI_StructureSummary";
|
||||
import { appendTreeWasteFields } from "./B08_Quantity_UI_Side_TreeWaste";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
@@ -109,6 +110,10 @@ async function saveQuantitySettings(projectId: string, draft: DraftSettings): Pr
|
||||
wood_chipping_enabled: draft.wood_chipping_enabled,
|
||||
wood_chipping_volume_m3: draft.wood_chipping_volume_m3,
|
||||
subgrade_compaction_enabled: draft.subgrade_compaction_enabled,
|
||||
// 조사값은 **통째로** — 지운 칸까지 가야 되돌릴 길이 있다.
|
||||
tree_waste: draft.tree_waste,
|
||||
tree_waste_unit_price_krw_per_ton: draft.tree_waste_unit_price_krw_per_ton,
|
||||
waste_separate_order: draft.waste_separate_order,
|
||||
// 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다.
|
||||
ancillary_counts: draft.ancillary_counts,
|
||||
// 토량환산계수 — 고른 갈래만 담긴다. 빈 dict 는 「전부 기본값으로 되돌림」이다.
|
||||
@@ -300,6 +305,10 @@ interface DraftSettings {
|
||||
wood_chipping_volume_m3: number | null;
|
||||
// 노체다짐 — **기본 꺼짐**(9-16-2 [주]⑤ 조건부). 켜면 토공집계에 별도 줄(수량 = 성토량).
|
||||
subgrade_compaction_enabled: boolean;
|
||||
// 임목폐기물 — 조사값 넷 · 수동 처리단가(미확정) · 분리발주(기본 아님).
|
||||
tree_waste: Record<string, number | null>;
|
||||
tree_waste_unit_price_krw_per_ton: number | null;
|
||||
waste_separate_order: boolean;
|
||||
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
||||
material_supply: Record<string, SupplyChoice>;
|
||||
// 갈래별 토량환산계수(다짐) — `compacted` 가 `null` 이면 「안 고름」이라 기본값이 선다.
|
||||
@@ -703,6 +712,9 @@ function buildQuantitySidePanel(
|
||||
);
|
||||
panel.append(hintRow(L("B08_Quantity_Side_SubgradeCompaction_Hint")));
|
||||
|
||||
// ── 임목폐기물 — 조사값 넷 · 수동 처리단가 · 분리발주(2026-09-14). 칸은 따로 뺀 파일에서.
|
||||
appendTreeWasteFields(panel, draft, { field, optionalNumberField, selectField, hintRow });
|
||||
|
||||
// ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ──
|
||||
panel.append(field(L("B08_Quantity_Side_Placing"), ""));
|
||||
panel.append(
|
||||
@@ -1001,6 +1013,10 @@ export async function renderB08Quantity(root: HTMLElement): Promise<void> {
|
||||
wood_chipping_enabled: Boolean(stored.wood_chipping_enabled),
|
||||
wood_chipping_volume_m3: (stored.wood_chipping_volume_m3 as number | null) ?? null,
|
||||
subgrade_compaction_enabled: Boolean(stored.subgrade_compaction_enabled),
|
||||
tree_waste: { ...((stored.tree_waste ?? {}) as Record<string, number | null>) },
|
||||
tree_waste_unit_price_krw_per_ton:
|
||||
(stored.tree_waste_unit_price_krw_per_ton as number | null) ?? null,
|
||||
waste_separate_order: Boolean(stored.waste_separate_order),
|
||||
ancillary_counts: {
|
||||
...((stored.ancillary_counts ?? {}) as Record<string, number | null>),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* =============================================================================
|
||||
* B08_Quantity_UI_Side_TreeWaste.ts
|
||||
* 산출 조건 「임목폐기물」 구획 — 현장 조사값 넷 · 수동 처리단가 · 분리발주(2026-09-14 판정).
|
||||
*
|
||||
* 페이지 본문(`B08_Quantity_UI_Page.ts`)이 700줄을 넘어 새 칸을 이 파일로 뺌(CLAUDE.md 4장).
|
||||
* ⚠ 톤은 서버가 셈 — 여기는 값만 캐시에 쌓고 [저장]에서 정본으로 감(5장).
|
||||
* ========================================================================== */
|
||||
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
export interface TreeWasteDraft {
|
||||
tree_waste: Record<string, number | null>;
|
||||
tree_waste_unit_price_krw_per_ton: number | null;
|
||||
waste_separate_order: boolean;
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
/** 페이지의 칸 만들기 도우미 — 같은 모양을 쓰려고 받아 씀. */
|
||||
export interface SideFieldHelpers {
|
||||
field(label: string, value: string): HTMLElement;
|
||||
optionalNumberField(
|
||||
label: string,
|
||||
value: number | null,
|
||||
step: string,
|
||||
onInput: (value: number | null) => void,
|
||||
): HTMLElement;
|
||||
selectField(
|
||||
label: string,
|
||||
value: string,
|
||||
options: { value: string; label: string }[],
|
||||
onChange: (value: string) => void,
|
||||
): HTMLElement;
|
||||
hintRow(text: string): HTMLElement;
|
||||
}
|
||||
|
||||
/** 조사값 칸 — 서버 `TREE_WASTE_INPUTS` 와 같은 키. */
|
||||
const INPUTS: [string, keyof typeof ui_locales, string][] = [
|
||||
["stems_per_1000m2", "B08_Quantity_TreeWaste_Stems", "1"],
|
||||
["dbh_cm", "B08_Quantity_TreeWaste_Dbh", "0.1"],
|
||||
["height_m", "B08_Quantity_TreeWaste_Height", "0.1"],
|
||||
["unit_weight_kg_m3", "B08_Quantity_TreeWaste_UnitWeight", "10"],
|
||||
];
|
||||
|
||||
/** 수동 단가 칸 — 값이 있으면 빨간 테두리(미확정). */
|
||||
function markManual(row: HTMLElement, value: number | null): void {
|
||||
const input = row.querySelector("input");
|
||||
if (input) input.style.outline = value ? "2px solid var(--color-danger, #d9534f)" : "";
|
||||
}
|
||||
|
||||
export function appendTreeWasteFields(
|
||||
panel: HTMLElement,
|
||||
draft: TreeWasteDraft,
|
||||
h: SideFieldHelpers,
|
||||
): void {
|
||||
panel.append(h.field(L("B08_Quantity_Side_TreeWaste"), ""));
|
||||
for (const [key, label, step] of INPUTS) {
|
||||
panel.append(
|
||||
h.optionalNumberField(L(label), draft.tree_waste[key] ?? null, step, (value) => {
|
||||
draft.tree_waste[key] = value;
|
||||
draft.dirty = true;
|
||||
}),
|
||||
);
|
||||
}
|
||||
panel.append(h.hintRow(L("B08_Quantity_Side_TreeWaste_Hint")));
|
||||
|
||||
const price = h.optionalNumberField(
|
||||
L("B08_Quantity_TreeWaste_Price"),
|
||||
draft.tree_waste_unit_price_krw_per_ton,
|
||||
"1000",
|
||||
(value) => {
|
||||
draft.tree_waste_unit_price_krw_per_ton = value;
|
||||
draft.dirty = true;
|
||||
markManual(price, value);
|
||||
},
|
||||
);
|
||||
markManual(price, draft.tree_waste_unit_price_krw_per_ton);
|
||||
panel.append(price);
|
||||
const priceHint = h.hintRow(L("B08_Quantity_TreeWaste_Price_Hint"));
|
||||
priceHint.classList.add("b08-quantity__hint--warn");
|
||||
panel.append(priceHint);
|
||||
|
||||
panel.append(
|
||||
h.selectField(
|
||||
L("B08_Quantity_TreeWaste_Separate"),
|
||||
draft.waste_separate_order ? "on" : "",
|
||||
[
|
||||
{ value: "", label: L("B08_Quantity_TreeWaste_Separate_Off") },
|
||||
{ value: "on", label: L("B08_Quantity_TreeWaste_Separate_On") },
|
||||
],
|
||||
(value) => {
|
||||
draft.waste_separate_order = value === "on";
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
panel.append(h.hintRow(L("B08_Quantity_TreeWaste_Separate_Hint")));
|
||||
}
|
||||
@@ -236,6 +236,8 @@ export interface PreparationRow {
|
||||
reason: string;
|
||||
reference_amount?: number;
|
||||
work_item_code: string | null;
|
||||
/** 수동 단가로 선 금액 수 — 「미확정 N건」·빨간 테두리(임목폐기물 처리). */
|
||||
unconfirmed?: number;
|
||||
}
|
||||
|
||||
export interface PreparationTable {
|
||||
@@ -260,6 +262,14 @@ export function renderPreparationGrid(
|
||||
const caption = document.createElement("p");
|
||||
caption.className = "b08-grid__caption";
|
||||
caption.textContent = `${table.row_count}줄 · 값을 낼 근거가 아직 없는 줄 ${table.pending_count}개`;
|
||||
const unconfirmed = table.rows.reduce((sum, row) => sum + (row.unconfirmed ?? 0), 0);
|
||||
if (unconfirmed) {
|
||||
const badge = document.createElement("span");
|
||||
badge.style.cssText =
|
||||
"margin-left:8px;padding:0 6px;border-radius:8px;color:#fff;background:var(--color-danger,#d9534f);font-size:12px";
|
||||
badge.textContent = `미확정 ${unconfirmed}건`;
|
||||
caption.append(badge);
|
||||
}
|
||||
wrap.append(caption);
|
||||
|
||||
const scroller = document.createElement("div");
|
||||
@@ -291,6 +301,8 @@ export function renderPreparationGrid(
|
||||
if (row.reference_amount) {
|
||||
note.append(document.createTextNode(` (참고 면적 ${num(row.reference_amount, 1)})`));
|
||||
}
|
||||
// 수동 단가로 선 금액이 든 줄 — 빨간 테두리(미확정).
|
||||
if (row.unconfirmed) note.style.outline = "2px solid var(--color-danger, #d9534f)";
|
||||
tr.append(note);
|
||||
// 값을 못 세운 줄의 「수량」은 **막힘** — 근거가 오면 채워질 자리라 제외과 갈라 보인다.
|
||||
markRow(tr, PREPARATION_KEYS, row.amount === null ? { 3: "blocked" } : undefined);
|
||||
|
||||
@@ -88,10 +88,14 @@ class CostInput:
|
||||
#: 이윤 수동 조정액 — 설계자 명시 입력일 때만. 프로그램이 스스로 채우지 않는다.
|
||||
profit_adjustment_krw: Decimal = _ZERO
|
||||
|
||||
#: 폐기물처리비 — 요율이 아니라 **실비**. 총원가 밖, 관급자재대와 나란히.
|
||||
#: TODO(미결 PLAN 8-14·9-6): 자리 확정 대기 (사용자 「실무자 확인 후 재공유」).
|
||||
#: 실무 근거는 거창 원가계산서의 `총공사비 = 도급액 + 관급자재대 + 폐기물처리비` 한 줄뿐.
|
||||
#: 폐기물처리비 — 요율이 아니라 **실비**(수량 × 처리단가).
|
||||
#: ⭐ 2026-09-14 판정 — **비목은 경비**(예정가격작성기준 제19조③18호)라 순공사원가에 들고
|
||||
#: **일반관리비·이윤 밑수에 든다.** 빠지면 총액이 조용히 작아진다.
|
||||
#: ⚠ 법정경비 밑수(직접공사비·노무비)에는 안 넣는다 — 그 밑수는 요율 데이터가 정한 합이다.
|
||||
waste_disposal_krw: Decimal = _ZERO
|
||||
#: 분리발주 — 켜면 공사와 따로 발주하는 용역이라 **총원가 밖**, 총공사비에만 더한다
|
||||
#: (거창 원가계산서 `총공사비 = 도급액 + 관급자재대 + 폐기물처리비` 모양). 기본 꺼짐.
|
||||
waste_separate_order: bool = False
|
||||
|
||||
#: 환경보전비 공종 (`rate_environment.all_work_types` 의 값).
|
||||
#: TODO(미결 PLAN 9-6): 임도가 「도로 0.9 %」인지 「기타 토목 0.8 %」인지 미확정.
|
||||
@@ -353,12 +357,14 @@ def _calculate_with_scale(
|
||||
scale_reference=scale,
|
||||
)
|
||||
statutory = statutory_expenses(dataset, data, ctx, result, emit)
|
||||
# 폐기물처리비 — 분리발주가 아니면 경비 한 줄. 법정경비 **뒤**에 둬 그 밑수에 안 섞인다.
|
||||
waste_in_expense = _ZERO if data.waste_separate_order else _waste_line(data, emit)
|
||||
|
||||
expense_total = data.direct_expense_krw + statutory
|
||||
expense_total = data.direct_expense_krw + statutory + waste_in_expense
|
||||
emit(
|
||||
key="expense",
|
||||
name="경비",
|
||||
base_label="직접경비(산출경비)+법정경비",
|
||||
base_label="직접경비(산출경비)+법정경비" + ("+폐기물처리비" if waste_in_expense else ""),
|
||||
base=expense_total,
|
||||
amount=expense_total,
|
||||
)
|
||||
@@ -413,13 +419,14 @@ def _calculate_with_scale(
|
||||
)
|
||||
|
||||
owner_total = _owner_supplied_line(data, emit)
|
||||
waste = _waste_line(data, emit)
|
||||
# 분리발주면 공사 원가가 아니다 — 총원가 밖에 서고 총공사비에만 더한다.
|
||||
waste = _waste_line(data, emit) if data.waste_separate_order else _ZERO
|
||||
|
||||
grand_total = contract_amount + owner_total + waste
|
||||
emit(
|
||||
key="grand_total",
|
||||
name="총공사비",
|
||||
base_label="도급공사비+관급자재대" + ("+폐기물처리비" if waste else ""),
|
||||
base_label="도급공사비+관급자재대" + ("+폐기물처리비(분리발주)" if waste else ""),
|
||||
base=grand_total,
|
||||
amount=grand_total,
|
||||
)
|
||||
@@ -436,7 +443,7 @@ def _calculate_with_scale(
|
||||
"vat": vat,
|
||||
"contract_amount": contract_amount,
|
||||
"owner_supplied_material_total": owner_total,
|
||||
"waste_disposal": waste,
|
||||
"waste_disposal": waste_in_expense + waste,
|
||||
"grand_total": grand_total,
|
||||
}
|
||||
return result
|
||||
@@ -508,16 +515,20 @@ def _owner_supplied_line(data: CostInput, emit) -> Decimal:
|
||||
|
||||
|
||||
def _waste_line(data: CostInput, emit) -> Decimal:
|
||||
"""폐기물처리비 — 요율이 아니라 실비. 설계자 입력이 있을 때만 줄이 선다."""
|
||||
"""폐기물처리비 — 요율이 아니라 실비. 금액이 있을 때만 줄이 선다."""
|
||||
if not data.waste_disposal_krw:
|
||||
return _ZERO
|
||||
return emit(
|
||||
key="waste_disposal",
|
||||
name="폐기물처리비",
|
||||
base_label="설계자 입력(실비)",
|
||||
base_label="수량 × 처리단가(실비)",
|
||||
base=data.waste_disposal_krw,
|
||||
amount=floor_won(data.waste_disposal_krw),
|
||||
note="⚠ 자리 미확정 — 실무 관측 한 줄이 유일한 근거 (PLAN 8-14)",
|
||||
note=(
|
||||
"분리발주 — 총원가 밖 별도 표기"
|
||||
if data.waste_separate_order
|
||||
else "경비(예정가격작성기준 제19조③18호) — 일반관리비·이윤 밑수에 듦"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ class CostRequest(BaseModel):
|
||||
estimated_price_krw: Decimal | None = None
|
||||
profit_adjustment_krw: Decimal = Field(default=Decimal(0), ge=0)
|
||||
waste_disposal_krw: Decimal = Field(default=Decimal(0), ge=0)
|
||||
#: 폐기물 분리발주 — 기본 아님(경비로 원가에 듦). 켜면 총원가 밖.
|
||||
waste_separate_order: bool = False
|
||||
|
||||
environment_work_type: str = "civil_road"
|
||||
equipment_guarantee_work_type: str = "civil_general"
|
||||
@@ -116,6 +118,7 @@ class CostRequest(BaseModel):
|
||||
estimated_price_krw=self.estimated_price_krw,
|
||||
profit_adjustment_krw=self.profit_adjustment_krw,
|
||||
waste_disposal_krw=self.waste_disposal_krw,
|
||||
waste_separate_order=self.waste_separate_order,
|
||||
environment_work_type=self.environment_work_type,
|
||||
equipment_guarantee_work_type=self.equipment_guarantee_work_type,
|
||||
subcontract_guarantee_variant=self.subcontract_guarantee_variant,
|
||||
|
||||
@@ -160,6 +160,13 @@ def default_settings() -> dict[str, Any]:
|
||||
# 노체다짐(9-16-2) — **기본 꺼짐**(2026-09-13 판정). [주]⑤ 「대규모 성토지로서
|
||||
# 층다짐이 필요한 경우 적용」 조건부라 늘 서지 않는다. 켜면 토공집계에 별도 줄.
|
||||
"subgrade_compaction_enabled": False,
|
||||
# 임목폐기물 — 현장 조사값 넷 `{stems_per_1000m2, dbh_cm, height_m, unit_weight_kg_m3}`.
|
||||
# ⚠ 기본값을 두지 않는다 — 비면 톤이 안 선다(수종·임분마다 달라 지어내지 않음).
|
||||
"tree_waste": {},
|
||||
# 처리단가(원/톤) — KCRA 중간처리단가 미확보라 **수동**. 넣으면 금액이 서되 미확정.
|
||||
"tree_waste_unit_price_krw_per_ton": None,
|
||||
# 폐기물 분리발주 — 기본 아님(경비로 원가에 듦 · 2026-09-14 판정).
|
||||
"waste_separate_order": False,
|
||||
"dataset_versions": {},
|
||||
},
|
||||
"estimation": {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""임목폐기물 처리 — 톤으로 서는가 · 경비라 내역에 안 서는가 (2026-09-14 판정).
|
||||
|
||||
⚠ 산식 — 한국건설기술연구원(2012) 3.2: WA = 0.5 × π × (B/2)² × h × 1.3 × W1 × N · WR = WA × 15/85.
|
||||
⚠ 실정보고 예(p26) 「V1 = 0.5×π×(0.16/2)²×10.0×1.3×134/1000×7,450 = 130.40㎥」 로
|
||||
지상부 부피를 대 봄.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation_TreeWaste import ( # noqa: E402
|
||||
tree_waste_rows,
|
||||
tree_waste_tons,
|
||||
)
|
||||
|
||||
SLOPE = {"tree_removal_fill": 4000.0, "tree_removal_cut": 3450.0} # 7,450㎡
|
||||
FIELD = {"stems_per_1000m2": 134, "dbh_cm": 16, "height_m": 10.0, "unit_weight_kg_m3": 1000}
|
||||
|
||||
|
||||
def test_지상부는_실정보고_부피와_같다() -> None:
|
||||
"""W1 = 1,000㎏/㎥ 로 두면 지상부 톤 = 지상부 부피(㎥) — 실정보고 130.40㎥."""
|
||||
above, root = tree_waste_tons(7450.0, FIELD)
|
||||
# 실정보고는 π 를 3.14 로 셈 — 같은 π 로 되돌리면 130.40 과 같다.
|
||||
assert above * 3.14 / math.pi == pytest.approx(130.40, abs=0.01)
|
||||
assert root == pytest.approx(above * 15 / 85)
|
||||
|
||||
|
||||
def test_칸이_비면_톤이_안_서고_무엇이_빈지_적는다() -> None:
|
||||
row = tree_waste_rows(SLOPE, {**FIELD, "height_m": None})[0]
|
||||
assert row["amount"] is None and "평균 수고" in row["reason"]
|
||||
assert row["reference_amount"] == 7450.0
|
||||
|
||||
|
||||
def test_단가가_없으면_금액이_안_서고_있으면_미확정() -> None:
|
||||
no_price = tree_waste_rows(SLOPE, FIELD)[0]
|
||||
assert no_price["unit"] == "톤" and no_price["amount"] > 0
|
||||
assert no_price["amount_krw"] is None and no_price["unconfirmed"] == 0
|
||||
priced = tree_waste_rows(SLOPE, FIELD, 60000)[0]
|
||||
assert priced["unconfirmed"] == 1
|
||||
assert priced["amount_krw"] == int(priced["amount"] * 60000)
|
||||
|
||||
|
||||
def test_톤_경계를_알린다() -> None:
|
||||
big = tree_waste_rows(SLOPE, FIELD)[0] # 약 153톤
|
||||
assert big["amount"] >= 100 and "100톤 이상" in big["reason"]
|
||||
small = tree_waste_rows({"tree_removal_fill": 500.0}, FIELD)[0] # 약 10톤
|
||||
assert 5 <= small["amount"] < 100 and "5톤 이상" in small["reason"]
|
||||
|
||||
|
||||
def test_경비라_내역에_안_서고_제외_사유가_간다() -> None:
|
||||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
||||
|
||||
table = build_table(SLOPE, tree_waste=FIELD, tree_waste_unit_price_krw_per_ton=60000)
|
||||
handed = {r["name"]: r for r in build_handoff(preparation_table=table)["work_items"]}
|
||||
row = handed["임목폐기물 처리"]
|
||||
assert row["in_bill"] is False and row["blocked_kind"] is None
|
||||
assert "경비" in row["in_bill_reason"]
|
||||
@@ -0,0 +1,92 @@
|
||||
"""폐기물처리비 자리 — 경비 비목 · 일반관리비·이윤 밑수에 듦 (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")
|
||||
@@ -711,6 +711,27 @@ export const ui_locales_b2 = {
|
||||
"기본은 안 셉니다(확정 5차 5번) — 현장에 따라 필요하면 켜세요. ⚠ 켜도 부피를 넣어야 값이 섭니다(실무는 부피로 셈)",
|
||||
"Off by default — turn on per site. Volume must be entered for the row to carry a quantity",
|
||||
],
|
||||
B08_Quantity_Side_TreeWaste: ["임목폐기물", "Tree Waste"],
|
||||
B08_Quantity_TreeWaste_Stems: ["표본지 1,000㎡당 본수", "Stems per 1,000 ㎡ (sample plot)"],
|
||||
B08_Quantity_TreeWaste_Dbh: ["평균 흉고직경(㎝)", "Mean DBH (cm)"],
|
||||
B08_Quantity_TreeWaste_Height: ["평균 수고(m)", "Mean tree height (m)"],
|
||||
B08_Quantity_TreeWaste_UnitWeight: ["단위체적중량(㎏/㎥)", "Unit weight (kg/㎥)"],
|
||||
B08_Quantity_Side_TreeWaste_Hint: [
|
||||
"현장 조사값 넷을 넣으면 준비공 「임목폐기물 처리」가 톤으로 섭니다. 산식: 지상부 WA = 0.5 × π × (흉고직경/2)² × 수고 × (1+0.3) × 단위체적중량 × 본수 · 뿌리부 WR = WA × 15/85 · 본수 = 1,000㎡당 본수 × 벌목·지장목제거 면적 — 한국건설기술연구원(2012) 3.2. 단위체적중량 참고: 소나무·침엽수 1,210~1,250 · 단풍·산벚 1,250~1,300 · 느티 1,300~1,340 · 상수리·졸참 1,340 이상(조경설계요람). 기본값 없음 — 수종·임분마다 다름",
|
||||
"Enter the four field-survey values to raise the tree-waste row in tonnes. WA = 0.5 × π × (DBH/2)² × height × 1.3 × unit weight × stems; roots WR = WA × 15/85 (KICT 2012). No defaults — values differ by species and stand",
|
||||
],
|
||||
B08_Quantity_TreeWaste_Price: ["처리단가(원/톤) — 수동", "Disposal price (KRW/t) — manual"],
|
||||
B08_Quantity_TreeWaste_Price_Hint: [
|
||||
"⚠ 공표 단가 없음(한국건설자원협회 중간처리단가 미확보) — 넣으면 금액이 서되 「미확정」으로 셉니다(빨간 테두리). 비목은 경비 — 원가계산서 폐기물처리비 줄로 가고 일반관리비·이윤 밑수에 듭니다(예정가격작성기준 제19조③18호)",
|
||||
"No published price — a manual price raises the amount as unconfirmed (red outline). Cost category is expense, inside the overhead and profit bases",
|
||||
],
|
||||
B08_Quantity_TreeWaste_Separate: ["분리발주", "Separate disposal contract"],
|
||||
B08_Quantity_TreeWaste_Separate_Off: ["분리발주 아님(기본)", "No (default)"],
|
||||
B08_Quantity_TreeWaste_Separate_On: ["분리발주", "Yes"],
|
||||
B08_Quantity_TreeWaste_Separate_Hint: [
|
||||
"기본은 분리발주 아님 — 원가계산서 경비에 듭니다. 켜면 총원가 밖에 서고 총공사비에만 더합니다. 참고: 건설폐기물 위탁처리 100톤 이상은 분리발주 대상(건설폐기물 업무처리지침)이나, 같은 지침은 임목폐기물을 건설폐기물이 아닌 사업장 일반폐기물로 분류함 · 5톤 이상은 배출자 신고 대상(폐기물관리법 시행령 제2조8호)",
|
||||
"Default: not separate — included in the expense category. When on, it sits outside total cost. Note: the 100 t separate-order rule is for construction waste; tree waste is classified as business general waste. 5 t or more requires a discharge report",
|
||||
],
|
||||
B08_Quantity_Side_SubgradeCompaction: ["노체다짐", "Subgrade Compaction"],
|
||||
B08_Quantity_SubgradeCompaction_Label: ["노체다짐을 셀 것인가", "Count subgrade compaction"],
|
||||
B08_Quantity_Side_SubgradeCompaction_Hint: [
|
||||
|
||||
Reference in New Issue
Block a user