- 측구 토사·암을 절토 면적비로 다시 안분하지 않고 B06 이 낸 ditch_soil_area_m2·ditch_rock_area_m2 를 그대로 씀. 근거 ditch_split_basis 도 줄에 실음. - 설계값이 없는 저장분만 옛 안분으로 떨어지고 사유를 줄 주기에 남김. - 표기 자리수 — 단면적 ㎡ 1자리 · 체적 계열 ㎥ 2자리 · 합계 정수 (계산은 전정밀 그대로). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GrXDD23Dvt2sR7q3X6oekp
246 lines
9.4 KiB
Python
246 lines
9.4 KiB
Python
"""토적표 엔진 검사 — PLAN 8-4b·8-16.
|
||
|
||
정답지는 거창 실무 토적표(오솔길 `1.BOM` 과 1:1)다. 평균단면적법이 실물과 같은 값을
|
||
내는지, 첫 측점에 체적이 없는지, 값을 자르지 않는지를 못 박는다.
|
||
"""
|
||
|
||
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 B08_Quantity.B08_Quantity_Engine_EarthworkTable import ( # noqa: E402
|
||
StationArea,
|
||
build_rows,
|
||
build_table,
|
||
totals,
|
||
)
|
||
from config.config_system_design import EARTHWORK_CONVERSION_FACTORS # noqa: E402
|
||
|
||
SOIL_C = EARTHWORK_CONVERSION_FACTORS["soil"]["compacted"]
|
||
|
||
|
||
def 거창_앞_네_측점() -> list[StationArea]:
|
||
"""거창 `1.BOM` 앞부분 — 절토 토사만 있고 측구는 단면 0.18㎡ 고정인 구간.
|
||
|
||
BOM 값: 0(0.00) · 0+10(1.89) · 1(1.73) · 1+10(1.45), 거리 10m 등간.
|
||
"""
|
||
return [
|
||
StationArea(chainage_m=0.0, cut_soil_area_m2=0.00, ditch_area_m2=0.0),
|
||
StationArea(chainage_m=10.0, cut_soil_area_m2=1.89, ditch_area_m2=0.18),
|
||
StationArea(chainage_m=20.0, cut_soil_area_m2=1.73, ditch_area_m2=0.18),
|
||
StationArea(chainage_m=30.0, cut_soil_area_m2=1.45, ditch_area_m2=0.18),
|
||
]
|
||
|
||
|
||
def test_첫_측점은_체적이_없음() -> None:
|
||
"""앞 측점이 없으면 평균을 낼 수 없다 — 실무 토적표도 첫 행 체적이 비어 있다."""
|
||
rows = build_rows(거창_앞_네_측점())
|
||
assert rows[0].distance_m == 0.0
|
||
assert rows[0].cut_soil_volume_m3 == 0.0
|
||
assert rows[0].adjusted_total_m3 == 0.0
|
||
|
||
|
||
def test_평균단면적법_실무값_재현() -> None:
|
||
"""BOM 0+10 행: 단면적 1.89 → 체적 9.45 → 보정 8.50 (거리 10m)."""
|
||
rows = build_rows(거창_앞_네_측점())
|
||
row = rows[1]
|
||
assert row.distance_m == pytest.approx(10.0)
|
||
assert row.cut_soil_volume_m3 == pytest.approx(9.45) # (0.00+1.89)/2 × 10
|
||
assert row.cut_soil_adjusted_m3 == pytest.approx(9.45 * SOIL_C)
|
||
assert row.cut_soil_adjusted_m3 == pytest.approx(8.505) # BOM 표기 8.50
|
||
|
||
|
||
def test_평균단면적법_다음_측점() -> None:
|
||
"""BOM 1 행: (1.89+1.73)/2 × 10 = 18.10 → 보정 16.29."""
|
||
row = build_rows(거창_앞_네_측점())[2]
|
||
assert row.cut_soil_volume_m3 == pytest.approx(18.10)
|
||
assert row.cut_soil_adjusted_m3 == pytest.approx(16.29)
|
||
|
||
|
||
def test_측구_단면_0_18이_체적으로() -> None:
|
||
"""측구 0.18㎡ 가 이어지면 10m 당 1.80㎥ — BOM 10열과 같다."""
|
||
rows = build_rows(거창_앞_네_측점())
|
||
assert rows[2].ditch_soil_volume_m3 == pytest.approx(1.80)
|
||
assert rows[2].ditch_soil_adjusted_m3 == pytest.approx(1.62)
|
||
|
||
|
||
def test_보정량계는_네_갈래_합() -> None:
|
||
row = build_rows(거창_앞_네_측점())[2]
|
||
assert row.adjusted_total_m3 == pytest.approx(
|
||
row.cut_soil_adjusted_m3
|
||
+ row.cut_rock_adjusted_m3
|
||
+ row.ditch_soil_adjusted_m3
|
||
+ row.ditch_rock_adjusted_m3
|
||
)
|
||
assert row.adjusted_total_m3 == pytest.approx(16.29 + 1.62) # BOM 15열 17.91
|
||
|
||
|
||
def test_누가토량은_차인토량의_누계() -> None:
|
||
"""유토곡선의 원본이 이 열이다 — 누계가 어긋나면 곡선이 통째로 틀린다."""
|
||
rows = build_rows(거창_앞_네_측점())
|
||
running = 0.0
|
||
for row in rows:
|
||
running += row.balance_m3
|
||
assert row.cumulative_m3 == pytest.approx(running)
|
||
|
||
|
||
def test_성토가_있으면_유용토는_작은_쪽() -> None:
|
||
stations = [
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(chainage_m=10.0, cut_soil_area_m2=2.0, fill_area_m2=8.0),
|
||
]
|
||
row = build_rows(stations)[1]
|
||
assert row.adjusted_total_m3 == pytest.approx(10.0 * SOIL_C) # 절취 10㎥ → 보정 9㎥
|
||
assert row.fill_volume_m3 == pytest.approx(40.0)
|
||
assert row.diverted_m3 == pytest.approx(9.0) # 둘 중 작은 쪽
|
||
assert row.balance_m3 == pytest.approx(9.0 - 40.0)
|
||
|
||
|
||
def test_암은_토사와_다른_계수() -> None:
|
||
"""토사 0.90 · 리핑암 1.15 — 한 계수로 뭉치면 암 물량이 틀린다."""
|
||
stations = [
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(chainage_m=10.0, cut_rock_area_m2=2.0, cut_rock_kind="ripping_rock"),
|
||
]
|
||
row = build_rows(stations)[1]
|
||
assert row.cut_rock_volume_m3 == pytest.approx(10.0)
|
||
assert row.cut_rock_adjusted_m3 == pytest.approx(
|
||
10.0 * EARTHWORK_CONVERSION_FACTORS["ripping_rock"]["compacted"]
|
||
)
|
||
|
||
|
||
def test_설계가_가른_값을_그대로_읽음() -> None:
|
||
"""측구 가름의 주인은 횡단 설계다 — 토적표가 절토 비율로 다시 나누면 안 된다.
|
||
|
||
절토 비율(3:1)로 안분하면 0.30/0.10 이 되지만, 설계는 암반 경계선으로 0.05/0.35 를
|
||
냈다. 그 값이 그대로 서야 횡단도와 토적표의 숫자가 같아진다.
|
||
"""
|
||
stations = [
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(
|
||
chainage_m=10.0,
|
||
cut_soil_area_m2=3.0,
|
||
cut_rock_area_m2=1.0,
|
||
ditch_area_m2=0.4,
|
||
ditch_soil_area_m2=0.05,
|
||
ditch_rock_area_m2=0.35,
|
||
ditch_split_basis="rock_boundary",
|
||
cut_rock_kind="ripping_rock",
|
||
),
|
||
]
|
||
row = build_rows(stations)[1]
|
||
assert row.ditch_soil_area_m2 == pytest.approx(0.05)
|
||
assert row.ditch_rock_area_m2 == pytest.approx(0.35)
|
||
assert row.ditch_split_basis == "rock_boundary"
|
||
assert row.notes == [] # 설계값을 읽었으므로 폴백 사유가 없다.
|
||
|
||
|
||
def test_못_가른_측점은_사유와_함께_전량_암() -> None:
|
||
"""암 지반인데 암반 경계선이 없으면 설계가 전량 암으로 낸다 — 여기서 손보지 않는다."""
|
||
stations = [
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(
|
||
chainage_m=10.0,
|
||
cut_soil_area_m2=2.0,
|
||
cut_rock_area_m2=2.0,
|
||
ditch_area_m2=0.18,
|
||
ditch_soil_area_m2=0.0,
|
||
ditch_rock_area_m2=0.18,
|
||
ditch_split_basis="rock_ground_no_boundary",
|
||
cut_rock_kind="blasting_rock",
|
||
),
|
||
]
|
||
row = build_rows(stations)[1]
|
||
assert row.ditch_soil_area_m2 == 0.0 # 절반씩 임의로 나누지 않는다.
|
||
assert row.ditch_rock_area_m2 == pytest.approx(0.18)
|
||
assert row.ditch_split_basis == "rock_ground_no_boundary"
|
||
|
||
|
||
def test_설계값이_없을_때만_절토_비율로_안분() -> None:
|
||
"""가름이 붙기 전 저장분 — 폴백으로 안분하되 사유를 남겨 설계값과 구분한다."""
|
||
stations = [
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(
|
||
chainage_m=10.0,
|
||
cut_soil_area_m2=3.0,
|
||
cut_rock_area_m2=1.0,
|
||
ditch_area_m2=0.4,
|
||
cut_rock_kind="ripping_rock",
|
||
),
|
||
]
|
||
row = build_rows(stations)[1]
|
||
assert row.ditch_soil_area_m2 == pytest.approx(0.3)
|
||
assert row.ditch_rock_area_m2 == pytest.approx(0.1)
|
||
assert row.ditch_split_basis == "cut_area_ratio_fallback"
|
||
assert row.notes and "안분" in row.notes[0]
|
||
|
||
|
||
def test_값을_자르지_않을것() -> None:
|
||
"""품셈 1-2-2 는 표기 규칙이다 — 엔진은 전정밀로 둔다(PLAN 8-16)."""
|
||
stations = [StationArea(chainage_m=0.0), StationArea(chainage_m=7.0, cut_soil_area_m2=1.0)]
|
||
row = build_rows(stations)[1]
|
||
assert row.cut_soil_volume_m3 == pytest.approx(3.5)
|
||
assert row.cut_soil_adjusted_m3 == pytest.approx(3.15)
|
||
assert repr(row.cut_soil_adjusted_m3) != "3.2"
|
||
|
||
|
||
def test_합계행() -> None:
|
||
rows = build_rows(거창_앞_네_측점())
|
||
total = totals(rows)
|
||
assert total["distance_m"] == pytest.approx(30.0)
|
||
assert total["cut_soil_volume_m3"] == pytest.approx(sum(r.cut_soil_volume_m3 for r in rows))
|
||
|
||
|
||
def test_표_모양() -> None:
|
||
table = build_table(거창_앞_네_측점())
|
||
assert table["method"] == "average_end_area"
|
||
assert table["station_count"] == 4
|
||
assert table["conversion_factors"] is EARTHWORK_CONVERSION_FACTORS
|
||
assert len(table["rows"]) == 4
|
||
assert "cut_soil_adjusted_m3" in table["rows"][1]
|
||
|
||
|
||
def test_설계결과에서_담기() -> None:
|
||
"""B06 설계 결과의 키 이름을 그대로 받는다 — 이름이 어긋나면 0 이 조용히 들어간다."""
|
||
design = {
|
||
"cut_soil_area_m2": 1.5,
|
||
"cut_rock_area_m2": 0.5,
|
||
"fill_area_m2": 2.0,
|
||
"ditch_area_m2": 0.18,
|
||
"ditch_soil_area_m2": 0.12,
|
||
"ditch_rock_area_m2": 0.06,
|
||
"ditch_split_basis": "rock_boundary",
|
||
"cut_rock_kind": "blasting_rock",
|
||
}
|
||
area = StationArea.from_design(20.0, design)
|
||
assert area.chainage_m == 20.0
|
||
assert area.cut_soil_area_m2 == 1.5
|
||
assert area.cut_rock_kind == "blasting_rock"
|
||
assert area.ditch_soil_area_m2 == 0.12
|
||
assert area.ditch_rock_area_m2 == 0.06
|
||
assert area.ditch_split_basis == "rock_boundary"
|
||
|
||
|
||
def test_가름이_없는_저장분은_None으로_받음() -> None:
|
||
"""0.0 과 「설계가 안 냄」은 다르다 — None 이라야 폴백이 선다."""
|
||
area = StationArea.from_design(20.0, {"cut_soil_area_m2": 1.5, "ditch_area_m2": 0.18})
|
||
assert area.ditch_soil_area_m2 is None
|
||
assert area.ditch_rock_area_m2 is None
|
||
assert area.ditch_split_basis is None
|
||
|
||
|
||
def test_측점_순서가_뒤죽박죽이어도_이정순으로() -> None:
|
||
stations = [
|
||
StationArea(chainage_m=20.0, cut_soil_area_m2=1.0),
|
||
StationArea(chainage_m=0.0),
|
||
StationArea(chainage_m=10.0, cut_soil_area_m2=2.0),
|
||
]
|
||
rows = build_rows(stations)
|
||
assert [r.chainage_m for r in rows] == [0.0, 10.0, 20.0]
|