Files
Aislo/resources/tester/test_b08_gravel_surfacing.py
T
eomsangdonandClaude Opus 5 0d4f46ecbe feat(b05·b08): 비탈면 녹화 ㎡ 넷(새심기·평떼·줄떼·비탈덮기) — 구간 사면 면적 × 대상 면 칸 · 떼 11매/3.33매(품셈 5-22-1·5-23-1) · 초류종자 살포는 공법에서 빼고 칸 밑 까닭 · 단 직고·선떼 급수 칸 기본값 없음(12장 D 녹화 · 브레인 ⓐ~ⓓ)
- 겹친 사면(초류종자살포·면고르기)은 자동으로 안 빼고 맨 앞 사유 · 평떼 떼꽂이 5-22-3 [주] 읽기 사유
- 혼합석 사유 정정: 토사 = B06 측점 토사 토글(구성비는 암 몫만) · 사전터파기 「산림품셈에 노면 사전 터파기 공종 없음 — 9-3-2 토사깍기(기계)로 이음」
- UnitQuantity 700줄 — 늦은 import 를 한 벌(_lazy)로

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-15 05:30:32 +09:00

134 lines
6.5 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-15 브레인 ⓑ).
근거
법령 별표2 Ⅰ.2.바.(2) — 「종단기울기가 8퍼센트를 초과하는 사질토양 또는 점토질 토양인 구간과
종단기울기가 8퍼센트 이하인 구간으로서 지반이 약하고 습한 구간에는 쇄석·자갈을 부설」
임도기술교본 3-2 — 「콘크리트 포장 구간 이외에는 혼합석 부설 다짐 후 0.10m 내외」(고르개)
임도기술교본 10-1 · 부록 7-2 — 포설 전 10㎝ 터파기 · 적용범위 「노면의 토사구간」
실무 소광 — 혼합석 C 0.85 · L 1.25 (원문 체적환산표에 혼합석 줄 없음 ·
역(礫) L 1.10~1.20 · C 1.05~1.10)
판정 — 토사 = 사질·점토 해당 · 암 = 비해당 · 폭 = 차도 표준 폭 + 확폭(노견 제외).
"""
from __future__ import annotations
import pytest
from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import SummaryInput, build_rows
from B08_Quantity.B08_Quantity_Engine_GravelSurfacing import gravel_material_rows, gravel_surfacing
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
def _design(ground: str = "soil", widening: float = 0.0, paved: bool = False) -> dict:
return {
"ground_type": ground,
"carriageway_standard_width_m": 3.0,
"widening_left_m": widening,
"paved": paved,
}
DESIGNS = [
{"chainage_m": 0.0, "design": _design()},
{"chainage_m": 20.0, "design": _design()},
{"chainage_m": 40.0, "design": _design(widening=1.0)},
{"chainage_m": 60.0, "design": _design(paved=True)},
{"chainage_m": 80.0, "design": _design(ground="ripping_rock")},
]
GRADES = {0.0: 10.0, 20.0: 10.0, 40.0: 5.0, 60.0: 12.0, 80.0: 12.0}
def test_법령_조건만_자동_토사_8퍼센트_초과() -> None:
result = gravel_surfacing(DESIGNS, GRADES, {})
# 0·20 만 해당 — [0,20] (3+3)/2×20 + [20,40] (3+0)/2×20
assert result["area_m2"] == pytest.approx(90.0)
assert result["thickness_m"] == pytest.approx(0.10)
assert result["loose_m3"] == pytest.approx(90.0 * 0.10 * 1.25 / 0.85)
assert result["excavation_soil_m3"] == pytest.approx(9.0)
text = " ".join(result["notes"])
assert "별표2" in text and "토사 전부를 해당으로 봄" in text
# 토사/암은 B06 토사 토글이 정본 — 리핑암은 「암」의 자리표시(브레인 2026-09-15 ① 정정).
assert "토사 = B06 측점 토사 토글(구성비는 암 몫만 가름)" in text
assert "원문 표에 혼합석 줄 없음" in text and "방향이 반대" in text
assert "다짐" in text
def test_연약_습윤_구간_칸이_8퍼센트_이하_측점을_더한다() -> None:
result = gravel_surfacing(
DESIGNS, GRADES, {"gravel_soft_wet_ranges": [{"from_m": 30, "to_m": 50}]}
)
# 40 추가(경사 5 · 확폭 1) — [20,40] (3+4)/2×20 + [40,60] (4+0)/2×20
assert result["area_m2"] == pytest.approx(60.0 + 70.0 + 40.0)
def test_비포장_전_구간_고르개는_포장만_뺀다_암은_사전터파기_안_셈() -> None:
result = gravel_surfacing(DESIGNS, GRADES, {"gravel_all_unpaved": True})
assert result["area_m2"] == pytest.approx(60.0 + 70.0 + 40.0 + 30.0)
# 암 측점(80)은 교본 부록 7-2 「토사구간」 밖 — 사전 터파기 밑수에서 빠짐
assert result["excavation_soil_m3"] == pytest.approx(170.0 * 0.10)
assert any("교본 3-2" in note for note in result["notes"])
assert any("토사구간" in note for note in result["notes"])
assert any(
"산림품셈에 노면 사전 터파기 공종 없음 — 9-3-2 토사깍기(기계)로 이음" in note
for note in result["notes"]
)
def test_칸이_두께_C_L_을_덮어쓴다() -> None:
settings = {"gravel_thickness_m": 0.2, "gravel_conversion_c": 1.0, "gravel_conversion_l": 1.2}
result = gravel_surfacing(DESIGNS, GRADES, settings)
assert result["loose_m3"] == pytest.approx(90.0 * 0.2 * 1.2 / 1.0)
def test_토공집계_두_줄과_인계_자재() -> None:
result = gravel_surfacing(DESIGNS, GRADES, {})
rows = {row.group: row for row in build_rows(SummaryInput(gravel=result))}
assert rows["혼합석부설"].amount == pytest.approx(result["loose_m3"])
assert "토사" in rows["혼합석 사전터파기"].spec
assert rows["혼합석 사전터파기"].amount == pytest.approx(9.0)
materials = gravel_material_rows(result)
assert materials == [
{
"name": "혼합석",
"spec": "",
"unit": "㎥",
"amount": pytest.approx(result["loose_m3"]),
"destination": "material",
"source": "혼합석 부설",
}
]
def test_산출_조건_저장이_구간을_거르고_자재총괄에_혼합석이_선다() -> None:
from B08_Quantity.B08_Quantity_Router_Earthwork_Settings import (
NULLABLE_SETTING_KEYS,
clean_setting_values,
)
from B08_Quantity.B08_Quantity_Router_Material import material_table_for
values = {"gravel_soft_wet_ranges": [{"from_m": 50, "to_m": 30}, {"from_m": 1}, "x"]}
assert clean_setting_values(values) is None
assert values["gravel_soft_wet_ranges"] == [{"from_m": 30, "to_m": 50}]
assert {"gravel_thickness_m", "gravel_conversion_c", "gravel_conversion_l"} <= set(
NULLABLE_SETTING_KEYS
)
gravel = gravel_surfacing(DESIGNS, GRADES, {})
rows = material_table_for({"rows": []}, {}, None, gravel)["rows"]
assert [row for row in rows if row["name"] == "혼합석"]
def test_인계_코드() -> None:
from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import build_table
result = gravel_surfacing(DESIGNS, GRADES, {})
items = build_handoff(summary_table=build_table(SummaryInput(gravel=result)))["work_items"]
codes = {row["work_item_code"]: row for row in items if "혼합석" in str(row["name"])}
assert codes["FP-11-04"]["quantity"] == pytest.approx(result["loose_m3"])
assert codes["FP-09-03-02"]["quantity"] == pytest.approx(9.0)
assert codes["FP-11-04"]["in_bill"] and codes["FP-09-03-02"]["in_bill"]
# 해당 측점이 없으면(모두 암) 0 원 줄을 안 세움 — 936be972 실측에서 잡음.
rock = [{**d, "design": {**d["design"], "ground_type": "ripping_rock"}} for d in DESIGNS]
empty = gravel_surfacing(rock, GRADES, {})
zero = build_handoff(summary_table=build_table(SummaryInput(gravel=empty)))["work_items"]
assert not [r for r in zero if "혼합석" in str(r["name"]) and r["in_bill"]]