- 겹친 사면(초류종자살포·면고르기)은 자동으로 안 빼고 맨 앞 사유 · 평떼 떼꽂이 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
116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
"""비탈면 녹화 ㎡ 넷 — 새심기·평떼·줄떼·비탈덮기 (2026-09-15 브레인 12장 D 녹화 ⓐ~ⓓ).
|
||
|
||
근거
|
||
산림품셈 5-14 새심기(㎡당) · 5-22 평떼(떼 0.3×0.3 11매/㎡ · 붙임 ㎡ · 떼꽂이 ㎡) ·
|
||
5-23 줄떼(떼 3.33매/㎡ · 붙임 ㎡ · 떼꽂이 ㎡) · 5-25 거적덮기(㎡당) · 5-28 비탈덮기(짚망 · 매트)
|
||
판정 — 면적 = 구간 안 사면 면적(칸: 절토면/성토면/양쪽 · 기본값 없음) · 초류종자 살포는 공법에서 뺌
|
||
(토공집계가 5-24 로 사면 전체를 셈) · 단 직고·선떼 급수 칸은 기본값 없음 · 길이형은 다음 차례.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revegetation import (
|
||
revegetation_from_designs,
|
||
)
|
||
|
||
AREAS = {"b06_cut_area_m2": 40.0, "b06_fill_area_m2": 60.0}
|
||
|
||
|
||
def _row(**options) -> dict:
|
||
structure = {
|
||
"structure_id": "g1",
|
||
"type_id": "revegetation",
|
||
"start_m": 0.0,
|
||
"end_m": 20.0,
|
||
"options": {**AREAS, **options},
|
||
}
|
||
table = build_table([structure], {"revegetation": "비탈면 녹화"})
|
||
return table
|
||
|
||
|
||
def _parts(table: dict) -> dict:
|
||
row = next(
|
||
r for r in build_handoff(unit_quantity_table=table)["work_items"] if "녹화" in r["name"]
|
||
)
|
||
return {part["code"]: part for part in row["composite_parts"] or []}
|
||
|
||
|
||
def test_평떼_양쪽은_면적과_떼_11매() -> None:
|
||
table = _row(method="평떼", face="양쪽")
|
||
row = table["structures"][0]
|
||
got = {(c["name"], c["unit"]): c["amount"] for c in row["components"]}
|
||
assert (row["billing_unit"], row["billing_quantity"]) == ("㎡", pytest.approx(100.0))
|
||
assert got[("평떼", "㎡")] == pytest.approx(100.0)
|
||
assert got[("떼", "매")] == pytest.approx(1100.0)
|
||
text = " ".join(row["notes"])
|
||
assert "겹친 몫을 자동으로 빼지 않음" in text and row["notes"][0].startswith("⚠")
|
||
assert "5-12 품에 걸린 것으로 읽음 — 5-22-3 떼꽂이는 따로 셈" in text
|
||
parts = _parts(table)
|
||
assert parts["FP-05-22-02"]["quantity"] == pytest.approx(100.0)
|
||
assert parts["FP-05-22-03"]["quantity"] == pytest.approx(100.0)
|
||
|
||
|
||
def test_줄떼_절토면은_3_33매() -> None:
|
||
table = _row(method="줄떼", face="절토면")
|
||
got = {(c["name"], c["unit"]): c["amount"] for c in table["structures"][0]["components"]}
|
||
assert got[("줄떼", "㎡")] == pytest.approx(40.0)
|
||
assert got[("떼", "매")] == pytest.approx(40.0 * 3.33)
|
||
assert set(_parts(table)) == {"FP-05-23-02", "FP-05-23-03"}
|
||
|
||
|
||
def test_새심기_성토면() -> None:
|
||
table = _row(method="새심기", face="성토면")
|
||
assert _parts(table)["FP-05-14"]["quantity"] == pytest.approx(60.0)
|
||
|
||
|
||
def test_비탈덮기는_덮개를_골라야_선다() -> None:
|
||
table = _row(method="비탈덮기", face="양쪽", cover_kind="거적")
|
||
assert _parts(table)["FP-05-25"]["quantity"] == pytest.approx(100.0)
|
||
unset = _row(method="비탈덮기", face="양쪽")["structures"][0]
|
||
assert not unset["components"] and any("덮개" in n for n in unset["notes"])
|
||
|
||
|
||
def test_면을_안_고르면_사유() -> None:
|
||
row = _row(method="평떼")["structures"][0]
|
||
assert not row["components"] and any("절토면" in n and "성토면" in n for n in row["notes"])
|
||
|
||
|
||
def test_길이형은_떼_매수표를_기다린다() -> None:
|
||
row = _row(method="선떼붙이기", face="양쪽")["structures"][0]
|
||
assert not row["components"]
|
||
assert any("사방기술교본" in n for n in row["notes"])
|
||
|
||
|
||
def test_구간_안_사면_면적은_B06_측점에서_보간() -> None:
|
||
designs = [
|
||
{"chainage_m": 0.0, "design": {}},
|
||
{"chainage_m": 20.0, "design": {}},
|
||
]
|
||
from B08_Quantity.B08_Quantity_Engine_SlopeLength import StationSlope
|
||
|
||
slopes = [
|
||
StationSlope(chainage_m=0.0, cut_length_m=2.0, fill_length_m=4.0),
|
||
StationSlope(chainage_m=20.0, cut_length_m=4.0, fill_length_m=0.0),
|
||
]
|
||
structure = {"type_id": "revegetation", "start_m": 5.0, "end_m": 20.0, "options": {}}
|
||
filled = revegetation_from_designs(structure, designs, slopes)
|
||
# 절토 5→2.5, 20→4 : (2.5+4)/2×15 · 성토 5→3, 20→0 : (3+0)/2×15
|
||
assert filled["options"]["b06_cut_area_m2"] == pytest.approx(48.75)
|
||
assert filled["options"]["b06_fill_area_m2"] == pytest.approx(22.5)
|
||
|
||
|
||
def test_등록부_공법에서_초류종자_살포를_빼고_까닭을_보인다() -> None:
|
||
options = {o.key: o for o in structure_type_map()["revegetation"].options}
|
||
assert "초류종자 살포" not in options["method"].choices
|
||
assert "토공집계" in (options["method"].default_basis or "")
|
||
assert (
|
||
options["face"].choices == ["절토면", "성토면", "양쪽"] and options["face"].default is None
|
||
)
|
||
for key in ("step_height_m", "sod_grade"):
|
||
assert options[key].default is None and "아직 표에 안 쓰임" in options[key].not_in_table
|