"""490-1 B05 종단 — 임시 횡단이 열리는 자리 가까이 표시 = 누름과 같은 판정(`crossPickAt`). 판정 함수 = 옛 누름 판정(원지반선 위아래 12 px · 500-1 8 → 12 · 정수 m · 추가 측점 선 = 그 측점 · 단추 = null)과 같은 값 · 허용 밖 null. 500-2 = 다른 측점선("station")은 띠 안이면 임시 횡단 · 띠 밖이면 null(측점 고름 몫). TS 쪽 = `helper_490_1_cross_pick.cjs`(Node). 화면 조작은 ORCA 로 확인. """ from __future__ import annotations import json import subprocess import tempfile from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] SAMPLES = [ {"chainage_m": 0.0, "elevation_m": 10.0}, {"chainage_m": 20.0, "elevation_m": 20.0}, {"chainage_m": 40.0, "elevation_m": None}, # 빈 표고는 건너뜀 {"chainage_m": 60.0, "elevation_m": 0.0}, ] def _ground_y(elevation: float) -> float: return 100 - elevation * 2 def _old_pick(hit, chainage: float, pointer_y: float) -> int | None: """옛 `groundPickAt` + 누름 처리(Render.ts 329~346) 그대로.""" if hit == "other": return None if isinstance(hit, dict): return round(hit["plusM"]) valid = [s for s in SAMPLES if s["elevation_m"] is not None] after = next((i for i, s in enumerate(valid) if s["chainage_m"] >= chainage), -1) if after <= 0: return None a, b = valid[after - 1], valid[after] t = (chainage - a["chainage_m"]) / (b["chainage_m"] - a["chainage_m"] or 1) ground = a["elevation_m"] + (b["elevation_m"] - a["elevation_m"]) * t return round(chainage) if abs(pointer_y - _ground_y(ground)) <= 12 else None CASES = [ {"hit": None, "chainageM": 10.2, "pointerY": 69.8}, # 지반 y = 69.8 · 딱 위 {"hit": None, "chainageM": 10.2, "pointerY": 81.8}, # 12 px 끝(옛 8 px 밖) {"hit": None, "chainageM": 10.2, "pointerY": 82.3}, # 허용 밖 {"hit": None, "chainageM": 10.2, "pointerY": 57.3}, # 위쪽 허용 밖 {"hit": None, "chainageM": 50.0, "pointerY": 93.0}, # 빈 표고 건너 20→60 보간 {"hit": None, "chainageM": 0.0, "pointerY": 80.0}, # 첫 표본 = null(옛 판정 그대로) {"hit": None, "chainageM": 70.0, "pointerY": 100.0}, # 끝 뒤 {"hit": {"plusM": 33.6}, "chainageM": 10.0, "pointerY": 0.0}, # 추가 측점 선 {"hit": "other", "chainageM": 10.4, "pointerY": 70.0}, # 단추 {"hit": "station", "chainageM": 10.4, "pointerY": 69.0}, # 측점선 · 띠 안 = 임시 횡단 {"hit": "station", "chainageM": 10.4, "pointerY": 81.0}, # 측점선 · 띠 끝 안 {"hit": "station", "chainageM": 10.4, "pointerY": 90.0}, # 측점선 · 띠 밖 = 측점 고름 {"hit": "station", "chainageM": 0.0, "pointerY": 80.0}, # 측점선 · 첫 표본 = null ] def _run() -> dict: with tempfile.TemporaryDirectory() as folder: path = Path(folder) / "job.json" path.write_text(json.dumps({"samples": SAMPLES, "cases": CASES}), encoding="utf-8") proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_490_1_cross_pick.cjs"), str(path)], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_판정_함수_옛_누름과_같은_값() -> None: result = _run() assert result["slack"] == 12 picks = [row["pick"] for row in result["out"]] assert picks == [_old_pick(c["hit"], c["chainageM"], c["pointerY"]) for c in CASES] assert picks == [10, 10, None, None, 50, None, None, 34, None, 10, 10, None, None] def test_누름_표시_같은_길() -> None: render = (ROOT / "B05_Profile/B05_Profile_UI_Profile_Render.ts").read_text(encoding="utf-8") assert render.count("crossPickFromEvent(chartWrap, event") == 2 # 누름 · 표시 assert "mountCrossHint(chartWrap, body" in render and "blocked: ctx.toolActive" in render modal = (ROOT / "B05_Profile/B05_Profile_UI_Profile_CrossModal.ts").read_text(encoding="utf-8") assert "crossPickAt({" in modal and "event.buttons || options.blocked()" in modal assert 'scroller.addEventListener("scroll", hide' in modal