238 lines
9.2 KiB
Python
238 lines
9.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""475-3(요구 275-3) — 노선이 바뀌면 자리 칸은 같은 자리 측점만 들고 감.
|
|
|
|
자리 칸 = 암 경계 · 2단 경사 · 손 상단측(TS `SEAT_KEYS` 하나 · 파이썬은 그 글을 읽음).
|
|
|
|
못박는 것
|
|
- 자리 판정 거울 — TS `sameSeat` ↔ 파이썬 `same_seat`(중심 0.01 m · 왼쪽 축 1e-3 ·
|
|
모르면 같은 자리).
|
|
- 브라우저 · Node 넘김(`carryKeyedStates`) — 같은 자리 = 자리 칸도 · 옮긴 자리 = 자리 칸
|
|
버림(새 자리 기본값) · 지반 · 측구 종류 · 사면 쉬프트는 그대로 · 값의 쓴 때 자리(`@seat` ·
|
|
되돌리기로 되살린 값)가 새 자리와 같으면 유지 · 자리 모르는 옛 목록 = 옛 동작.
|
|
- 미리보기 입력(`crossAtSeat`) — 자리 칸 묶음이 측점 자리와 다르면 뺌 · 같으면 그대로.
|
|
- 서버 노선 저장 넘김(`carry_cross_inputs` · `seats`) — 같은 규칙 · `seats` 없으면 옛 동작.
|
|
- 기본 설계가 넘긴 손 단면 유형으로 셈(`compute_default_designs` · 없으면 상단측 깎기).
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_475_3_seat_carry.py -q
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
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))
|
|
|
|
from B05_Profile.B05_Profile_Engine_Sections import find_cross_file # noqa: E402
|
|
from B05_Profile.B05_Profile_Route_Save_Parts import carry_cross_inputs, station_seats # noqa: E402
|
|
from B06_Section.B06_Section_Router_Design import ( # noqa: E402
|
|
CARRY_INPUT_KEYS,
|
|
SEAT_KEYS,
|
|
compute_default_designs,
|
|
)
|
|
from common_util.common_util_station_key import same_seat, station_seat # noqa: E402
|
|
|
|
SEATS = [
|
|
[{"x": 0, "y": 0, "left": [1, 0]}, {"x": 0.009, "y": 0, "left": [1, 0]}],
|
|
[{"x": 0, "y": 0, "left": [1, 0]}, {"x": 0.011, "y": 0, "left": [1, 0]}],
|
|
[{"x": 0, "y": 0, "left": [1, 0]}, {"x": 0, "y": 0, "left": [0.998, 0.002]}],
|
|
[{"x": 0, "y": 0, "left": [1, 0]}, {"x": 0, "y": 0, "left": [0.9995, 0.0009]}],
|
|
[{"x": 0, "y": 0, "left": None}, {"x": 0, "y": 0.005, "left": [0, 1]}],
|
|
[None, {"x": 9, "y": 9, "left": [0, 1]}],
|
|
]
|
|
|
|
|
|
def _py_seat(seat):
|
|
if seat is None:
|
|
return None
|
|
left = seat.get("left")
|
|
return seat["x"], seat["y"], tuple(left) if left else None
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def out(tmp_path_factory):
|
|
work = tmp_path_factory.mktemp("b475_3")
|
|
build = subprocess.run(
|
|
[
|
|
"node",
|
|
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
|
"build",
|
|
"--configLoader",
|
|
"runner",
|
|
"--ssr",
|
|
"../resources/tester/helper_475_3_entry.ts",
|
|
"--outDir",
|
|
str(work),
|
|
"--logLevel",
|
|
"error",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=300,
|
|
)
|
|
assert build.returncode == 0, build.stderr
|
|
bundle = next(work.glob("*.js"), None) or next(work.glob("*.mjs"))
|
|
inputs = work / "input.json"
|
|
inputs.write_text(json.dumps({"seats": SEATS}), "utf-8")
|
|
proc = subprocess.run(
|
|
["node", str(HERE / "helper_475_3_seat_carry.mjs"), str(bundle), str(inputs)],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_자리_칸_목록_정본은_TS_하나():
|
|
swap = (ROOT / "B06_Section" / "logic" / "B06_Section_Station_Swap.ts").read_text("utf-8")
|
|
body = re.search(r"export const SEAT_KEYS = \[(.*?)\] as const;", swap, re.S)
|
|
assert tuple(re.findall(r'"([a-z_0-9]+)"', body.group(1))) == SEAT_KEYS
|
|
assert SEAT_KEYS == (
|
|
"two_stage_slope",
|
|
"rock_boundary_offset_m",
|
|
"cut_slope_ratio_user",
|
|
"section_mode",
|
|
"ditch_side",
|
|
)
|
|
|
|
|
|
def test_자리_판정_거울(out):
|
|
assert out["sameSeat"] == [same_seat(_py_seat(a), _py_seat(b)) for a, b in SEATS]
|
|
assert out["sameSeat"] == [True, False, False, True, True, True]
|
|
|
|
|
|
def test_같은_자리는_자리_칸도_옮긴_자리는_기본값(out):
|
|
carried = out["carried"]
|
|
assert carried["rockb"] == {
|
|
"grid:1": 1.2,
|
|
"grid:3": 0.9,
|
|
} # grid:2 옮김 = 버림 · grid:3 = 쓴 때 자리
|
|
assert carried["cutslope"] == {"grid:1": 0.8}
|
|
assert carried["crossdesign"]["grid:1"] == { # 포장은 안 넘김(138-1)
|
|
"ground_type": "soil",
|
|
"ditch_type": "l_type",
|
|
"two_stage_slope": True,
|
|
"section_mode": "right_cut",
|
|
"ditch_side": "left",
|
|
}
|
|
assert carried["crossdesign"]["grid:2"] == {"ground_type": "soil", "ditch_type": "l_type"}
|
|
assert carried["shoulderext"] == {"grid:2": {"left": 0.5}} # 자리 칸 아님 = 그대로
|
|
assert out["carriedSeats"]["grid:3"] == {"x": 3, "y": 60, "left": [-1, 0]}
|
|
assert out["outside"] == 0
|
|
|
|
|
|
def test_자리_모르는_옛_목록은_옛_동작(out):
|
|
unknown = out["unknown"]
|
|
assert unknown["rockb"] == {"grid:1": 1.2, "grid:2": 1.5, "grid:3": 0.9}
|
|
assert unknown["crossdesign"]["grid:2"]["two_stage_slope"] is True
|
|
|
|
|
|
def test_미리보기_입력_자리_칸(out):
|
|
at = out["atSeat"]
|
|
assert at["grid:1"] == {
|
|
"groundType": "soil",
|
|
"rockBoundaryOffsetM": 1,
|
|
"sectionMode": "right_cut",
|
|
}
|
|
# grid:2 = 저장 자리(0, 40)에서 온 2단 경사는 뺌 · 새 자리(5, 40)에서 쓴 암 경사는 그대로
|
|
assert at["grid:2"] == {"groundType": "soil", "cutSlopeRatio": 0.7}
|
|
|
|
|
|
def test_B06_갈아끼움_자리_바뀐_측점은_자리_칸_기본값(out):
|
|
same, moved = out["swapDraft"]
|
|
assert same["rock_boundary_offset_m"] == -1.2 and same["section_mode"] == "both_cut"
|
|
assert moved == {
|
|
"ground_type": "soil",
|
|
"section_mode": "left_cut",
|
|
"shoulder_extend_left_m": 0.5,
|
|
}
|
|
# 노선 초안 아닌 차례(관 · 구조물 · 추가 측점만) = 이어 받은 설계 그대로
|
|
assert out["swapNoDraft"][1]["two_stage_slope"] is True
|
|
|
|
|
|
def _row(chainage, x, left=(-1.0, 0.0)):
|
|
return {
|
|
"chainage_m": chainage,
|
|
"center_x": x,
|
|
"center_y": chainage,
|
|
"frame": {"left_xy": list(left)},
|
|
}
|
|
|
|
|
|
def test_서버_노선_저장_넘김_같은_규칙():
|
|
old_keys = {"grid:1": 20.0, "grid:2": 40.0}
|
|
new_keys = {"grid:1": 20.0, "grid:2": 40.5}
|
|
old_rows = [_row(20.0, 0.0), _row(40.0, 0.0)]
|
|
new_rows = [_row(20.0, 0.0), _row(40.5, 5.0)]
|
|
seats = (station_seats(old_rows, old_keys), station_seats(new_rows, new_keys))
|
|
hand = {
|
|
"ground_type": "soil",
|
|
"two_stage_slope": True,
|
|
"rock_boundary_offset_m": 1.5,
|
|
"cut_slope_ratio_user": 0.8,
|
|
"section_mode": "right_cut",
|
|
"ditch_side": "left",
|
|
"shoulder_extend_left_m": 0.5,
|
|
}
|
|
designs = {20.0: dict(hand), 40.0: dict(hand)}
|
|
keys = (*CARRY_INPUT_KEYS, "shoulder_extend_left_m")
|
|
inputs, _, dropped = carry_cross_inputs(old_keys, new_keys, designs, keys, (), SEAT_KEYS, seats)
|
|
assert inputs[20.0] == hand
|
|
assert inputs[40.5] == {"ground_type": "soil", "shoulder_extend_left_m": 0.5}
|
|
assert dropped == 0
|
|
# seats 없음 = 옛 동작(손 상단측은 안 넘김 · 자리 칸 셋은 넘김)
|
|
old, _, _ = carry_cross_inputs(old_keys, new_keys, designs, keys, ())
|
|
assert old[40.5] == {k: v for k, v in hand.items() if k not in ("section_mode", "ditch_side")}
|
|
assert station_seat({"chainage_m": 1}) is None
|
|
|
|
|
|
def test_기본_설계가_넘긴_손_단면_유형으로_셈(tmp_path, monkeypatch):
|
|
"""엔진 입력 = 넘긴 단면 유형 · 측구 방향(결과 단면 유형은 엔진이 지형으로 다시 판정)."""
|
|
import B06_Section.B06_Section_Router_Design as design_module
|
|
|
|
seen: list[tuple[str, object]] = []
|
|
real = design_module.compute_cross_designs # 511-B B-4 — 측점 묶음 Node 한 번
|
|
|
|
def spy(jobs, *args, **kwargs):
|
|
seen.extend((options["section_mode"], options.get("ditch_side")) for *_, options in jobs)
|
|
return real(jobs, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(design_module, "compute_cross_designs", spy)
|
|
route = tmp_path / "routes" / "r1"
|
|
(route / "longitudinal").mkdir(parents=True)
|
|
(route / "cross_sections").mkdir()
|
|
profile = [
|
|
{"chainage_m": 0.0, "elevation_m": 100.0},
|
|
{"chainage_m": 40.0, "elevation_m": 100.0},
|
|
]
|
|
relative = "routes/r1/longitudinal/longitudinal.json"
|
|
stations = [{"chainage_m": 20.0, "uphill_side": "left"}]
|
|
(tmp_path / relative).write_text(
|
|
json.dumps({"stations": stations, "design_profiles": [{"samples": profile}]}), "utf-8"
|
|
)
|
|
samples = [
|
|
{"offset_m": float(o), "elevation_m": 100.0 + 0.3 * o, "valid": True}
|
|
for o in range(-15, 16)
|
|
]
|
|
cross = {"chainage_m": 20.0, "samples": samples, "uphill_side": "left"}
|
|
find_cross_file(route / "cross_sections", 20.0).write_text(json.dumps(cross), "utf-8")
|
|
plain = compute_default_designs(tmp_path, relative, [20.0])
|
|
hand = compute_default_designs(
|
|
tmp_path,
|
|
relative,
|
|
[20.0],
|
|
carried={20.0: {"section_mode": "both_cut", "ditch_side": "right"}},
|
|
)
|
|
assert plain and hand
|
|
assert seen == [("left_cut", None), ("both_cut", "right")]
|