- 암석 → 연암 · 측구터파기 → 측구파기 · 측구 암 칸 → 암 · 암 칸 머리 풍선 「리핑암 · 발파암 합」 · 값 · 계산 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
214 lines
8.3 KiB
Python
214 lines
8.3 KiB
Python
"""B08 토공 탭 (PLAN 69-2 · 69-3) — 토적표 견본 칸 · 면 계열 · 운반 시트 · 토공집계표 식.
|
|
|
|
정답지 ① 사용자 견본 두 행(120 · 120+18) ② 거창 실무 워크북 토적표 131행(70) ·
|
|
③ 같은 워크북 토공집계표 · 평균운반거리 · 종방향무대운반거리 시트 식.
|
|
"""
|
|
|
|
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 B07_DesignDetail.B07_DesignDetail_Engine_Cross_Quantity import derived_cells # noqa: E402
|
|
from B08_Quantity.B08_Quantity_Engine_Earthwork import ( # noqa: E402
|
|
FACE_KEYS,
|
|
StationArea,
|
|
build_rows,
|
|
build_table,
|
|
)
|
|
from B08_Quantity.B08_Quantity_Engine_Earthwork_Summary import ( # noqa: E402
|
|
excel_round,
|
|
haul_lists,
|
|
summary_rows,
|
|
)
|
|
|
|
# 견본 보정량 36.2 / 41.1 · 55.2 / 48.0 에서 거꾸로 — 토사 0.88 · 연암 1.15
|
|
SAMPLE_FACTORS = {
|
|
"soil": {"compacted": 0.88},
|
|
"ripping_rock": {"compacted": 1.15},
|
|
"blasting_rock": {"compacted": 1.3},
|
|
}
|
|
SAMPLE_FACES = (
|
|
# grading_fill · grading_cut · tree_fill · tree_cut · grubbing · road
|
|
(4.4, 1.3, 4.7, 4.6, 1.3, 4.0),
|
|
(6.9, 0.4, 10.1, 4.6, 4.8, 5.7),
|
|
)
|
|
|
|
|
|
def _faces(values: tuple[float, ...]) -> dict[str, float | None]:
|
|
return dict(zip(FACE_KEYS, (*values, None)))
|
|
|
|
|
|
def 견본_두_측점() -> list[StationArea]:
|
|
return [
|
|
StationArea(
|
|
chainage_m=120.0,
|
|
cut_soil_area_m2=3.28,
|
|
cut_rock_area_m2=1.61,
|
|
ditch_soil_area_m2=0.0,
|
|
ditch_rock_area_m2=0.18,
|
|
fill_area_m2=1.9,
|
|
cut_rock_kind="ripping_rock",
|
|
faces=_faces(SAMPLE_FACES[0]),
|
|
),
|
|
StationArea(
|
|
chainage_m=138.0,
|
|
cut_soil_area_m2=1.29,
|
|
cut_rock_area_m2=3.72,
|
|
ditch_soil_area_m2=0.0,
|
|
ditch_rock_area_m2=0.0,
|
|
fill_area_m2=19.13,
|
|
cut_rock_kind="ripping_rock",
|
|
faces=_faces(SAMPLE_FACES[1]),
|
|
),
|
|
]
|
|
|
|
|
|
def test_견본_120_18_행() -> None:
|
|
first, row = build_rows(견본_두_측점(), SAMPLE_FACTORS)
|
|
assert row.distance_m == pytest.approx(18)
|
|
assert round(row.cut_soil_volume_m3, 1) == 41.1
|
|
assert round(row.cut_soil_adjusted_m3, 1) == 36.2
|
|
assert round(row.cut_rock_volume_m3, 1) == 48.0
|
|
assert round(row.cut_rock_adjusted_m3, 1) == 55.2
|
|
assert round(row.ditch_rock_volume_m3, 1) == 1.6
|
|
assert round(row.ditch_rock_adjusted_m3, 1) == 1.9
|
|
assert round(row.adjusted_total_m3, 1) == 91.4 # 절토 보정량만
|
|
assert round(row.fill_volume_m3, 1) == 189.3
|
|
assert round(row.diverted_m3, 1) == 91.4
|
|
assert round(row.balance_m3, 1) == -97.9
|
|
assert round(row.cumulative_m3, 1) == -97.9
|
|
areas = [round(row.faces[key]["area_m2"], 1) for key in FACE_KEYS[:6]]
|
|
assert areas == [101.7, 15.3, 133.2, 82.8, 54.9, 87.3]
|
|
|
|
|
|
def test_첫_측점은_면_거리만() -> None:
|
|
first = build_rows(견본_두_측점(), SAMPLE_FACTORS)[0]
|
|
assert [first.faces[key]["length_m"] for key in FACE_KEYS[:6]] == list(SAMPLE_FACES[0])
|
|
assert all(first.faces[key]["area_m2"] is None for key in FACE_KEYS)
|
|
|
|
|
|
def test_거창_토적표_131행() -> None:
|
|
"""워크북 130(+10) → 131(70) — 절토 · 측구 · 성토 · 지장목 성토 면적 같은 입력 다시 셈."""
|
|
stations = [
|
|
StationArea(
|
|
chainage_m=0.0,
|
|
cut_soil_area_m2=6.72,
|
|
ditch_soil_area_m2=0.18,
|
|
ditch_rock_area_m2=0.0,
|
|
fill_area_m2=0.19,
|
|
faces=_faces((0.1, 4.7, 2.4, 6.3, 0, 0)),
|
|
),
|
|
StationArea(
|
|
chainage_m=10.0,
|
|
cut_soil_area_m2=3.15,
|
|
ditch_soil_area_m2=0.18,
|
|
ditch_rock_area_m2=0.0,
|
|
fill_area_m2=15.02,
|
|
faces=_faces((8.0, 3.8, 11.3, 4.5, 0, 0)),
|
|
),
|
|
]
|
|
row = build_rows(stations)[1]
|
|
assert row.cut_soil_volume_m3 == pytest.approx(49.35) # E131
|
|
assert row.cut_soil_adjusted_m3 == pytest.approx(44.415) # F131 44.41
|
|
assert row.ditch_soil_volume_m3 == pytest.approx(1.8) # K131
|
|
assert row.fill_volume_m3 == pytest.approx(76.05) # R131
|
|
assert row.faces["tree_removal_fill"]["area_m2"] == pytest.approx(68.5) # AG131
|
|
# 워크북 면적은 거리 소수 둘째 자리로 셈 — 표기 거리로는 ±0.5 안
|
|
assert row.faces["grading_fill"]["area_m2"] == pytest.approx(40.7, abs=0.5) # Y131
|
|
assert row.faces["tree_removal_cut"]["area_m2"] == pytest.approx(54.4, abs=0.5) # AI131
|
|
|
|
|
|
def test_면_거리는_B07_횡단도_수량과_같음() -> None:
|
|
design = {
|
|
"design_line": [
|
|
{"offset_m": o, "elevation_m": z}
|
|
for o, z in ((-8, 102), (-2, 100), (2, 100), (6, 102.5))
|
|
],
|
|
"road_edges": {"left": {"offset_m": 2}, "right": {"offset_m": -2}},
|
|
"cut_slope_ratio": 0.8,
|
|
"fill_slope_ratio": 1.5,
|
|
"roadbed_width_m": 4.0,
|
|
}
|
|
area = StationArea.from_design(120.0, design)
|
|
cells = derived_cells(120.0, design)
|
|
for key in ("grading_fill", "grading_cut", "tree_removal_fill", "tree_removal_cut"):
|
|
assert area.faces[key] == cells.get(key)
|
|
assert area.faces["road_surface"] == 4.0
|
|
assert area.faces["grubbing"] is None # B07 에 값 없음
|
|
|
|
|
|
def test_엑셀_ROUND() -> None:
|
|
assert excel_round(2.5) == 3
|
|
assert excel_round(-97.5) == -98
|
|
assert excel_round(1.005, 2) == 1.01
|
|
|
|
|
|
HAUL_PLAN = {
|
|
"blocks": [
|
|
{
|
|
"bands": [
|
|
{"equipment": "free_haul", "volume_m3": 50, "haul_distance_m": 10},
|
|
{"equipment": "dozer", "volume_m3": 20, "haul_distance_m": 40},
|
|
{"equipment": "dump_truck", "volume_m3": 68, "haul_distance_m": 35},
|
|
]
|
|
},
|
|
{"bands": [{"equipment": "free_haul", "volume_m3": 32, "haul_distance_m": 13}]},
|
|
],
|
|
"transfers": [{"equipment": "dump_truck", "volume_m3": 2, "haul_distance_m": 61}],
|
|
}
|
|
|
|
|
|
def test_운반_시트() -> None:
|
|
haul = haul_lists(HAUL_PLAN)
|
|
free = haul["free_haul"]
|
|
assert [(r["q"], r["l"]) for r in free["rows"]] == [(50, 10), (32, 13)]
|
|
assert free["q"] == 82 and free["ql"] == 50 * 10 + 32 * 13
|
|
assert free["average_m"] == excel_round((500 + 416) / 82, 2)
|
|
dump = haul["dump_truck"]
|
|
assert dump["q"] == 70 and dump["ql"] == 68 * 35 + 2 * 61 # 덤프 띠 + 떨어진 구간 운반
|
|
assert haul["dozer"]["average_m"] == 40
|
|
assert haul_lists(None)["dump_truck"] == {"rows": [], "q": 0, "ql": 0, "average_m": None}
|
|
|
|
|
|
def test_토공집계표_식() -> None:
|
|
table = build_table(견본_두_측점(), SAMPLE_FACTORS)
|
|
t = table["totals"]
|
|
rows = summary_rows(t, haul_lists(HAUL_PLAN))
|
|
value = {(r["group"], r["kind"], r["spec"]): r["value"] for r in rows}
|
|
assert value[("흙깍기", "토사", "기계(굴삭기)")] == excel_round(t["cut_soil_volume_m3"])
|
|
assert value[("흙깍기", "연암", "굴삭기 + 브레카")] == 48
|
|
assert value[("흙깍기", "경암", "발파 + 굴삭기")] == 0
|
|
assert value[("측구 터파기", "연암", "굴삭기 + 브레카")] == 2
|
|
assert value[("보정량계", "", "")] == 91
|
|
assert value[("성토", "", "")] == 189
|
|
assert value[("종방향유용토", "", "백호우")] == 82
|
|
assert value[("토사운반", "", "BH+DT+BH")] == 70
|
|
assert value[("토사운반", "", "19 TON B/D")] == 20
|
|
assert value[("유용토 소계", "", "")] == pytest.approx(t["diverted_m3"] + 82)
|
|
assert value[("잔토처리", "", "백호우")] == -98
|
|
assert value[("성토면다짐", "", "")] == excel_round(101.7 * 0.8)
|
|
assert value[("초류종자살포", "", "씨드스프레이")] == excel_round(101.7 * 0.5 + 15.3)
|
|
tree = excel_round(133.2 * 0.5 + 82.8, 2)
|
|
assert value[("지장목제거", "", "")] == excel_round(tree * 0.8)
|
|
assert value[("표토제거", "", "")] is None
|
|
assert len(rows) == 33 # 시트 C5:C37
|
|
|
|
|
|
def test_토적표_머리_글자는_사용자_견본대로():
|
|
# 「[절토] <토사> · <연암> · 측구파기 <토사> · <암>」 — 값 · 계산은 그대로
|
|
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Earthwork.ts").read_text(encoding="utf-8")
|
|
for head in (
|
|
'["절토", "연암"]',
|
|
'["측구파기", "토사"]',
|
|
'["측구파기", "암"]',
|
|
"리핑암 · 발파암 합",
|
|
):
|
|
assert head in ui, head
|
|
assert "암석" not in ui and "측구터파기" not in ui
|