"""임목폐기물 처리 — 톤으로 서는가 · 경비라 내역에 안 서는가 (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 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) # 지침 부록1 식·실정보고 모두 π 를 3.14 로 적음 — 그대로 셈해 130.40 과 같다. assert above == 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 "분리발주 대상 아님" 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"] 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"] 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"]