Files
Aislo/resources/tester/test_b08_earthwork_table.py
T
eomsangdonandClaude Opus 5 1bfe1aa6e7 fix(B08): 토적표가 설계 측구 가름값을 그대로 읽도록 하고 표기 자리수를 품셈에 맞춤
- 측구 토사·암을 절토 면적비로 다시 안분하지 않고 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
2026-09-12 13:14:13 +09:00

246 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""토적표 엔진 검사 — 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]