Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -671,6 +671,7 @@ def build_table(
|
||||
tree_waste: dict[str, Any] | None = None,
|
||||
tree_waste_unit_price_krw_per_ton: Any = None,
|
||||
waste_separate_order: Any = False,
|
||||
tree_waste_root_method: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
|
||||
rows = (
|
||||
@@ -687,7 +688,11 @@ def build_table(
|
||||
+ 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
|
||||
slope_totals,
|
||||
tree_waste,
|
||||
tree_waste_unit_price_krw_per_ton,
|
||||
waste_separate_order,
|
||||
tree_waste_root_method,
|
||||
)
|
||||
+ ancillary_rows(ancillary_counts)
|
||||
)
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
"""준비공 — 임목폐기물 처리 한 줄 (톤) · 2026-09-14 판정 「폐기물처리비」.
|
||||
|
||||
⭐ 수량 산식 — 한국건설기술연구원 「건설현장 임목폐기물의 재활용 및 자원화 기술개발」(2012) 3.2
|
||||
지상부 WA = k × π × (B/2)² × h × (1+p) × W1 × N (kg)
|
||||
뿌리부 WR = WA × 뿌리 분배비 ÷ 지상 분배비 = WA × 15/85 (일반 수목류, 같은 보고서 표)
|
||||
= 환경친화적인 도로건설 지침 부록1 문단 493~508 「3) 임목폐기물」(산출근거: 조경설계요람)
|
||||
지상부 WA = k × 3.14 × (B/2)² × h × (1+p) × W1 × N (kg · 지침 식이 π 를 3.14 로 적음)
|
||||
뿌리부 — 지침이 두 방법을 둔다. 고르는 칸.
|
||||
① 분배비(기본) WR = WA × 15/85 (KICT 일반 수목류 — 2026-09-14 판정 유지)
|
||||
② 뿌리분 체적 WR = 0.3927 × D³ × 1,300 × N (지침 「V = πD²(D/2 + RH2/3) = 0.3927D³」
|
||||
· 「UW1 뿌리부분의 단위중량 1,300kg/m3」). ⚠ D 는 지침이 「뿌리분의 직경(DBH, m)」으로
|
||||
적었으나 2026-09-14 판정으로 **뿌리분 직경**(흉고직경 아님) — 따로 받고 기본값 없음.
|
||||
k 수간 형상계수 0.5 · p 지엽 보합률(임목 0.3) — **산식 상수**라 칸이 없다.
|
||||
B 평균 흉고직경 · h 평균 수고 · W1 수목 단위체적중량 · N 본수 —
|
||||
**현장 조사값**이라 칸으로 받는다.
|
||||
N = 표본지 1,000㎡ 실측 본수 × 대상 면적 ÷ 1,000 (실정보고 예 「134/1000 × 7,450」 모양).
|
||||
대상 면적 = 벌목·지장목제거 면적(같은 나무를 벤 자리).
|
||||
⚠ 실무 실정보고(임목폐기물처리, 한 현장)는 뿌리 14:86 · 무게 0.65톤/㎥ ·
|
||||
처리비를 공급가액 뒤(승률 밖)에 두었다 — 판정은 법 문언·공인 자료 쪽(PLAN 10장).
|
||||
발주처가 요구하면 칸으로 열 자리.
|
||||
|
||||
⚠ 금액은 **처리단가가 없어** 못 선다(KCRA 중간처리단가 미확보) — 수동 단가를 넣으면
|
||||
금액이 서되 「미확정」으로 센다(빨간 테두리). 비목은 **경비**라 내역 줄이 아니고
|
||||
@@ -24,7 +32,11 @@ from B08_Quantity.B08_Quantity_Engine_Preparation_Status import STATUS_PENDING,
|
||||
TREE_WASTE_ITEM = "임목폐기물 처리"
|
||||
STEM_FORM_FACTOR = 0.5 # k
|
||||
FOLIAGE_ALLOWANCE = 0.3 # p — 임목(고립목은 1.0)
|
||||
PI_AS_WRITTEN = 3.14 # 지침 부록1 식 원문 · 실정보고도 3.14
|
||||
ROOT_SHARE, ABOVE_SHARE = 15.0, 85.0 # 일반 수목류 분배비
|
||||
ROOT_BALL_FACTOR = 0.3927 # V = 0.3927 D³ (지침 부록1)
|
||||
ROOT_BALL_UNIT_WEIGHT = 1300.0 # ㎏/㎥ (지침 부록1 UW1)
|
||||
ROOT_METHOD_RATIO, ROOT_METHOD_BALL = "ratio", "root_ball"
|
||||
|
||||
#: 칸 이름 · 화면 이름 · 단위 — 모두 채워야 수량이 선다.
|
||||
TREE_WASTE_INPUTS: tuple[tuple[str, str], ...] = (
|
||||
@@ -34,13 +46,18 @@ TREE_WASTE_INPUTS: tuple[tuple[str, str], ...] = (
|
||||
("unit_weight_kg_m3", "단위체적중량(㎏/㎥)"),
|
||||
)
|
||||
TREE_WASTE_BASIS = (
|
||||
"한국건설기술연구원(2012) 3.2 임목 발생량 — k 0.5 · p 0.3(임목)"
|
||||
" · 뿌리 15 : 지상 85(일반 수목류)"
|
||||
"환경친화적인 도로건설 지침 부록1 · 한국건설기술연구원(2012) 3.2 — k 0.5 · p 0.3(임목) · π 3.14"
|
||||
" · N = 표본지 1,000㎡ 본수 × 벌목·지장목제거 면적"
|
||||
)
|
||||
#: 톤 경계 — 화면이 알릴 뿐 값을 바꾸지 않는다.
|
||||
ROOT_RATIO_BASIS = "뿌리 WR = WA × 15/85(분배비 · 일반 수목류)"
|
||||
ROOT_BALL_BASIS = "뿌리 WR = 0.3927 × D³ × 1,300㎏/㎥ × N(뿌리분 체적 · 지침 부록1)"
|
||||
ROOT_BALL_LABEL = "뿌리분 직경 D(m, 흉고직경 아님 · 굴취 흙덩이 직경)"
|
||||
#: 배출자 신고 경계 — 화면이 알릴 뿐 값을 바꾸지 않는다.
|
||||
REPORT_THRESHOLD_TON = 5.0
|
||||
SEPARATE_ORDER_THRESHOLD_TON = 100.0
|
||||
NOT_SEPARATE_NOTE = (
|
||||
"임목폐기물은 분리발주 대상 아님(환경부 질의회신 — 임목폐기물처리 실정보고 p4 ·"
|
||||
" 건설폐기물 업무처리지침: 건설폐기물 아닌 사업장 일반폐기물)"
|
||||
)
|
||||
EXPENSE_NOTE = (
|
||||
"비목은 경비(예정가격작성기준 제19조③18호) — 내역 줄이 아니라 원가계산서 폐기물처리비로 감"
|
||||
)
|
||||
@@ -54,23 +71,37 @@ def _positive(value: Any) -> float | None:
|
||||
return number if number > 0 else None
|
||||
|
||||
|
||||
def tree_waste_tons(area_m2: float, inputs: dict[str, Any]) -> tuple[float, float] | None:
|
||||
def _missing(inputs: dict[str, Any], root_method: str) -> list[str]:
|
||||
"""비어 있는 칸 이름 — 뿌리분 체적법이면 뿌리분 직경도 든다."""
|
||||
missing = [label for key, label in TREE_WASTE_INPUTS if _positive(inputs.get(key)) is None]
|
||||
if root_method == ROOT_METHOD_BALL and _positive(inputs.get("root_ball_diameter_m")) is None:
|
||||
missing.append(ROOT_BALL_LABEL)
|
||||
return missing
|
||||
|
||||
|
||||
def tree_waste_tons(
|
||||
area_m2: float, inputs: dict[str, Any], root_method: str = ROOT_METHOD_RATIO
|
||||
) -> 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()):
|
||||
if area_m2 <= 0 or _missing(inputs, root_method):
|
||||
return None
|
||||
stems = values["stems_per_1000m2"] * area_m2 / 1000.0
|
||||
diameter = values["dbh_cm"] / 100.0
|
||||
stems = float(inputs["stems_per_1000m2"]) * area_m2 / 1000.0
|
||||
diameter = float(inputs["dbh_cm"]) / 100.0
|
||||
above_kg = (
|
||||
STEM_FORM_FACTOR
|
||||
* math.pi
|
||||
* PI_AS_WRITTEN
|
||||
* (diameter / 2.0) ** 2
|
||||
* values["height_m"]
|
||||
* float(inputs["height_m"])
|
||||
* (1.0 + FOLIAGE_ALLOWANCE)
|
||||
* values["unit_weight_kg_m3"]
|
||||
* float(inputs["unit_weight_kg_m3"])
|
||||
* stems
|
||||
)
|
||||
return above_kg / 1000.0, above_kg * ROOT_SHARE / ABOVE_SHARE / 1000.0
|
||||
if root_method == ROOT_METHOD_BALL:
|
||||
ball = float(inputs["root_ball_diameter_m"])
|
||||
root_kg = ROOT_BALL_FACTOR * ball**3 * ROOT_BALL_UNIT_WEIGHT * stems
|
||||
else:
|
||||
root_kg = above_kg * ROOT_SHARE / ABOVE_SHARE
|
||||
return above_kg / 1000.0, root_kg / 1000.0
|
||||
|
||||
|
||||
def tree_waste_rows(
|
||||
@@ -78,11 +109,14 @@ def tree_waste_rows(
|
||||
inputs: dict[str, Any] | None,
|
||||
unit_price_krw_per_ton: Any = None,
|
||||
separate_order: Any = False,
|
||||
root_method: Any = None,
|
||||
) -> 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 {}
|
||||
method = ROOT_METHOD_BALL if root_method == ROOT_METHOD_BALL else ROOT_METHOD_RATIO
|
||||
root_basis = ROOT_BALL_BASIS if method == ROOT_METHOD_BALL else ROOT_RATIO_BASIS
|
||||
base = {
|
||||
"group": "준비공",
|
||||
"item": TREE_WASTE_ITEM,
|
||||
@@ -93,9 +127,9 @@ def tree_waste_rows(
|
||||
"bill_reason": EXPENSE_NOTE,
|
||||
"separate_order": bool(separate_order),
|
||||
}
|
||||
tons = tree_waste_tons(area, inputs)
|
||||
tons = tree_waste_tons(area, inputs, method)
|
||||
if tons is None:
|
||||
missing = [label for key, label in TREE_WASTE_INPUTS if _positive(inputs.get(key)) is None]
|
||||
missing = _missing(inputs, method)
|
||||
why = (
|
||||
f"현장 조사값이 비어 있음 — {' · '.join(missing)}"
|
||||
if missing
|
||||
@@ -106,23 +140,20 @@ def tree_waste_rows(
|
||||
**base,
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": f"{why}. 산식: {TREE_WASTE_BASIS}",
|
||||
"reason": f"{why}. 산식: {TREE_WASTE_BASIS} · {root_basis}",
|
||||
"reference_amount": area,
|
||||
}
|
||||
]
|
||||
above, root = tons
|
||||
total = above + root
|
||||
notes = [
|
||||
f"지상부 WA {above:,.2f}톤 + 뿌리부 WR {root:,.2f}톤(WA × 15/85)",
|
||||
f"지상부 WA {above:,.2f}톤 + 뿌리부 WR {root:,.2f}톤",
|
||||
TREE_WASTE_BASIS,
|
||||
root_basis,
|
||||
EXPENSE_NOTE,
|
||||
NOT_SEPARATE_NOTE,
|
||||
]
|
||||
if total >= SEPARATE_ORDER_THRESHOLD_TON:
|
||||
notes.append(
|
||||
"⚠ 100톤 이상 — 건설폐기물이면 분리발주 대상(업무처리지침). 다만 임목폐기물은"
|
||||
" 건설폐기물이 아닌 사업장 일반폐기물로 분류됨(같은 지침) — 산출 조건에서 고를 것"
|
||||
)
|
||||
elif total >= REPORT_THRESHOLD_TON:
|
||||
if total >= REPORT_THRESHOLD_TON:
|
||||
notes.append("5톤 이상 — 배출자 신고 대상 사업장(폐기물관리법 시행령 제2조8호)")
|
||||
price = _positive(unit_price_krw_per_ton)
|
||||
row = {
|
||||
|
||||
@@ -178,6 +178,7 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
||||
settings.get("tree_waste") or {},
|
||||
settings.get("tree_waste_unit_price_krw_per_ton"),
|
||||
settings.get("waste_separate_order"),
|
||||
settings.get("tree_waste_root_method"),
|
||||
)
|
||||
method, method_is_default = concrete_placing_method(settings)
|
||||
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
||||
@@ -464,6 +465,8 @@ class QuantitySettingsBody(BaseModel):
|
||||
tree_waste: dict[str, float | None] | None = None
|
||||
tree_waste_unit_price_krw_per_ton: float | None = None
|
||||
waste_separate_order: bool | None = None
|
||||
# 뿌리 산정법 — "root_ball"(뿌리분 체적 × 1,300) · `""` 는 기본(분배비 15/85)으로 되돌림.
|
||||
tree_waste_root_method: str | None = None
|
||||
# 토량환산계수(다짐) — `{갈래: {"compacted": C, "reason": 사유}}`.
|
||||
# ⚠ **기본값을 복사해 넣지 않는다** — 안 고른 갈래는 키가 없어야 정본이 선다.
|
||||
# 빈 dict 는 「전부 기본값으로 되돌림」이라 통째로 갈아 끼운다.
|
||||
@@ -531,6 +534,9 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
for key, value in (values["tree_waste"] or {}).items()
|
||||
if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0
|
||||
}
|
||||
if "tree_waste_root_method" in values:
|
||||
method = values["tree_waste_root_method"]
|
||||
values["tree_waste_root_method"] = method if method == "root_ball" else None
|
||||
if "topsoil_target" in values:
|
||||
# 기본(노면 + 절토)은 저장하지 않는다 — 「안 정함」과 같게 둬 법 문언이 선다.
|
||||
target = values["topsoil_target"]
|
||||
@@ -571,6 +577,7 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
"concrete_placing_method",
|
||||
"topsoil_target",
|
||||
"tree_waste",
|
||||
"tree_waste_root_method",
|
||||
"ancillary_counts",
|
||||
# 고른 계수를 **기본값으로 되돌릴 길**이 있어야 한다 — 병합이면 못 지운다.
|
||||
"conversion_factors_override",
|
||||
|
||||
@@ -114,6 +114,8 @@ export interface QuantitySettings {
|
||||
tree_waste_unit_price_krw_per_ton?: number | null;
|
||||
/** 폐기물 분리발주 — 기본 아님. */
|
||||
waste_separate_order?: boolean | null;
|
||||
/** 뿌리 산정법 — 없음이면 분배비 15/85 · `"root_ball"` 은 뿌리분 체적 × 1,300. */
|
||||
tree_waste_root_method?: string | null;
|
||||
}
|
||||
|
||||
/** 갈래 하나의 「무엇을 골랐나」. 서버 `earthwork_conversion_choices` 와 짝이다. */
|
||||
|
||||
@@ -114,6 +114,7 @@ async function saveQuantitySettings(projectId: string, draft: DraftSettings): Pr
|
||||
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,
|
||||
tree_waste_root_method: draft.tree_waste_root_method,
|
||||
// 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다.
|
||||
ancillary_counts: draft.ancillary_counts,
|
||||
// 토량환산계수 — 고른 갈래만 담긴다. 빈 dict 는 「전부 기본값으로 되돌림」이다.
|
||||
@@ -309,6 +310,7 @@ interface DraftSettings {
|
||||
tree_waste: Record<string, number | null>;
|
||||
tree_waste_unit_price_krw_per_ton: number | null;
|
||||
waste_separate_order: boolean;
|
||||
tree_waste_root_method: string;
|
||||
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
||||
material_supply: Record<string, SupplyChoice>;
|
||||
// 갈래별 토량환산계수(다짐) — `compacted` 가 `null` 이면 「안 고름」이라 기본값이 선다.
|
||||
@@ -1017,6 +1019,7 @@ export async function renderB08Quantity(root: HTMLElement): Promise<void> {
|
||||
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),
|
||||
tree_waste_root_method: (stored.tree_waste_root_method as string) ?? "",
|
||||
ancillary_counts: {
|
||||
...((stored.ancillary_counts ?? {}) as Record<string, number | null>),
|
||||
},
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface TreeWasteDraft {
|
||||
tree_waste: Record<string, number | null>;
|
||||
tree_waste_unit_price_krw_per_ton: number | null;
|
||||
waste_separate_order: boolean;
|
||||
/** 뿌리 산정법 — `""` 분배비 15/85(기본) · "root_ball" 뿌리분 체적 × 1,300. */
|
||||
tree_waste_root_method: string;
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
@@ -67,6 +69,34 @@ export function appendTreeWasteFields(
|
||||
}
|
||||
panel.append(h.hintRow(L("B08_Quantity_Side_TreeWaste_Hint")));
|
||||
|
||||
// 뿌리 — 지침 부록1 이 두 방법을 둠. 뿌리분 직경은 흉고직경과 갈라 따로 받음(지침 표기가 모호).
|
||||
panel.append(
|
||||
h.selectField(
|
||||
L("B08_Quantity_TreeWaste_RootMethod"),
|
||||
draft.tree_waste_root_method,
|
||||
[
|
||||
{ value: "", label: L("B08_Quantity_TreeWaste_RootRatio") },
|
||||
{ value: "root_ball", label: L("B08_Quantity_TreeWaste_RootBall") },
|
||||
],
|
||||
(value) => {
|
||||
draft.tree_waste_root_method = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
panel.append(
|
||||
h.optionalNumberField(
|
||||
L("B08_Quantity_TreeWaste_RootBallDiameter"),
|
||||
draft.tree_waste["root_ball_diameter_m"] ?? null,
|
||||
"0.01",
|
||||
(value) => {
|
||||
draft.tree_waste["root_ball_diameter_m"] = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
panel.append(h.hintRow(L("B08_Quantity_TreeWaste_Root_Hint")));
|
||||
|
||||
const price = h.optionalNumberField(
|
||||
L("B08_Quantity_TreeWaste_Price"),
|
||||
draft.tree_waste_unit_price_krw_per_ton,
|
||||
|
||||
@@ -163,6 +163,9 @@ def default_settings() -> dict[str, Any]:
|
||||
# 임목폐기물 — 현장 조사값 넷 `{stems_per_1000m2, dbh_cm, height_m, unit_weight_kg_m3}`.
|
||||
# ⚠ 기본값을 두지 않는다 — 비면 톤이 안 선다(수종·임분마다 달라 지어내지 않음).
|
||||
"tree_waste": {},
|
||||
# 뿌리 산정법 — `None` 은 분배비 15/85(기본) · `"root_ball"` 은 뿌리분 체적 × 1,300㎏/㎥
|
||||
# (환경친화적인 도로건설 지침 부록1). 뿌리분 직경은 `tree_waste.root_ball_diameter_m`.
|
||||
"tree_waste_root_method": None,
|
||||
# 처리단가(원/톤) — KCRA 중간처리단가 미확보라 **수동**. 넣으면 금액이 서되 미확정.
|
||||
"tree_waste_unit_price_krw_per_ton": None,
|
||||
# 폐기물 분리발주 — 기본 아님(경비로 원가에 듦 · 2026-09-14 판정).
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -28,8 +27,8 @@ FIELD = {"stems_per_1000m2": 134, "dbh_cm": 16, "height_m": 10.0, "unit_weight_k
|
||||
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)
|
||||
# 지침 부록1 식·실정보고 모두 π 를 3.14 로 적음 — 그대로 셈해 130.40 과 같다.
|
||||
assert above == pytest.approx(130.40, abs=0.01)
|
||||
assert root == pytest.approx(above * 15 / 85)
|
||||
|
||||
|
||||
@@ -48,9 +47,9 @@ def test_단가가_없으면_금액이_안_서고_있으면_미확정() -> None:
|
||||
assert priced["amount_krw"] == int(priced["amount"] * 60000)
|
||||
|
||||
|
||||
def test_톤_경계를_알린다() -> None:
|
||||
def test_톤_경계를_알리고_분리발주_대상_아님을_적는다() -> None:
|
||||
big = tree_waste_rows(SLOPE, FIELD)[0] # 약 153톤
|
||||
assert big["amount"] >= 100 and "100톤 이상" in big["reason"]
|
||||
assert "분리발주 대상 아님" in big["reason"] and "환경부" 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"]
|
||||
|
||||
@@ -64,3 +63,17 @@ def test_경비라_내역에_안_서고_제외_사유가_간다() -> None:
|
||||
row = handed["임목폐기물 처리"]
|
||||
assert row["in_bill"] is False and row["blocked_kind"] is None
|
||||
assert "경비" in row["in_bill_reason"]
|
||||
|
||||
|
||||
def test_뿌리분_체적법은_D_세제곱_곱하기_1300() -> None:
|
||||
"""지침 부록1 — V = 0.3927 D³ · 뿌리분 중량 = V × 1,300㎏/㎥ (본수만큼)."""
|
||||
field = {**FIELD, "root_ball_diameter_m": 0.5}
|
||||
above, root = tree_waste_tons(7450.0, field, "root_ball")
|
||||
stems = 134 / 1000 * 7450
|
||||
assert root == pytest.approx(0.3927 * 0.5**3 * 1300 * stems / 1000)
|
||||
assert above == pytest.approx(tree_waste_tons(7450.0, FIELD)[0])
|
||||
|
||||
|
||||
def test_체적법인데_뿌리분_직경이_비면_안_선다() -> None:
|
||||
row = tree_waste_rows(SLOPE, FIELD, root_method="root_ball")[0]
|
||||
assert row["amount"] is None and "뿌리분 직경" in row["reason"]
|
||||
|
||||
@@ -717,8 +717,22 @@ export const ui_locales_b2 = {
|
||||
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",
|
||||
"현장 조사값 넷을 넣으면 준비공 「임목폐기물 처리」가 톤으로 섭니다. 산식: 지상부 WA = 0.5 × 3.14 × (흉고직경/2)² × 수고 × (1+0.3) × 단위체적중량 × 본수 · 본수 = 1,000㎡당 본수 × 벌목·지장목제거 면적 — 환경친화적인 도로건설 지침 부록1 · 한국건설기술연구원(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 × 3.14 × (DBH/2)² × height × 1.3 × unit weight × stems (road guideline appendix 1, KICT 2012). No defaults — values differ by species and stand",
|
||||
],
|
||||
B08_Quantity_TreeWaste_RootMethod: ["뿌리부 산정법", "Root estimate"],
|
||||
B08_Quantity_TreeWaste_RootRatio: [
|
||||
"분배비 WA × 15/85 (기본)",
|
||||
"Share ratio WA × 15/85 (default)",
|
||||
],
|
||||
B08_Quantity_TreeWaste_RootBall: ["뿌리분 체적 × 1,300㎏/㎥", "Root-ball volume × 1,300 kg/㎥"],
|
||||
B08_Quantity_TreeWaste_RootBallDiameter: [
|
||||
"뿌리분 직경 D(m, 흉고직경 아님) — 체적법만",
|
||||
"Root-ball diameter D (m, not DBH) — volume method",
|
||||
],
|
||||
B08_Quantity_TreeWaste_Root_Hint: [
|
||||
"환경친화적인 도로건설 지침 부록1 이 두 방법을 둡니다. 분배비(기본): 뿌리 15 : 지상 85(일반 수목류, 한국건설기술연구원 2012). 뿌리분 체적: V = πD²(D/2 + RH2/3) = 0.3927D³ · 뿌리분 중량 = V × 1,300㎏/㎥ × 본수. ⚠ 지침은 D 를 「뿌리분의 직경(DBH, m)」으로 적어 흉고직경인지 뿌리분 직경인지 갈리지 않음 — 흉고직경으로 두지 않고 따로 받습니다. 참고: 실무 실정보고(한 현장)는 뿌리 14 : 86 · 0.65톤/㎥ 를 씀",
|
||||
"Road guideline appendix 1 gives two methods: share ratio 15:85 (default), or root-ball volume 0.3927·D³ × 1,300 kg/㎥ × stems. The guideline labels D ambiguously, so it is entered separately",
|
||||
],
|
||||
B08_Quantity_TreeWaste_Price: ["처리단가(원/톤) — 수동", "Disposal price (KRW/t) — manual"],
|
||||
B08_Quantity_TreeWaste_Price_Hint: [
|
||||
@@ -729,8 +743,8 @@ export const ui_locales_b2 = {
|
||||
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",
|
||||
"⚠ 임목폐기물은 분리발주 대상 아님(환경부 질의회신 — 임목폐기물처리 실정보고 p4 · 건설폐기물 업무처리지침: 건설폐기물 아닌 사업장 일반폐기물) — 기본 꺼짐으로 원가계산서 경비에 듭니다. 이 칸은 다른 건설폐기물(위탁처리 100톤 이상 분리발주 대상)이 생길 때 쓰는 칸 — 켜면 총원가 밖에 서고 총공사비에만 더합니다. 5톤 이상은 배출자 신고 대상(폐기물관리법 시행령 제2조8호)",
|
||||
"Tree waste is not subject to separate ordering (Ministry of Environment reply; classified as business general waste) — off by default, inside the expense category. Use this switch for other construction waste (100 t or more). When on, it sits outside total cost. 5 t or more requires a discharge report",
|
||||
],
|
||||
B08_Quantity_Side_SubgradeCompaction: ["노체다짐", "Subgrade Compaction"],
|
||||
B08_Quantity_SubgradeCompaction_Label: ["노체다짐을 셀 것인가", "Count subgrade compaction"],
|
||||
|
||||
Reference in New Issue
Block a user