- 입력이면 서는 줄(표토 두께·거리 · 임목폐기물 조사값 · 부대시설 개소 · 임목파쇄 부피 · 앞 단계 횡단·사면표) → 「입력이 필요함」 · 인계 input_missing - 자료·산식이 없는 줄(뿌리 운반 부피 · 사방 원단위 · 사면표에 성토고 칸 없음) → 「근거 없음」 · 인계 unit_data_missing(종전 input_missing = 반대 방향 거짓 사유) - 사면표가 섰는데 비탈 규준틀 0 개소 → 「해당 없음」 - 품셈 공종이 없는 부대시설은 개소를 넣어도 인계 input_missing 그대로(별도 단가) - 표 머리 「입력이 필요한 줄 N · 근거 없는 줄 M」 · 700줄 — 규준틀 재료를 `_Preparation_FrameMaterial` 로 뗌(719 → 651) - 936be972: 15줄 중 입력 7 · 근거 없음 1(뿌리 운반) · 내역 「원단위가 없습니다(우리가 만들 것)」 · 금액 불변 155,207,971 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
165 lines
7.3 KiB
Python
165 lines
7.3 KiB
Python
"""표토 운반·적치 — 법이 요구하는데 제거만 세고 있던 줄 (2026-09-09 · 2026-09-13 판정 「실무대로」).
|
||
|
||
⚠⚠ 시행규칙 별표2 Ⅰ.2.차.(6)·Ⅰ.3.카.(6):
|
||
「노면·절토대상지에 있는 입목…과 그 뿌리, **표토는 전량 제거한 후** 강우 시 유실되거나
|
||
경관에 저해되지 않도록 **최고 홍수위보다 높은 장소로 운반하고 쌓아두어야 한다**」
|
||
⇒ 제거(9-15)만 세면 **운반이 빠진다.**
|
||
|
||
⚠ 겨누는 것
|
||
① 줄이 **선다** — 값이 없어도 목록에 남는다
|
||
② 부피는 **제거 면적(㎡) × 두께** — 제거는 ㎡ 로 서고 두께는 운반 부피에만 쓴다
|
||
③ 거리는 **설계 입력** · 제거 품(9-15-2)이 **20m 압토**를 품어 **넘는 몫만** 운반으로 셈
|
||
④ 제거가 안 서거나 두께가 비면 운반도 안 선다 — 0 으로 때우지 않는다
|
||
⑤ 법 문구가 **사유에 적힌다** — 왜 세는지 화면에서 보여야 한다
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_Preparation import ( # noqa: E402
|
||
STATUS_COUNTED_ELSEWHERE,
|
||
STATUS_NEEDS_INPUT,
|
||
STATUS_READY,
|
||
build_table,
|
||
preparation_rows,
|
||
)
|
||
|
||
사면 = {"face_dressing_fill": 600.0, "face_dressing_cut": 400.0}
|
||
노면 = 600.0 # 노면 600 + 절토 사면 400 = 제거 1,000㎡
|
||
|
||
|
||
def 줄(thickness: float | None, distance: float | None, road: float | None = 노면) -> dict:
|
||
rows = preparation_rows(사면, [], thickness, distance, None, road)
|
||
return {row["item"]: row for row in rows}
|
||
|
||
|
||
def test_줄이_선다() -> None:
|
||
assert "표토 운반·적치" in 줄(None, None)
|
||
|
||
|
||
def test_부피는_제거_면적_곱하기_두께_거리는_20m_넘는_몫() -> None:
|
||
rows = 줄(0.2, 300.0)
|
||
assert rows["표토제거"]["amount"] == 1000.0
|
||
운반 = rows["표토 운반·적치"]
|
||
assert 운반["amount"] == 1000.0 * 0.2 and 운반["status"] == STATUS_READY
|
||
assert 운반["work_item_code"] == "FP-10-12"
|
||
assert 운반["haul_distance_m"] == 280.0 # 300 − 압토 20
|
||
assert "20m 를 넘는 몫" in 운반["reason"]
|
||
|
||
|
||
def test_20m_이내면_제거_품에_들어_따로_안_셈() -> None:
|
||
운반 = 줄(0.2, 15.0)["표토 운반·적치"]
|
||
assert 운반["amount"] is None and 운반["status"] == STATUS_COUNTED_ELSEWHERE
|
||
assert 운반["reference_amount"] == 200.0
|
||
|
||
|
||
def test_거리를_안_넣으면_막고_사유() -> None:
|
||
"""⚠ 「최고 홍수위보다 높은 장소」는 현장이 정한다 — 품셈이 거리를 주지 않는다."""
|
||
row = 줄(0.2, None)["표토 운반·적치"]
|
||
# 거리만 넣으면 서는 줄 — 「근거 없음」이 아니라 「입력이 필요함」(2026-09-14 ㉴).
|
||
assert row["amount"] is None and row["status"] == STATUS_NEEDS_INPUT
|
||
assert "운반거리가 아직 입력되지 않았습니다" in row["reason"]
|
||
assert row["reference_amount"] == 200.0 # 값을 버리지 않는다
|
||
|
||
|
||
def test_두께가_비거나_제거가_안_서면_운반도_안_선다() -> None:
|
||
no_thickness = 줄(None, 300.0)["표토 운반·적치"]
|
||
assert no_thickness["amount"] is None and "T=0.2m" in no_thickness["reason"]
|
||
no_removal = 줄(0.2, 300.0, road=None)["표토 운반·적치"]
|
||
assert no_removal["amount"] is None and "제거 면적이 아직 안 서서" in no_removal["reason"]
|
||
|
||
|
||
def test_법_문구가_사유에_적힌다() -> None:
|
||
for thickness, distance in ((None, None), (0.2, None), (0.2, 300.0), (0.2, 10.0)):
|
||
reason = 줄(thickness, distance)["표토 운반·적치"]["reason"]
|
||
assert "별표2" in reason and "운반하고 쌓아두어야" in reason
|
||
|
||
|
||
# ── 뿌리 적재·운반 — 품셈이 네 단계로 둔 자리 (2026-09-09) ──────────────
|
||
|
||
|
||
def test_뿌리_뒤_단계_두_줄이_선다() -> None:
|
||
rows = 줄(0.2, 300.0)
|
||
assert "뿌리 적재" in rows and "뿌리 운반" in rows
|
||
|
||
|
||
def test_적재는_9_20_2_운반은_덤프() -> None:
|
||
"""⭐ 2026-09-09 확정 5차 4번 — 「뿌리 운반은 **덤프**」. 물량은 부피가 없어 안 선다."""
|
||
rows = 줄(0.2, 300.0)
|
||
assert rows["뿌리 적재"]["work_item_code"] == "FP-09-20-02"
|
||
assert rows["뿌리 운반"]["work_item_code"] == "FP-10-12"
|
||
assert rows["뿌리 운반"]["amount"] is None
|
||
assert "부피" in rows["뿌리 운반"]["reason"]
|
||
|
||
|
||
def test_적재는_면적으로_서고_운반은_못_선다() -> None:
|
||
사면_ = {"tree_removal_fill": 600.0, "tree_removal_cut": 400.0}
|
||
rows = {row["item"]: row for row in preparation_rows(사면_, [], 0.2, 300.0)}
|
||
assert rows["뿌리 적재"]["amount"] == 1000.0
|
||
assert rows["제근·뿌리다듬기"]["amount"] == 1000.0 # 같은 밑수로 함께 섬
|
||
assert rows["뿌리 운반"]["amount"] is None
|
||
assert rows["뿌리 운반"]["reference_amount"] == 1000.0
|
||
|
||
|
||
def test_운반이_갈래를_실어_보낸다() -> None:
|
||
"""⚠ `FP-10-12` 는 **부모**다 — 갈래(토사)를 안 보내면 B09 가 「후보 3건」에서 멈춘다."""
|
||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||
|
||
table = build_table(
|
||
slope_totals={"face_dressing_cut": 9000.0, "face_dressing_fill": 6726.9},
|
||
topsoil_thickness_m=0.2,
|
||
topsoil_haul_distance_m=500.0,
|
||
road_surface_area_m2=12000.0,
|
||
)
|
||
handed = {r["name"]: r for r in build_handoff(preparation_table=table)["work_items"]}
|
||
운반 = handed["표토 운반·적치"]
|
||
assert 운반["in_bill"] is True
|
||
assert (운반["variant_axis"], 운반["variant_value"]) == ("ground_class", "토사")
|
||
제거 = handed["표토제거"]
|
||
assert (제거["unit"], 제거["quantity"], 제거["in_bill"]) == ("㎡", 21000.0, True)
|
||
|
||
|
||
def test_노면_면적은_노체_폭의_평균단면적() -> None:
|
||
from types import SimpleNamespace as S
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_SlopeLength import _roadbed_width, road_surface_area
|
||
|
||
assert (
|
||
_roadbed_width({"road_edges": {"left": {"offset_m": 2.5}, "right": {"offset_m": -2.5}}})
|
||
== 5.0
|
||
)
|
||
assert (
|
||
road_surface_area(
|
||
[S(chainage_m=20.0, roadbed_width_m=6.0), S(chainage_m=0.0, roadbed_width_m=4.0)]
|
||
)
|
||
== 100.0
|
||
)
|
||
assert (
|
||
road_surface_area(
|
||
[S(chainage_m=0.0, roadbed_width_m=4.0), S(chainage_m=20.0, roadbed_width_m=None)]
|
||
)
|
||
is None
|
||
)
|
||
assert road_surface_area([]) is None
|
||
|
||
|
||
def test_노면만을_고르면_절토_사면을_안_더한다() -> None:
|
||
"""⭐ 2026-09-14 판정 — 기본은 법 문언(노면 + 절토) · 노면만은 영월 한 건이라 설계자가 고름."""
|
||
rows = {r["item"]: r for r in preparation_rows(사면, [], 0.2, 300.0, None, 노면, True)}
|
||
assert rows["표토제거"]["amount"] == 600.0 and "노면만" in rows["표토제거"]["reason"]
|
||
assert "법 문언" in rows["표토제거"]["reason"]
|
||
assert rows["표토 운반·적치"]["amount"] == 600.0 * 0.2 # 운반도 고른 대상 따라감
|
||
|
||
|
||
def test_대상_설정은_모르는_값이면_기본() -> None:
|
||
from common_util.common_util_project_settings import topsoil_target
|
||
|
||
assert topsoil_target({}) == "road_and_cut"
|
||
assert topsoil_target({"topsoil_target": "road_only"}) == "road_only"
|
||
assert topsoil_target({"topsoil_target": "cut_only"}) == "road_and_cut"
|