Files
Aislo/resources/tester/test_b05_station_roundtrip.py
T
eomsangdonandClaude Opus 5.5 bbd6254892 feat(구조물표): 322-1 부품 상자 · 표 읽기 — 목록 · 측점 · 카드 이름 · 형상 고르기 · 3D 몸체 · 알약 띠가 종류 키 · 군 글자 · 자리꼴 · managed_by 대신 구조물 표(catalog) 한 줄을 읽음 · 코드 숨김 목록 걷음 · ★ 넷 + 관 공식 한 벌
- 부품 상자 common_util_structure_parts.json(갈래마다 부품 · 읽는 칸 · 자리꼴 · 정본 · 42종 처음값) · 표 읽기 common_util_structure_catalog.ts / .py 짝(정의 반영 ⊕ 처음값 ⊕ 부품 끔) · 거울 시험 test_322_1_catalog_mirror
- 목록: pickGroups 군 차례 = 구조물군 열쇠 · 페이지 거르기 listPick(B05 both · B06 both + b06) · HIDDEN_PICK_GROUPS · HIDDEN_PICK_TYPES · pickable 걷음(옛 대피 셋 asRoad 그대로)
- 측점: structureStationMarks · pipeStationMarks · is_station_planting_type · resolve_extra_stations = 표 횡단도(anchor · span3 · none)
- 카드 이름: cardTitle / card_title 한 공식(빈칸 = 그 종류 정의 기본값) · 표시 이름이 정의 관종도 앎
- 형상: 벽 = shape wall(형태 = 형상칸.form · 물빼기 = 형상칸.weep) · 포장 = shape pavement(「사용 중」 거르기 없앰) · 맹암거 · 사토 · 관 지점 시설 세트 = shape
- 3D 몸체 = 그림 자료 주인 종류의 표 3D 부품 · 알약 띠 = 표 자리꼴 구간(관 지점 시설은 알약만)
- ★1 노출형 · 개거 횡단도 none · ★2 쇄석 화면에서도 포장층 · ★3 기슭막이 표 자리꼴 interval(정의 파일 그대로) · ★4 코드 숨김 10종 list hidden · ★5 관 빈칸 D800 → 정의 기본 D1000
- 옛 시험: 도움 파일 로더 27개가 표 모듈 공용 한 벌(helper_322_catalog_module) 씀 · 기대값 고침(272_1 · b05_extra_stations · roundtrip · 314 · 52_7 · walls 거울 · wall_drain_pipe)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 15:09:45 +09:00

132 lines
5.4 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"] # 칸 표기도 정수 m(167) · 값은 실어 둔 그대로
assert results["untouched"] == 123.37
assert results["untouchedRaw"] == 123.3721
assert results["retypedSame"] == 123.37
assert results["r119"] == ["5", "20", 119.8]
def test_화면_표기는_거리를_정수로(results):
assert results["labels"] == ["5+20", "6+0", "4+4", "6+3", "5+20"]
def test_칸을_고치면_친_값(results):
# 친 값 125.2 → 정수 m 125(234-1 · 저장값도 정수 m — 9-29 「소수 그대로」를 바꿈)
assert results["edited"] == 125
assert results["normalized"] == ["2", "10", 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_underdrain", # 측점 심는 점형(노출형은 322-1 ★1 로 횡단도 none)
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"