# -*- coding: utf-8 -*- """구조물 측점 왕복 — 옵션 · 메모만 고쳐 저장해도 측점이 그대로(PLAN 19장 sub6). 측점 칸은 잔여거리를 소수 1자리로 보인다. 칸을 안 고쳤으면 실어 둔 누가거리를 그대로 읽어야 한다(123.37 → 123.4 로 뭉개져 옮겨 저장되던 흠). 서버가 관 측점을 0.01m 로 반올림해 돌려주면 계획고 편집(station_offsets)은 지우지 않고 새 측점으로 옮긴다. Node 헬퍼(`helper_b05_station_roundtrip.cjs`)가 TS 를 트랜스파일해 돌리고 여기서 판정한다. """ import json import os import subprocess import pytest HERE = os.path.dirname(os.path.abspath(__file__)) @pytest.fixture(scope="module") def results(): proc = subprocess.run( ["node", os.path.join(HERE, "helper_b05_station_roundtrip.cjs")], capture_output=True, text=True, timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_칸을_안_고치면_실어_둔_측점_그대로(results): assert results["shown"] == ["6", "3.4"] assert results["untouched"] == 123.37 assert results["untouchedRaw"] == 123.3721 assert results["retypedSame"] == 123.37 assert results["r119"] == ["5", "19.8", 119.8] def test_화면_표기는_거리를_정수로(results): assert results["labels"] == ["5+20", "6+0", "4+4", "6+3", "5+20"] def test_칸을_고치면_친_값(results): assert results["edited"] == pytest.approx(125.2) assert results["normalized"] == ["2", "10.0", 50] assert results["cleared"] is None def test_계획고_편집은_가까운_새_측점으로_옮김(results): assert results["moved"] == { "station_offsets": {"200.000": 0.3, "123.370": 0.5}, "curve_radii": {"123.370": 40}, } # 옮길 곳이 없으면 종전대로 지움 assert "200.000" not in results["deleted"]["station_offsets"] # ── 서버 — 관 · 구조물 자체 측점은 소수 그대로(정수 m 는 횡단 파일 이름에만) ───────── # 브레인 판정(다) · 19장 남은 차이: 파일 이름은 한 정수 m 에 한 장 그대로, 측점 값은 사용자가 놓은 # 소수 그대로 저장 · 다시 읽기 · 종단 측점. 임시 storage 에서만 씀(공유 storage 안 건드림). def _flat_sampler(): import numpy as np class _Flat: def sample_xy(self, xy): count = len(np.asarray(xy, dtype=np.float64)) return np.full(count, 100.0), np.ones(count, dtype=bool) return _Flat() def test_사용자_구조물_119_8_저장_다시_읽기_그대로(tmp_path): from B05_Profile.B05_Profile_Engine_Sections import cross_filename, resolve_extra_stations from B05_Profile.B05_Profile_Engine_Sections_Core import ( SectionGenerationOptions, generate_sections, ) from B05_Profile.B05_Profile_Structures_Repository import load_structures, save_structures from B05_Profile.B05_Profile_Structures_Schema import StructureInstance item = StructureInstance( type_id="cross_drain_exposed", placement="point", chainage_m=119.8, memo="메모만", placement_source="manual", ) save_structures(str(tmp_path), [item], base_revision=0) _, loaded = load_structures(str(tmp_path)) assert [s.chainage_m for s in loaded] == [119.8] extras = resolve_extra_stations(tmp_path, []) assert [chainage for chainage, _ in extras] == [119.8] sections = generate_sections( [[0.0, 0.0, 100.0], [300.0, 0.0, 100.0]], _flat_sampler(), SectionGenerationOptions(extra_stations=extras), ) chainages = [round(s["chainage_m"], 3) for s in sections["longitudinal"]["stations"]] # 소수 측점 횡단을 따로 둠(PLAN 20장) — 119.8 · 격자 120 둘 다 · 이름도 따로 assert 119.8 in chainages and 120.0 in chainages assert (cross_filename(119.8), cross_filename(120.0)) == ( "cross_00119.80m.json", "cross_00120m.json", ) _, again = load_structures(str(tmp_path)) assert [s.chainage_m for s in again] == [119.8] and again[0].memo == "메모만" def test_옛_프로젝트_정수_이름_횡단은_찾고_안_지움(tmp_path): """소수 측점이 정수 m 이름으로 저장된 옛 프로젝트 — 열 때 지우지 않고 그 파일을 씀.""" from B05_Profile.B05_Profile_Engine_Sections import find_cross_file, prune_stale_cross_files (tmp_path / "cross_00084m.json").write_text("{}", encoding="utf-8") # 옛 84.3 (tmp_path / "cross_00100m.json").write_text("{}", encoding="utf-8") (tmp_path / "cross_00119.80m.json").write_text("{}", encoding="utf-8") # 새 이름 (tmp_path / "cross_00500m.json").write_text("{}", encoding="utf-8") # 잔재 stations = [{"chainage_m": 84.3}, {"chainage_m": 100.0}, {"chainage_m": 119.8}] valid = prune_stale_cross_files(tmp_path, stations) assert sorted(p.name for p in tmp_path.iterdir()) == [ "cross_00084m.json", "cross_00100m.json", "cross_00119.80m.json", ] assert valid == {"cross_00084m.json", "cross_00100m.json", "cross_00119.80m.json"} assert find_cross_file(tmp_path, 84.3).name == "cross_00084m.json" assert find_cross_file(tmp_path, 119.8).name == "cross_00119.80m.json"