PLAN 20장 소수 측점 횡단(sub6). 사용자 확정: 관 · 구조물이 놓인 소수 측점(예 119.8)에 그 자리 횡단을
따로 둠 · 백엔드 · 파일 · 저장값은 소수 그대로 · 화면 표기만 「측점 + 거리」의 거리를 정수로(5+20).
- 이름 규칙 한 벌(B05_Profile_Engine_Sections): cross_key · cross_filename · parse_cross_key ·
CROSS_KEY_PATTERN — 정수 m 는 옛 이름 그대로(cross_00120m) · 소수 측점은 cross_00119.80m.
find_cross_file = 읽기용(새 이름이 없으면 옛 정수 m 이름) · prune_stale_cross_files 도 이것으로
가려 옛 프로젝트의 정수 이름 파일을 지우지 않음
- generate_sections: 정수 m 격자 붙임을 풂 — 같은 이름이 되는 0.005m 안 겹침만 하나로
- B06: 찾기 · 패치 입력(Router_Design 둘) · 샘플 넓힘(SampleExtend)이 find_cross_file ·
DB 설계 찾기 · 병합(get_cross_section_design · merge_…_by_round)은 ROUND(chainage_m)=정수 →
0.005m 안 매칭
- B07: 도면 id · 장 id(cross_{key}m · cross_s{key}m) 정규식 · 측점 키(cross_station — 정수는 int
그대로라 옛 id · 수량표 키 안 바뀜) · 측점은 파일 속 값으로(옛 정수 이름 파일도) · 횡단 파일 찾기
- 화면 표기 한 벌 common_util/common_util_station.ts(stationLabel · stationPartsLabel · 순수 함수) —
common_util_svg 가 다시 내보내고 B05 formatStation 을 대신함(B05 목록 · 표시 · 폼 범위 · 계획노선
편집 · 비정규 측점 라벨 · B06 카드 · 종단 · 유토) · 입력 칸 · 저장값은 소수 그대로
- B05_Profile_UI_Structures_Panel 700줄 → 690(순수 도우미 셋을 Panel_Types 로)
- M02_Table_Fill 관연장 찾기는 이미 측점 값(정확 먼저 · 0.5m 안)이라 그대로
시험: test_b05_station_roundtrip(표기 5+20 · 옛 정수 이름 찾기 · 안 지움 · 119.8 과 120 따로) ·
test_b05_extra_stations_supplier(새 규칙) · test_b06_patch_station(119.8 한 건) ·
test_b05_b06_save_roundtrip(보낸 관 측점마다 소수 이름 횡단 파일 있음) 등 고친 대상 108 통과 ·
test_b05_station_merge 통과 · 타입검사 통과.
ORCA(IP 5181 · 사본 프로젝트 sub6_검증 — 끝나고 DB 행 · 폴더 지움 · 원본 안 씀): 노출형 횡단수로를
5+19.8 에 놓고 저장 → structures.json 119.8 · 종단 측점 119.8(irregular) + 120 · 파일
cross_00119.80m.json 과 cross_00120m.json · B05 목록 「5+20」 · B06 카드 「5+20 · 119.8m」 열림.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVM3xZ1aUPcUTuW2WZSnwg
131 lines
5.2 KiB
Python
131 lines
5.2 KiB
Python
# -*- 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"
|