Files
Aislo/resources/tester/test_315_ford_chute_overflow.py
T
eomsangdonandClaude Opus 5.5 b7eb44cd5c fix(b06): 물넘이포장 도수로/돌붙임 길이 = 성토 사면보다 길면 원지반 이음 조각까지 한 선 · 월류 높이선 성토 쪽 = 성토선 점선 연장과 교점 (315)
- 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
2026-10-13 19:46:28 +09:00

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']}",
]