tmp/ 가 창끼리 안 건너가는 것이 확정돼(랩탑이 시간 두고 두 번 확인) 시험·예외가 저절로 건너가도록 git 안으로 옮김. 사용자 확정. - 내용은 하나도 안 고침 — 자리만 옮김. tmp/tests 는 남겨 둠. - 같은 이름이 이미 있던 64 개는 랩탑 것을 그대로 두고 건너뜀. - helper_b05_*.js 둘은 랩탑이 .cjs 로 이미 올린 것과 **줄바꿈만 다른 같은 내용**이라 복사본을 도로 뺌(시험이 .cjs 를 부름). - resources/tester/ 에서 전체 1176 통과 · 29 건너뜀 · 실패 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
261 lines
10 KiB
Python
261 lines
10 KiB
Python
"""사면길이 유도·사면 4계열 면적 검사 — PLAN 8-4b·8-11.
|
|
|
|
사면길이는 저장분에서 **유도**하는 값이라 판정 규칙이 곧 정확성이다. 그래서
|
|
실측 설계선 모양을 그대로 넣어 사면과 원지반이 갈리는지를 못 박는다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
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_SlopeArea import ( # noqa: E402
|
|
SlopeRatios,
|
|
build_rows,
|
|
build_table,
|
|
totals,
|
|
unclosed_stations,
|
|
)
|
|
from B08_Quantity.B08_Quantity_Engine_SlopeLength import ( # noqa: E402
|
|
StationSlope,
|
|
station_slope,
|
|
)
|
|
|
|
|
|
def line(points: list[tuple[float, float]]) -> list[dict[str, float]]:
|
|
return [{"offset_m": o, "elevation_m": z} for o, z in points]
|
|
|
|
|
|
def 절토_설계선() -> dict:
|
|
"""측점 20.0 **실측 모양 그대로** — 노면·측구·2단 절토 사면·원지반 (우측).
|
|
|
|
실측 구간 기울기: 노면 n=33.3 · 측구 벽 n=1.0 · 절토(암) n=0.4 · 절토(토사) n=1.0 ·
|
|
원지반 n=1.63. 측구 바닥은 노면보다 **낮다**(깊이 0.3).
|
|
⚠ 측구 벽이 토사 절토비(1.0)와 같은 경사라, 이 모양이 곧 「측구를 사면으로 세지 않는가」
|
|
를 재는 시험이다.
|
|
"""
|
|
return {
|
|
# 실측 표고를 그대로 세운 것 — 노면(-2.00) 870.83 을 기준으로 바깥으로 쌓았다.
|
|
"design_line": line(
|
|
[
|
|
(-5.00, 873.73), # 원지반 (n=1.63)
|
|
(-4.50, 873.32), # 절토 토사 n=1.0
|
|
(-4.00, 872.82), # 절토 토사 n≈1.03
|
|
(-3.50, 872.33), # 절토 암 n=0.4
|
|
(-3.00, 871.08), # 절토 암 n=0.4
|
|
(-2.90, 870.83),
|
|
(-2.60, 870.53), # 측구 바깥 벽 n=1.0 — 사면이 아니다
|
|
(-2.30, 870.53), # 측구 바닥 (노면보다 0.3 낮다)
|
|
(-2.00, 870.83), # 노체 끝
|
|
(0.00, 870.89), # 노면
|
|
]
|
|
),
|
|
"road_edges": {"left": {"offset_m": 2.0}, "right": {"offset_m": -2.0}},
|
|
"ditch_enabled": True,
|
|
"ditch_side": "right",
|
|
"ditch": {"type": "standard", "top_width_m": 0.9, "bottom_width_m": 0.3, "depth_m": 0.3},
|
|
"cut_slope_ratio": 0.4,
|
|
"soil_cut_slope_ratio": 1.0,
|
|
"fill_slope_ratio": 1.2,
|
|
"two_stage_slope": True,
|
|
}
|
|
|
|
|
|
def 성토_설계선() -> dict:
|
|
"""성토 사면 n=1.2 가 이어지다 원지반(n=2.5)에서 끊긴다."""
|
|
return {
|
|
"design_line": line(
|
|
[
|
|
(2.0, 870.00), # 노체 끝
|
|
(4.4, 868.00), # 성토 사면 n=1.2
|
|
(6.8, 866.00),
|
|
(9.8, 864.80), # 원지반 n=2.5
|
|
]
|
|
),
|
|
"road_edges": {"left": {"offset_m": 2.0}, "right": {"offset_m": -2.0}},
|
|
"cut_slope_ratio": 0.4,
|
|
"soil_cut_slope_ratio": 1.0,
|
|
"fill_slope_ratio": 1.2,
|
|
}
|
|
|
|
|
|
# ── 사면길이 유도 ────────────────────────────────────────────────────
|
|
|
|
|
|
def test_절토_사면만_잡고_원지반은_끊음() -> None:
|
|
slope = station_slope(20.0, 절토_설계선())
|
|
# 암 두 조각(0.10/0.25 · 0.50/1.25) + 토사 두 조각(0.50/0.49 · 0.50/0.50).
|
|
# 원지반(-4.50~-5.00, n=1.22)은 경사비가 안 맞아 안 든다.
|
|
expected = (
|
|
math.hypot(0.10, 0.25)
|
|
+ math.hypot(0.50, 1.25)
|
|
+ math.hypot(0.50, 0.49)
|
|
+ math.hypot(0.50, 0.50)
|
|
)
|
|
assert slope.cut_length_m == pytest.approx(expected, rel=0.02)
|
|
|
|
|
|
def test_절토_2단이_암토사로_갈림() -> None:
|
|
"""8-11 의 암 5분류는 설계자 % 입력 몫 — 여기서는 2단 경계까지만 본다."""
|
|
slope = station_slope(20.0, 절토_설계선())
|
|
materials = {segment.material for segment in slope.segments if segment.role == "cut"}
|
|
assert materials == {"rock", "soil"}
|
|
|
|
|
|
def test_측구는_사면이_아님() -> None:
|
|
"""측구 벽도 n=1.0 이라 토사 절토비와 같다 — 노체 안쪽이므로 세면 안 된다."""
|
|
slope = station_slope(20.0, 절토_설계선())
|
|
for segment in slope.segments:
|
|
assert segment.from_offset_m <= -2.9 + 1e-6, f"측구 구간이 섞였다: {segment}"
|
|
|
|
|
|
def test_성토_사면도_유도됨() -> None:
|
|
"""원지반선 없이도 경사비 불일치로 끊긴다 — 이 길이 3번의 갈림길이었다."""
|
|
slope = station_slope(40.0, 성토_설계선())
|
|
assert slope.fill_length_m == pytest.approx(math.hypot(4.8, 4.0), rel=0.02)
|
|
assert slope.cut_length_m == 0.0
|
|
|
|
|
|
def 성토_실측_설계선() -> dict:
|
|
"""route 150 측점 20.0 **좌측 실측 좌표** — 성토 사면이 원지반을 만나 끊기는 자리.
|
|
|
|
2.00~6.95 가 n=1.2 로 일정하고 6.95 에서 n=2.0 으로 꺾인다. 그 꺾임이 곧 원지반이다.
|
|
⚠ 이 시험이 있는 까닭 — 「성토 사면적이 실무의 2.8배」라는 의심이 들어 손으로 따라간
|
|
자리다. 결과는 **엔진이 맞았고** 삼각형 근사(평지 가정)로 견준 쪽이 틀렸다.
|
|
지반이 기울어 있으면 같은 면적이라도 사면이 훨씬 길어진다.
|
|
"""
|
|
return {
|
|
"design_line": line(
|
|
[
|
|
(2.00, 870.323),
|
|
(2.50, 869.906),
|
|
(3.00, 869.490),
|
|
(3.50, 869.073),
|
|
(4.00, 868.656),
|
|
(4.50, 868.240),
|
|
(5.00, 867.823),
|
|
(5.50, 867.406),
|
|
(6.00, 866.990),
|
|
(6.50, 866.573),
|
|
(6.95, 866.202), # 여기까지가 사면 — 낙차 4.121m
|
|
(7.00, 866.177), # 원지반 n=2.0
|
|
(7.50, 865.921),
|
|
(8.00, 865.716),
|
|
]
|
|
),
|
|
"road_edges": {"left": {"offset_m": 2.0}, "right": {"offset_m": -2.0}},
|
|
"ditch_enabled": True,
|
|
"ditch_side": "right", # 좌측에는 측구가 없다
|
|
"ditch": {"top_width_m": 0.9},
|
|
"fill_slope_ratio": 1.2,
|
|
"cut_slope_ratio": 0.4,
|
|
"soil_cut_slope_ratio": 1.0,
|
|
}
|
|
|
|
|
|
def test_성토_사면이_원지반에서_정확히_끊김() -> None:
|
|
"""실측 좌표로 잰다 — 사면길이 6.44m(수평 4.95 · 낙차 4.121)."""
|
|
slope = station_slope(20.0, 성토_실측_설계선())
|
|
assert slope.fill_length_m == pytest.approx(math.hypot(4.95, 4.121), rel=0.005)
|
|
assert slope.fill_length_m == pytest.approx(6.44, abs=0.01)
|
|
# 원지반(7.00 바깥)은 한 조각도 안 든다.
|
|
assert max(s.to_offset_m for s in slope.segments) == pytest.approx(6.95)
|
|
|
|
|
|
def test_잘린_측점은_표시가_따라옴() -> None:
|
|
design = dict(성토_설계선(), slope_unclosed=True)
|
|
assert station_slope(40.0, design).unclosed is True
|
|
|
|
|
|
def test_설계선이_없으면_0() -> None:
|
|
assert station_slope(0.0, {}).cut_length_m == 0.0
|
|
|
|
|
|
# ── 사면 4계열 면적 ──────────────────────────────────────────────────
|
|
|
|
|
|
def 사면들() -> list[StationSlope]:
|
|
return [
|
|
StationSlope(chainage_m=0.0, cut_length_m=0.0, fill_length_m=0.0),
|
|
StationSlope(chainage_m=20.0, cut_length_m=4.0, fill_length_m=6.0),
|
|
StationSlope(chainage_m=40.0, cut_length_m=6.0, fill_length_m=2.0),
|
|
]
|
|
|
|
|
|
def test_면적은_평균단면적법() -> None:
|
|
"""토적표와 같은 식이다 — 체적 대신 면적이 나올 뿐 계산을 두 벌로 짜지 않는다."""
|
|
rows = build_rows(사면들())
|
|
assert rows[1].areas["face_dressing_cut"] == pytest.approx((0.0 + 4.0) / 2 * 20)
|
|
assert rows[2].areas["face_dressing_cut"] == pytest.approx((4.0 + 6.0) / 2 * 20)
|
|
assert rows[2].areas["face_dressing_fill"] == pytest.approx((6.0 + 2.0) / 2 * 20)
|
|
|
|
|
|
def test_첫_측점은_면적이_없음() -> None:
|
|
assert all(value == 0.0 for value in build_rows(사면들())[0].areas.values())
|
|
|
|
|
|
def test_층따기는_성토면만() -> None:
|
|
rows = build_rows(사면들())
|
|
assert "bench_cut_fill" in rows[1].areas
|
|
assert "bench_cut_cut" not in rows[1].areas
|
|
|
|
|
|
def test_법면보호공은_면고르기와_같은_값() -> None:
|
|
"""기본은 참조다 — 실무 시트가 그러했고 오솔길 산출에는 보호공이 비어 있었다."""
|
|
row = build_rows(사면들())[2]
|
|
assert row.areas["slope_protection_cut"] == pytest.approx(row.areas["face_dressing_cut"])
|
|
assert row.areas["slope_protection_fill"] == pytest.approx(row.areas["face_dressing_fill"])
|
|
|
|
|
|
def test_반영률_기본은_100퍼센트() -> None:
|
|
"""실무 관측 80/50/80 은 참고일 뿐 기본값이 아니다 — 법대로 방침(8-10 ★)."""
|
|
ratios = SlopeRatios()
|
|
assert (ratios.bench_cut, ratios.face_dressing, ratios.slope_protection, ratios.tree_removal) == (
|
|
1.0,
|
|
1.0,
|
|
1.0,
|
|
1.0,
|
|
)
|
|
|
|
|
|
def test_반영률을_주면_곱해짐() -> None:
|
|
plain = build_rows(사면들())[2].areas["face_dressing_cut"]
|
|
scaled = build_rows(사면들(), SlopeRatios(face_dressing=0.8))[2].areas["face_dressing_cut"]
|
|
assert scaled == pytest.approx(plain * 0.8)
|
|
|
|
|
|
def test_잘린_측점은_목록으로_드러남() -> None:
|
|
"""조용히 적게 내면 안 된다 — 화면이 이 목록을 그대로 보인다."""
|
|
slopes = 사면들()
|
|
slopes[1].unclosed = True
|
|
rows = build_rows(slopes)
|
|
assert unclosed_stations(rows) == [20.0]
|
|
|
|
|
|
def test_합계() -> None:
|
|
rows = build_rows(사면들())
|
|
total = totals(rows)
|
|
assert total["face_dressing_cut"] == pytest.approx(
|
|
sum(row.areas["face_dressing_cut"] for row in rows)
|
|
)
|
|
|
|
|
|
def test_표_모양() -> None:
|
|
table = build_table(사면들())
|
|
assert table["method"] == "average_end_area"
|
|
assert table["protection_source"] == "face_dressing"
|
|
assert table["station_count"] == 3
|
|
assert set(table["ratios"]) == {
|
|
"bench_cut",
|
|
"face_dressing",
|
|
"slope_protection",
|
|
"tree_removal",
|
|
}
|
|
assert len(table["rows"]) == 3
|