# -*- coding: utf-8 -*- """380-3 노선이 바뀌어 좌표로 따라간 사토장 · 토취장(site) = 새 노선 일반 측점 중 가장 가까운 곳. 요구 241-8 「기존에 있던 측점들을 기준으로」 — 따라간 뒤에도 기존 측점에 섬(CAD 글 ±0.51 m). 그 밖 꼴은 종전대로 정수 m. 짝 = 파이썬 `follow_structures` · `snap_existing_station` · `general_stations` ↔ TS `followStructures(…, intervalM)` · `snapExistingStation` · `gridStations` (`helper_380_3_site_follow.cjs`). """ from __future__ import annotations import json import subprocess import sys from pathlib import Path from types import SimpleNamespace 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 common_util.common_util_route_follow import follow_structures # noqa: E402 from common_util.common_util_structure_station import ( # noqa: E402 general_stations, snap_existing_station, ) from config.config_system_design import SECTION_STATION_INTERVAL_M # noqa: E402 # 새 노선 = 길이 130.0 m(50 + 50 + 30) — 일반 측점 0 · 20 · … · 120 · 130 LINE = [(0.0, 0.0), (50.0, 0.0), (50.0, 50.0), (80.0, 50.0)] ITEMS = [ {"placement": "site", "chainage_m": 33.0, "x": 33.0, "y": 1.0}, # 투영 33 → 40 {"placement": "site", "chainage_m": 75.0, "x": 51.0, "y": 25.0}, # 투영 75 → 80(가까운 쪽) {"placement": "site", "chainage_m": 10.0, "x": 10.0, "y": -2.0}, # 가운데 10 → 앞 측점 0 {"placement": "site", "chainage_m": 127.0, "x": 78.0, "y": 50.0}, # 투영 128 → 끝 130 {"placement": "point", "chainage_m": 33.0, "x": 33.0, "y": 1.0}, # site 아님 = 정수 m 33 {"placement": "site", "chainage_m": 40.0}, # 좌표 없음 — 그대로 ] WANT = [40.0, 80.0, 0.0, 130.0, 33.0, 40.0] GRIDS = [(130.0, 20.0), (120.0, 20.0), (0.0, 20.0), (5.5, 2.0)] SNAPS = [(25.0, [0.0, 20.0, 40.0]), (30.0, [0.0, 20.0, 40.0]), (7.0, [0.0, 10.0])] pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), reason="프론트엔드 의존성 미설치", ) @pytest.fixture(scope="module") def ts(tmp_path_factory) -> dict: source = tmp_path_factory.mktemp("s3803") / "in.json" source.write_text( json.dumps( { "grids": GRIDS, "snaps": SNAPS, "items": ITEMS, "line": [{"x": x, "y": y} for x, y in LINE], "interval": SECTION_STATION_INTERVAL_M, } ), encoding="utf-8", ) proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_380_3_site_follow.cjs"), str(source)], capture_output=True, text=True, encoding="utf-8", cwd=str(ROOT), check=False, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def _python_follow() -> list[float]: items = [ SimpleNamespace(**{"x": None, "y": None, "start_m": None, "end_m": None, **item}) for item in ITEMS ] follow_structures(items, LINE) return [item.chainage_m for item in items] def test_따라간_site_는_새_노선_일반_측점() -> None: assert SECTION_STATION_INTERVAL_M == 20.0 assert _python_follow() == WANT def test_거울_파이썬_TS(ts) -> None: assert ts["follow"] == _python_follow() assert ts["grid"] == [general_stations(total, interval) for total, interval in GRIDS] assert ts["snap"] == [snap_existing_station(value, stations) for value, stations in SNAPS] assert ts["snap"] == [20.0, 20.0, 10.0] # 가운데면 앞 측점 def test_간격_안_주면_종전_정수_m(ts) -> None: assert ts["plain"] == [33.0, 75.0, 10.0, 128.0, 33.0, 40.0]