Files
Aislo/resources/tester/test_b08_topsoil_haul.py
T
eomsangdonandClaude Opus 5 364c28a700 fix(b08): 준비공 상태 칸 「입력이 필요함」과 「값을 낼 근거가 없음」을 가름 · 인계 막힘 갈래도 상태를 따름(㉴)
- 입력이면 서는 줄(표토 두께·거리 · 임목폐기물 조사값 · 부대시설 개소 · 임목파쇄 부피 · 앞 단계 횡단·사면표) → 「입력이 필요함」 · 인계 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
2026-09-15 00:44:33 +09:00

165 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""표토 운반·적치 — 법이 요구하는데 제거만 세고 있던 줄 (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"