"""PLAN 349-0f — 노선 초안 동안 유토곡선 「다시 계산 중…」 머묾. · 두 번 끌기(2번째 꺾임점 서쪽 20 m = 길이 바뀜 · 가운데 꺾임점 북동 10 m) 뒤 새 노선 횡단 낡음 0 (cross 단계가 바뀐 범위 밖 측점도 계획고가 바뀌었으면 다시 셈) · 「다시 계산 중」 = 묶음 그 차례 cross 를 기다리는 동안만 · 끝나면 풂 · 남은 낡음은 곡선 + 그 측점(342-1) · 그 차례 cross 가 끝내 안 와도 3 s 뒤 풂 · 다시 그림 짝: `helper_349_0f_draft_stale.cjs` · 지형 · 노선 · 관 틀 = `test_346_2_pipeline_cross_page.py`. """ from __future__ import annotations import json import subprocess import sys import tempfile from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] for path in (ROOT, HERE): if str(path) not in sys.path: sys.path.insert(0, str(path)) from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402 design_ground_following_profile, ) from common_util.common_util_surface_sampler import DtmGridSampler, dtm_grid_bytes # noqa: E402 from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402 from shapely.geometry import LineString, Point # noqa: E402 from test_346_2_pipeline_cross_page import ( # noqa: E402 MIN_RADIUS_M, INTERVAL_M, ONES, _fake, _real, _line, _pipes, _saved_detail, _sections, _types, ) pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), reason="프론트엔드 의존성 미설치", ) def _drags(nodes: list[list[float]], line: list[list[float]]) -> list[dict]: """① 2번째 꺾임점 서쪽 20 m · ② 25+0(500 m) 가까운 꺾임점 북동 10 m(짧은 노선은 가운데).""" shape = LineString(line) target = min(500.0, shape.length / 2) inner = [i for i in range(2, len(nodes) - 1)] or [1] second = min(inner, key=lambda i: abs(shape.project(Point(nodes[i])) - target)) return [ {"index": 1, "dx": -20.0, "dy": 0.0}, {"index": second, "dx": 7.07, "dy": 7.07}, ] @pytest.fixture(scope="module", params=[_fake, _real], ids=["가짜", "검증_sub7"]) def r(request) -> dict: with tempfile.TemporaryDirectory() as folder: npz, nodes = request.param(Path(folder)) line = _line(nodes) sampler = DtmGridSampler.from_npz(npz) pipes = _pipes(line) saved = _sections(line, sampler, pipes) options = resolve_grade_options("trunk", terrain_type="normal", paved=False, stored=None) policy = design_ground_following_profile(saved["longitudinal"], options)[0]["policy"] job = { "nodes": nodes, "line": line, "minRadius": MIN_RADIUS_M, "policy": policy, "types": _types(), "interval": INTERVAL_M, "pipes": pipes, "standard": STANDARD_CROSS_SECTION, "conversion": ONES, "detail": _saved_detail(saved), "haulDelayMs": 20, "drags": _drags(nodes, line), } path = Path(folder) / "job.json" path.write_text(json.dumps(job), encoding="utf-8") grid = Path(folder) / "grid.bin" grid.write_bytes(dtm_grid_bytes(npz)) proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_349_0f_draft_stale.cjs"), str(path), str(grid)], capture_output=True, text=True, encoding="utf-8", timeout=180, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_길이_바뀐_끌기_뒤_낡음_0(r): one = r["one"] assert one["outcome"] == "done" assert abs(one["length"] - r["savedLength"]) > 1 # 노선 길이 바뀜 assert one["stale"] == [] def test_가운데_꺾임점_끌기_뒤_낡음_0(r): two = r["two"] assert two["outcome"] == "done" and two["reused"] assert two["stale"] == [] def test_다시_계산_중은_그_차례_cross_까지만(r): assert r["pendingDuring"] is True # 끌어 놓은 차례 — cross 기다림 assert r["one"]["pendingAfter"] is False # cross 끝 — 남은 낡음은 곡선 + 그 측점 assert r["two"]["pendingAfter"] is False assert r["two"]["pendingSaved"] is False def test_cross_가_안_와도_3초_뒤_풂(r): released = r["released"] assert released["stuck"] is True assert released["after"] is False and released["redrew"] == 1