diff --git a/B05_Profile/B05_Profile_Route_Save.py b/B05_Profile/B05_Profile_Route_Save.py index 75d225105..8d71fba47 100644 --- a/B05_Profile/B05_Profile_Route_Save.py +++ b/B05_Profile/B05_Profile_Route_Save.py @@ -176,6 +176,7 @@ def followed_pipes( """관 따라감 + 새 세류선 교차점 기본 관(저장 안 함) → (짝, 교차 수, 새 노선 정점) — 체인 · 추천 초기 종단(476) 한 벌.""" from B04_PreProcess.B04_PreProcess_Engine_Watershed_Analyze import _base_pipes + from B04_PreProcess.B04_PreProcess_Router_Basins import _apply_new_defaults from common_util.common_util_drainage_pipes import fill_pipe_coordinates vertices = vertices_of([(p["x"], p["y"]) for p in points]) @@ -183,6 +184,8 @@ def followed_pipes( pairs = follow_pipes(old_pipes, vertices) total = float(vertices[-1].chainage_m) crossing = add_crossing_pipes(pairs, _base_pipes(vertices, streams), old_xy, total) + # 새 교차 관만 B04 와 같은 등록부 기본값(54-39 · 494-2) — 옛 관 빈칸은 미확정 그대로 + _apply_new_defaults([pipe for index, pipe in pairs if index < 0]) return pairs, crossing, vertices diff --git a/resources/tester/test_283_route_change_reset.py b/resources/tester/test_283_route_change_reset.py index 4d61925b6..bcd6884f2 100644 --- a/resources/tester/test_283_route_change_reset.py +++ b/resources/tester/test_283_route_change_reset.py @@ -12,7 +12,7 @@ ⑥ 추천 [적용] 실패 — 초기값 사본(`initial_snapshot`)까지 되돌림 ⑦ 떠 두기 중 뒤 미리 셈이 파일을 지움(`_derived` 원자 쓰기 · 정리) — 건너뜀(옛 = 500 FileNotFoundError) -492 코드 = ① ④(손 고침) ⑤ 실패 · 옛 떠 두기 = ⑦ 실패. +492 코드 = ① ④(손 고침) ⑤ 실패 · 옛 떠 두기 = ⑦ 실패 · 새 교차 관 = B04 새 관 기본값(494-2). 체인 · API 는 346-3 가짜 세계(`chain_world` · `api_world`)를 빌려 씀 — 파일은 임시 폴더만. @@ -43,8 +43,10 @@ import B04_PreProcess.B04_PreProcess_Engine_Watershed_Analyze as analyze # noqa import B05_Profile.B05_Profile_Route_Save as save # noqa: E402 import B05_Profile.B05_Profile_Router_Replan as replan # noqa: E402 import B06_Section.B06_Section_Repository as b06_repo # noqa: E402 +from B04_PreProcess.B04_PreProcess_Router_Basins import _apply_new_defaults # noqa: E402 from B05_Profile.B05_Profile_Plus_Stations import load_plus_stations, save_plus_stations # noqa: E402 from B05_Profile.B05_Profile_Structures_Repository import load_structures # noqa: E402 +from common_util.common_util_drainage_pipes import PipePoint # noqa: E402 from common_util.common_util_json import atomic_write_json # noqa: E402 FIXTURE = HERE / "fixtures" / "req283_sub8_route.json" @@ -70,6 +72,13 @@ WALL = { CROSSING = SimpleNamespace(chainage_m=158.31, x=150.0, y=30.0) +def _new_pipe_options() -> dict: + """새 교차 관 옵션 = B04 유역 길의 새 관 기본값(494-2 · 54-39).""" + pipe = PipePoint(chainage_m=0.0, source="stream") + _apply_new_defaults([pipe]) + return pipe.options + + def _side(xy, x: float, y: float) -> float: from shapely.geometry import LineString, Point @@ -137,7 +146,7 @@ def _assert_reset(world, result: dict, cross_seen: list) -> None: assert load_plus_stations(world.root) == [] points = json.loads(world.pipes_file.read_text("utf-8"))["points"] assert len(points) == 1, points # 옛 사용자 관 버림 · 새 교차 관만 - assert points[0]["source"] == "stream" and not points[0].get("options") + assert points[0]["source"] == "stream" and points[0]["options"] == _new_pipe_options() assert _side(base.NEW, points[0]["x"], points[0]["y"]) < 1e-6 assert "profile" not in world.order # 계획고 옮김 없음 = 1차 계획선 assert cross_seen == [[]] # 횡단 손 입력 안 넘김 @@ -257,7 +266,7 @@ def test_검증_sub8_추천_적용은_교차_하나(sub8, chain_world, cross_see points = json.loads(chain_world.pipes_file.read_text("utf-8"))["points"] assert [round(p["chainage_m"]) for p in points] == [388] assert _side(sub8["new_xy"], points[0]["x"], points[0]["y"]) < 0.01 - assert not points[0].get("options") + assert points[0]["options"] == _new_pipe_options() # 494-2 새 교차 관 = 기본값 assert result["route_reset"]["dropped"]["pipes"] == 1 diff --git a/resources/tester/test_494_2_crossing_pipe_defaults.py b/resources/tester/test_494_2_crossing_pipe_defaults.py new file mode 100644 index 000000000..fe4717358 --- /dev/null +++ b/resources/tester/test_494_2_crossing_pipe_defaults.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +"""494-2 노선 바뀜 길의 새 세류선 교차 관 = B04 와 같은 등록부 기본값(54-39). + +못박는 것 + - 손 고침 [저장] · 추천 [적용](체인 `_place_pipes`) · 추천 초기 종단(`reset_facilities`) 셋 다 + `followed_pipes` 를 지나 새 교차 관이 B04 `_apply_new_defaults` 와 같은 옵션을 받음. + - 이미 저장된 빈 관 · 사용자가 옵션을 고른 관은 그대로(10-05 「기존 빈칸 = 미확정」). +""" + +from __future__ import annotations + +import asyncio +import sys +from pathlib import Path +from types import SimpleNamespace + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +import B04_PreProcess.B04_PreProcess_Engine_Watershed_Analyze as analyze # noqa: E402 +import B04_PreProcess.B04_PreProcess_Router_Watershed as watershed # noqa: E402 +import B05_Profile.B05_Profile_Engine_InitialProfile as initial_profile # noqa: E402 +import B05_Profile.B05_Profile_Route_Save as save # noqa: E402 +import common_util.common_util_drainage_pipes as drainage_pipes # noqa: E402 +from B04_PreProcess.B04_PreProcess_Router_Basins import _apply_new_defaults # noqa: E402 +from common_util.common_util_drainage_pipes import PIPE_SOURCE_STREAM, PipePoint # noqa: E402 + +OLD = [(0.0, 0.0), (100.0, 0.0), (200.0, 0.0), (300.0, 0.0), (400.0, 0.0)] +NEW = [(0.0, 0.0), (100.0, 0.0), (200.0, 60.0), (300.0, 0.0), (400.0, 0.0)] +# 새 노선 북쪽 휨 위(옛 노선에서 먼) 세류선 교차 하나 +CROSSING = SimpleNamespace(chainage_m=150.4, x=150.0, y=30.0) + + +def _b04_defaults() -> dict: + """B04 유역 길이 새 관에 주는 옵션 — 비교 기준.""" + pipe = PipePoint(chainage_m=150.0, source=PIPE_SOURCE_STREAM) + _apply_new_defaults([pipe]) + return pipe.options + + +def _old_pipes() -> list[PipePoint]: + return [ + PipePoint(chainage_m=50.0, x=50.0, y=0.0), # 저장된 빈 관 — 미확정 그대로 + PipePoint(chainage_m=350.0, x=350.0, y=0.0, options={"pipe_kind": "흄관"}), # 사용자 고름 + ] + + +def _points(xy) -> list[dict[str, float]]: + return [{"x": x, "y": y} for x, y in xy] + + +def _fake_crossings(monkeypatch) -> None: + monkeypatch.setattr(analyze, "_base_pipes", lambda vertices, streams: [CROSSING]) + + +def _check(pipes: list[PipePoint], kept: bool) -> None: + new = [pipe for pipe in pipes if abs(pipe.chainage_m - 150.0) < 1] + assert len(new) == 1 and new[0].options == _b04_defaults() + assert new[0].options.get("pipe_diameter_mm") # 관경 빈칸 아님(횡단 관 그림 · 54-39) + if kept: # 누가거리 순 = 빈 관 · 새 관 · 사용자 관 + assert [pipe.options for pipe in pipes[::2]] == [None, {"pipe_kind": "흄관"}] + + +def test_손_고침_저장_followed_pipes(monkeypatch): + _fake_crossings(monkeypatch) + old = _old_pipes() + pairs, crossing, _ = save.followed_pipes(old, [list(p) for p in OLD], _points(NEW), []) + assert crossing == 1 + _check([pipe for _, pipe in pairs], kept=True) + assert old[0].options is None # 옛 목록 그대로 + + +def test_추천_적용_체인_관_저장(monkeypatch): + """체인 [적용](492 초기화) — 옛 관 버림 · 새 교차 관 = 기본값으로 정본 저장.""" + _fake_crossings(monkeypatch) + written: list = [] + + async def prepare(_project_id): + return {"streams": []} + + monkeypatch.setattr(watershed, "_prepare", prepare) + monkeypatch.setattr( + drainage_pipes, "save_pipe_points", lambda path, sig, pipes, verts: written.extend(pipes) + ) + pairs, counts = asyncio.run(save._place_pipes(None, "unused", [], [], _points(NEW))) + assert counts["crossing"] == 1 + _check(written, kept=False) + + +def test_추천_초기_종단_reset_facilities(monkeypatch): + _fake_crossings(monkeypatch) + pipes, structures, plus = initial_profile.reset_facilities(NEW, []) + assert structures == [] and plus == [] + _check(pipes, kept=False)