Files
Aislo/resources/tester/test_475_3_seat_carry.py
T

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")]