"""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