⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
136 lines
5.5 KiB
Python
136 lines
5.5 KiB
Python
"""배수 추천 구조물·관경과 자동 배치 주입 (PLAN 2026-08-17 「4」).
|
|
|
|
추천 근거는 **유량(유효직경)뿐**이다 — 계곡 횡단경사·하천 차수는 지형 계산이 필요해
|
|
이번 범위에서 뺐고 화면이 "현장 확인"으로 안내한다(사용자 확정).
|
|
|
|
D ≤ 1,500㎜ 배관 (레지스트리 선택지로 스냅, 하한 800㎜)
|
|
1,500 < D ≤ 2,000 BOX암거 후보
|
|
D > 2,000㎜ 세월교·물넘이 검토
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from B04_PreProcess.B04_PreProcess_Router_Basins import _apply_recommendations
|
|
from B05_Profile.B05_Profile_Structures_Schema import load_structure_types
|
|
from common_util.common_util_drainage_detail import (
|
|
DrainageDetail,
|
|
WatershedBasin,
|
|
recommend_structure,
|
|
)
|
|
from common_util.common_util_drainage_pipes import (
|
|
PIPE_FACILITY_BOX,
|
|
PIPE_FACILITY_FORD_BRIDGE,
|
|
PIPE_FACILITY_PIPE,
|
|
PIPE_SOURCE_STREAM,
|
|
PIPE_SOURCE_USER,
|
|
PipePoint,
|
|
)
|
|
from config.config_system import DRAINAGE_RECOMMEND_DIAMETERS_MM
|
|
|
|
|
|
# ── 추천 판정 ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"diameter_mm, expected",
|
|
[
|
|
(None, (PIPE_FACILITY_PIPE, None)), # 강우량표 없음 — 관경 미정
|
|
(300.0, (PIPE_FACILITY_PIPE, 800)), # 하한 800㎜ 아래로는 안 내린다
|
|
(800.0, (PIPE_FACILITY_PIPE, 800)),
|
|
(800.1, (PIPE_FACILITY_PIPE, 1000)), # 규격을 넘으면 바로 위 규격
|
|
(1000.0, (PIPE_FACILITY_PIPE, 1000)),
|
|
(1100.0, (PIPE_FACILITY_PIPE, 1200)),
|
|
(1500.0, (PIPE_FACILITY_PIPE, 1500)), # 경계값은 아직 배관
|
|
(1500.1, (PIPE_FACILITY_BOX, None)), # 초과 → BOX암거 후보
|
|
(2000.0, (PIPE_FACILITY_BOX, None)), # 경계값은 아직 BOX암거
|
|
(2000.1, (PIPE_FACILITY_FORD_BRIDGE, None)), # 관 최대 규격 초과
|
|
],
|
|
)
|
|
def test_recommend_structure_thresholds(diameter_mm, expected):
|
|
assert recommend_structure(diameter_mm) == expected
|
|
|
|
|
|
def test_recommended_sizes_match_registry_choices():
|
|
"""추천 관경은 폼에서 고를 수 있어야 한다 — 레지스트리 선택지와 같은 목록."""
|
|
by_id = {item.type_id: item for item in load_structure_types()}
|
|
options = {option.key: option for option in by_id["pipe"].options}
|
|
choices = [int(value) for value in options["pipe_diameter_mm"].choices]
|
|
assert list(DRAINAGE_RECOMMEND_DIAMETERS_MM) == choices
|
|
|
|
|
|
# ── 자동 배치 주입 ─────────────────────────────────────────────────────────
|
|
|
|
|
|
def _detail(*basins: WatershedBasin) -> DrainageDetail:
|
|
detail = DrainageDetail.__new__(DrainageDetail)
|
|
detail.basins = list(basins)
|
|
return detail
|
|
|
|
|
|
def _basin(chainage: float, facility: str, diameter: int | None) -> WatershedBasin:
|
|
return WatershedBasin(
|
|
index=1,
|
|
chainage_m=chainage,
|
|
outlet_x=0.0,
|
|
outlet_y=0.0,
|
|
recommended_facility=facility,
|
|
recommended_diameter_mm=diameter,
|
|
)
|
|
|
|
|
|
def test_auto_pipe_gets_recommended_diameter():
|
|
"""자동 배치 관은 폼을 열지 않아도 유역 유량에 맞는 관경을 갖는다."""
|
|
points = [PipePoint(chainage_m=100.0, source=PIPE_SOURCE_STREAM)]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_PIPE, 1200)), points)
|
|
assert points[0].options == {"pipe_diameter_mm": 1200}
|
|
assert points[0].facility == PIPE_FACILITY_PIPE
|
|
|
|
|
|
def test_auto_point_switches_facility_to_box():
|
|
"""추천이 BOX암거면 시설 종류까지 바꾼다(관경은 붙이지 않는다)."""
|
|
points = [PipePoint(chainage_m=100.0, source=PIPE_SOURCE_STREAM)]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_BOX, None)), points)
|
|
assert points[0].facility == PIPE_FACILITY_BOX
|
|
assert points[0].options is None
|
|
|
|
|
|
def test_user_placed_point_is_never_overwritten():
|
|
"""사용자가 직접 놓거나 옮긴 관은 재계산이 건드리지 않는다."""
|
|
points = [PipePoint(chainage_m=100.0, source=PIPE_SOURCE_USER)]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_BOX, None)), points)
|
|
assert points[0].facility == PIPE_FACILITY_PIPE
|
|
assert points[0].options is None
|
|
|
|
|
|
def test_existing_option_is_kept():
|
|
"""설계자가 고른 관경을 추천이 되돌리면 안 된다 — 빈칸만 채운다."""
|
|
points = [
|
|
PipePoint(
|
|
chainage_m=100.0,
|
|
source=PIPE_SOURCE_STREAM,
|
|
options={"pipe_diameter_mm": 800},
|
|
)
|
|
]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_PIPE, 1500)), points)
|
|
assert points[0].options == {"pipe_diameter_mm": 800}
|
|
|
|
|
|
def test_already_chosen_facility_is_kept():
|
|
"""이미 다른 시설로 바꿔 둔 지점은 추천이 되돌리지 않는다."""
|
|
points = [
|
|
PipePoint(
|
|
chainage_m=100.0,
|
|
source=PIPE_SOURCE_STREAM,
|
|
facility=PIPE_FACILITY_FORD_BRIDGE,
|
|
)
|
|
]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_BOX, None)), points)
|
|
assert points[0].facility == PIPE_FACILITY_FORD_BRIDGE
|
|
|
|
|
|
def test_point_without_basin_is_untouched():
|
|
"""담당 유역이 없는 관(유역 미산출)은 그대로 둔다."""
|
|
points = [PipePoint(chainage_m=500.0, source=PIPE_SOURCE_STREAM)]
|
|
_apply_recommendations(_detail(_basin(100.0, PIPE_FACILITY_PIPE, 1200)), points)
|
|
assert points[0].options is None
|