Files
Aislo/resources/tester/test_69_b08_earthwork_tabs.py
T
eomsangdonandClaude Opus 5.5 2649ae7d91 fix(B08): 토적표 머리 글자 사용자 견본대로(71)
- 암석 → 연암 · 측구터파기 → 측구파기 · 측구 암 칸 → 암 · 암 칸 머리 풍선 「리핑암 · 발파암 합」 · 값 · 계산 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
2026-10-06 23:50:10 +09:00

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