- 부품 상자 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
187 lines
7.3 KiB
Python
187 lines
7.3 KiB
Python
"""272-1 측점 한 벌 — 화면(TS)과 서버(파이썬)가 같은 측점 목록을 냄(요구 167 · 측점 겹침 근본).
|
|
|
|
- 심는 자리: TS `plantedChainages` ↔ 서버 `resolve_extra_stations`(관 · 독립 기슭막이 시작 ·
|
|
기준 · 끝 · A군 · C군 구간형 시작 · 기준 · 끝 · 물넘이포장 기준(286-1) · 다른 군 · 점형 C/D 는 안 심음).
|
|
- 같은 m = 측점 하나 · 구조물이 위 · 떠나면 일반 측점: TS `oneStationList` ↔ 서버
|
|
`_merge_irregular_into_longitudinal`.
|
|
진짜 TS 는 `helper_272_1_station_list.cjs` 가 Node 로 돌림.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B05_Profile.B05_Profile_Engine_Sections import resolve_extra_stations # noqa: E402
|
|
from B05_Profile.B05_Profile_Router_Confirm import _merge_irregular_into_longitudinal # noqa: E402
|
|
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
|
from common_util.common_util_drainage_pipes import parse_pipe_points # noqa: E402
|
|
|
|
PIPES = [
|
|
{"chainage_m": 95.0, "source": "user"},
|
|
{"chainage_m": 160.0, "source": "user", "facility": "box_culvert"},
|
|
{"chainage_m": 230.0, "source": "user", "facility": "ford_pavement"},
|
|
{"chainage_m": 300.0, "source": "user", "facility": "ford_bridge"},
|
|
{
|
|
"chainage_m": 400.0,
|
|
"source": "user",
|
|
"facility": "revetment",
|
|
"start_m": 395.0,
|
|
"end_m": 410.0,
|
|
},
|
|
]
|
|
STRUCTURES = [
|
|
# 노출형 = 구조물 표 횡단도 none(322-1 ★1) — 측점 안 심음
|
|
{"type_id": "cross_drain_exposed", "placement": "point", "chainage_m": 120.0},
|
|
{
|
|
"type_id": "retaining_wall",
|
|
"placement": "interval",
|
|
"chainage_m": 510.0,
|
|
"start_m": 500.0,
|
|
"end_m": 530.0,
|
|
},
|
|
{ # 옹벽 끝 530 과 같은 자리에서 시작 — 겹친 자리도 그대로 셈(합치기는 측점 만들기 몫)
|
|
"type_id": "berm",
|
|
"placement": "interval",
|
|
"chainage_m": 545.0,
|
|
"start_m": 530.0,
|
|
"end_m": 560.0,
|
|
},
|
|
{"type_id": "erosion_check", "placement": "point", "chainage_m": 600.0}, # D군 점형
|
|
{
|
|
"type_id": "pavement_concrete",
|
|
"placement": "interval",
|
|
"chainage_m": 650.0,
|
|
"start_m": 650.0,
|
|
"end_m": 700.0,
|
|
},
|
|
{"type_id": "evac_road", "placement": "point", "chainage_m": 720.0},
|
|
]
|
|
EXPECTED = [95, 160, 230, 300, 395, 400, 410, 500, 510, 530, 530, 545, 560]
|
|
|
|
# 서버 종단 측점(맞추기 전) — 격자 0~100 · 옛 관 측점 95.38 · 구조물이 떠난 격자 자리 40(`grid`) ·
|
|
# 옛 판 「regular + 글」 60 · 새 목록 = 격자 위 100(구조물이 위) · 새 자리 35
|
|
SERVER = [
|
|
{"chainage_m": 0.0, "kind": "bp"},
|
|
{"chainage_m": 20.0, "kind": "regular"},
|
|
{"chainage_m": 40.0, "kind": "irregular", "grid": True, "structure": "옹벽"},
|
|
{"chainage_m": 60.0, "kind": "regular", "structure": "옛 글"},
|
|
{"chainage_m": 80.0, "kind": "regular"},
|
|
{"chainage_m": 95.38, "kind": "irregular", "structure": "파형강관 D800"},
|
|
{"chainage_m": 100.0, "kind": "regular"},
|
|
]
|
|
FRESH = [
|
|
{"chainage_m": 35.0, "kind": "irregular", "structure": "파형강관 D800"},
|
|
{"chainage_m": 100.0, "kind": "irregular", "grid": True, "structure": "BOX암거"},
|
|
]
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
|
reason="프론트엔드 의존성 미설치",
|
|
)
|
|
|
|
|
|
def _types() -> list[dict]:
|
|
return [
|
|
{
|
|
"type_id": item.type_id,
|
|
"group": item.group,
|
|
"placement": item.placement,
|
|
"managed_by": item.managed_by,
|
|
}
|
|
for item in structure_type_map().values()
|
|
]
|
|
|
|
|
|
FIXTURE = ROOT / "resources/tester/fixtures/b05_192_1_moved_pipe_longitudinal.json"
|
|
|
|
|
|
def _snapped(data: dict) -> list[dict]:
|
|
"""옛 소수 구조물 측점을 정수 m 로 옮긴 새 목록(272-4 자동 맞춤 뒤)."""
|
|
return [
|
|
{**s, "chainage_m": float(math.floor(s["chainage_m"] + 0.5))}
|
|
for s in data["stations"]
|
|
if s.get("kind") == "irregular"
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def ts(tmp_path_factory) -> dict:
|
|
source = tmp_path_factory.mktemp("s272") / "in.json"
|
|
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
payload = {
|
|
"pipes": PIPES,
|
|
"structures": STRUCTURES,
|
|
"types": _types(),
|
|
"server": SERVER,
|
|
"fresh": FRESH,
|
|
"nodes": fixture["profile_alignment"]["base_pvi"],
|
|
"gone": [s["chainage_m"] for s in fixture["stations"] if s.get("kind") == "irregular"],
|
|
"born": [s["chainage_m"] for s in _snapped(fixture)],
|
|
}
|
|
source.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
|
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
["node", str(HERE / "helper_272_1_station_list.cjs"), str(source)],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
cwd=str(ROOT),
|
|
check=False,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_planted_mirror(tmp_path: Path, ts) -> None:
|
|
path = tmp_path / "B05_Profile/route/structures.json"
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(
|
|
json.dumps({"revision": 1, "structures": STRUCTURES}, ensure_ascii=False),
|
|
encoding="utf-8",
|
|
)
|
|
server = [mark for mark, _label in resolve_extra_stations(tmp_path, parse_pipe_points(PIPES))]
|
|
assert sorted(server) == EXPECTED
|
|
assert ts["planted"] == EXPECTED
|
|
|
|
|
|
def test_one_list_mirror(tmp_path: Path, ts) -> None:
|
|
rel = "B06_Section/longitudinal/longitudinal.json"
|
|
path = tmp_path / rel
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(json.dumps({"stations": SERVER}, ensure_ascii=False), encoding="utf-8")
|
|
_merge_irregular_into_longitudinal(tmp_path, rel, FRESH)
|
|
merged = json.loads(path.read_text(encoding="utf-8"))["stations"]
|
|
assert [s["chainage_m"] for s in merged] == [0, 20, 35, 40, 60, 80, 100]
|
|
assert [s["chainage_m"] for s in ts["list"]] == [s["chainage_m"] for s in merged]
|
|
# 일반 측점은 글을 떼고 일반으로 · 같은 m 는 구조물 측점 하나(새 목록 것)
|
|
for side in (merged, ts["list"]):
|
|
by = {s["chainage_m"]: s for s in side}
|
|
assert by[40]["kind"] == "regular" and "structure" not in by[40] and "grid" not in by[40]
|
|
assert "structure" not in by[60]
|
|
assert by[100]["structure"] == "BOX암거"
|
|
assert 95.38 not in by
|
|
|
|
|
|
def test_moved_nodes_mirror(tmp_path: Path, ts) -> None:
|
|
"""옛 소수 → 정수 m 맞춤 뒤 꼭짓점 — 화면(저장 전 그림)과 서버([저장] 뒤)가 같음."""
|
|
rel = "B06_Section/longitudinal/longitudinal.json"
|
|
path = tmp_path / rel
|
|
path.parent.mkdir(parents=True)
|
|
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
path.write_text(json.dumps(fixture, ensure_ascii=False), encoding="utf-8")
|
|
_merge_irregular_into_longitudinal(tmp_path, rel, _snapped(fixture))
|
|
server = json.loads(path.read_text(encoding="utf-8"))["profile_alignment"]["base_pvi"]
|
|
assert [(n["chainage_m"], n["elevation_m"]) for n in ts["nodes"]] == [
|
|
(n["chainage_m"], n["elevation_m"]) for n in server
|
|
]
|