- 315-1 fordChuteLine — 길이 맞춘 선의 첫 조각만 쓰던 흠(원지반 이음 조각 버림) · 이은 조각까지 한 선 · 길이 없음 · 짧은 길이 · 절토 쪽 그대로 - 315-2 overflowLine — 성토 쪽(노면 끝 밖 설계선이 내려감) = 성토 사면선을 노면 끝 너머 위로 연장해 월류 높이선과 만나는 자리에서 끝 · 절토 · 늘인 노면 끝(294-3) 그대로 · 같은 점선 양식(B06 카드 · B07 [상세] 같은 그림) - 시험 test_315_ford_chute_overflow 새로 · test_287 월류선 기대값 315 규칙 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
162 lines
6.4 KiB
Python
162 lines
6.4 KiB
Python
"""315 물넘이포장 횡단 — 도수로 · 돌붙임 길이(성토) · 월류 높이선 성토 쪽 연장(요구 211-1).
|
|
|
|
못박는 것
|
|
- 315-1 길이 없음 = 성토는 원지반까지(그대로) · 길이 > 성토 사면 = 사면 끝에서 원지반 따라 이어 그 길이
|
|
(이은 조각까지 한 선 · 옛 흠 = 첫 조각만 그림) · 길이 < 사면 = 사면 따라 그 길이 · 절토(늘인 노면 끝이
|
|
원지반) = 그대로(길이 없음 = 안 그림 · 길이 = 원지반 따라).
|
|
- 315-2 월류 높이선 — 절토 쪽 = 노면 끝 위 월류 높이(그대로) · 성토 쪽 = 성토 사면선을 노면 끝 너머 위 ·
|
|
안쪽으로 점선 연장해 월류 높이선과 만나는 자리에서 끝(같은 점선 양식 한 벌).
|
|
|
|
진짜 TS 는 `helper_315_ford_chute_overflow.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
if str(HERE) not in sys.path:
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
|
|
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
|
|
from test_b06_cross_design_mirror import _dip_ground # noqa: E402
|
|
|
|
KW = {
|
|
"ground_type": "soil",
|
|
"section_mode": "left_cut",
|
|
"ditch_side": "left",
|
|
"paved": True,
|
|
"tilted": True,
|
|
"tilt_pct": 5.0,
|
|
"standard": STANDARD_CROSS_SECTION,
|
|
}
|
|
DEPTH = 0.3
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
|
reason="프론트엔드 의존성 미설치",
|
|
)
|
|
|
|
|
|
def _fill_samples() -> list[dict]:
|
|
"""좌 = 절토(원지반이 노면 위) · 우(낮은 쪽) = 성토 — 원지반 100 + 0.35·o."""
|
|
return [
|
|
{"offset_m": o / 2, "elevation_m": round(100.0 + 0.35 * o / 2, 4), "valid": True}
|
|
for o in range(-40, 41)
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def ts(tmp_path_factory) -> dict:
|
|
source = tmp_path_factory.mktemp("s315") / "in.json"
|
|
payload = {
|
|
"fillSamples": _fill_samples(),
|
|
"fillDesign": compute_cross_design(_fill_samples(), 100.4, **KW),
|
|
"dipSamples": _dip_ground(),
|
|
"dipDesign": compute_cross_design(_dip_ground(), 100.4, **KW),
|
|
}
|
|
source.write_text(json.dumps(payload), encoding="utf-8")
|
|
proc = subprocess.run(
|
|
["node", str(HERE / "helper_315_ford_chute_overflow.cjs"), str(source)],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def _length(line: list[dict]) -> float:
|
|
return sum(
|
|
math.hypot(b["offset"] - a["offset"], b["elevation"] - a["elevation"])
|
|
for a, b in zip(line, line[1:])
|
|
)
|
|
|
|
|
|
def _on_ground(p: dict) -> bool:
|
|
return p["elevation"] == pytest.approx(100.0 + 0.35 * p["offset"], abs=1e-3)
|
|
|
|
|
|
def test_길이_없음_성토는_원지반까지(ts):
|
|
line = ts["fillNone"]
|
|
assert line[0]["offset"] == pytest.approx(-2.0) # 낮은 쪽(우) 노견 끝
|
|
assert _on_ground(line[-1]) and not _on_ground(line[0])
|
|
assert all(b["elevation"] < a["elevation"] for a, b in zip(line, line[1:]))
|
|
|
|
|
|
def test_성토보다_긴_길이는_원지반_따라_이어_그_길이(ts):
|
|
slope = _length(ts["fillNone"])
|
|
line = ts["fillLong"]
|
|
assert slope < 6
|
|
assert _length(line) == pytest.approx(6.0, abs=1e-6) # 옛 흠 = 사면 길이에서 멈춤
|
|
toe = ts["fillNone"][-1]
|
|
beyond = [p for p in line if p["offset"] < toe["offset"] - 1e-6]
|
|
assert beyond and all(_on_ground(p) for p in beyond) # 사면 끝 너머 = 원지반 위
|
|
assert line[: len(ts["fillNone"])] == ts["fillNone"] # 앞은 사면 그대로 · 한 선
|
|
|
|
|
|
def test_짧은_길이는_사면_따라_그_길이(ts):
|
|
line = ts["fillShort"]
|
|
assert _length(line) == pytest.approx(1.0, abs=1e-6)
|
|
assert not any(_on_ground(p) for p in line)
|
|
|
|
|
|
def test_절토_쪽_그대로(ts):
|
|
assert ts["dipNone"] == [] # 늘인 노면 끝이 원지반 · 길이 없음 = 안 그림
|
|
line = ts["dipLong"] # 길이 = 늘인 끝에서 원지반 따라 그 길이
|
|
assert line[0]["offset"] == pytest.approx(ts["dipEdges"]["right"]["offset_m"], abs=1e-6)
|
|
assert _length(line) == pytest.approx(2.0, abs=1e-6)
|
|
|
|
|
|
def test_그린_띠도_긴_길이(ts):
|
|
nums = [float(v) for pair in ts["bandPoints"].split() for v in pair.split(",")]
|
|
offsets = nums[0::2]
|
|
assert min(offsets) < ts["fillNone"][-1]["offset"] - 1 # 띠가 사면 끝 너머 원지반까지
|
|
|
|
|
|
def test_월류선_절토_쪽은_노면_끝_위(ts):
|
|
edges = ts["fillEdges"]
|
|
first = ts["fillOverflow"][0] # 좌 = 절토
|
|
assert first["offset"] == pytest.approx(edges["left"]["offset_m"])
|
|
assert first["elevation"] == pytest.approx(edges["left"]["elevation_m"] + DEPTH)
|
|
dip = ts["dipOverflow"] # 양측 절토 = 두 점 그대로
|
|
assert len(dip) == 2
|
|
assert dip[-1]["offset"] == pytest.approx(ts["dipEdges"]["right"]["offset_m"])
|
|
assert dip[-1]["elevation"] == pytest.approx(ts["dipEdges"]["right"]["elevation_m"] + DEPTH)
|
|
|
|
|
|
def test_월류선_성토_쪽은_성토선_점선_연장과_만남(ts):
|
|
edges = ts["fillEdges"]
|
|
left, right = edges["left"], edges["right"]
|
|
*_, meet, end = ts["fillOverflow"]
|
|
# 끝 = 우 노면 끝(성토 사면 시작) · 그 앞 교점 = 노면 안쪽
|
|
assert end == pytest.approx({"offset": right["offset_m"], "elevation": right["elevation_m"]})
|
|
assert right["offset_m"] < meet["offset"] < left["offset_m"]
|
|
# 교점은 월류 높이선 위
|
|
road = (left["elevation_m"] - right["elevation_m"]) / (left["offset_m"] - right["offset_m"])
|
|
over = right["elevation_m"] + DEPTH + road * (meet["offset"] - right["offset_m"])
|
|
assert meet["elevation"] == pytest.approx(over, abs=1e-9)
|
|
# 교점 → 노면 끝 = 성토 사면과 같은 물매(사면선의 연장)
|
|
slope = ts["fillNone"]
|
|
fill = (slope[0]["elevation"] - slope[1]["elevation"]) / (
|
|
slope[0]["offset"] - slope[1]["offset"]
|
|
)
|
|
ext = (meet["elevation"] - end["elevation"]) / (meet["offset"] - end["offset"])
|
|
assert ext == pytest.approx(fill, abs=1e-9)
|
|
# 그린 점선 = 같은 점(같은 양식 한 벌)
|
|
assert ts["deckPoints"].split()[-2:] == [
|
|
f"{meet['offset']},{meet['elevation']}",
|
|
f"{end['offset']},{end['elevation']}",
|
|
]
|