Files
Aislo/resources/tester/test_133_1_analysis_terrain.py
T
eomsangdonandClaude Opus 5.5 99ec4b8efe feat(B05 평면 지도): 공용 도구줄 표 · 겹 줄 · 범례 · 지형 칩 붙임 · 노선추천 = 도구줄 미리보기(넷 · 하나만 · 끔) · [적용] · 추천 모달 · 좌측 칸 걷음 · 지형 바꿈 = 지형 칩 (PLAN 624-3 · 626-1 · 요구 530 · 531)
- 624-3 도구줄 = 공용 표 「b05」 · 아래 줄 = 공용 겹 줄(띠 · 올린 임도망도 숨김 · 처음 = 등고선 · 하천) · 범례 = 공용 legendRows(상태 글판 위)
- 노선 띠 300 m 자르기 = B05 자기 칸(등고선 · 하천 · 1 m)만 · 그 밖 겹 = 안 자르는 둘째 GL 칸(맨 밑 · 켠 겹 있을 때만 · GL 만) · 등고선 끔 = 등고 글도 숨김
- 626-1 추천 보기 = 도구줄 아이콘 + 아래 판(영선 · 토공 최소 · 기울기 최소 · 비중 계산 · 끔) — route_recommend 저장본(지금 설계 지형) 선만 · 다시 셈 · 쓰기 없음
- 지형 칩 → 확인창 → 새 POST /route/design-terrain(설계 지형만 바꿈 · 노선 저장 체인 다시 · 노선 · 초기값 · 추천 저장본 그대로 · 잠금 · 실패 되돌림)
- 걷음 — 추천 모달(_Recommend · _Help · _Map · CSS) · 좌측 「노선 추천」 칸 · saveRecommendedRoute · UI_RouteReset(서버 as_initial 길 · 시험은 남김)
- 부품 상자 — createPickMenu(아이콘 고르기 메뉴 · 판 = body fixed · ✓)
- 낡은 시험 고침 · 지움 — 모달 · 좌측 칸 · [적용] 화면 배선을 보던 시험(283 · 346_3 · 349 · 364_4 · 383_1 · 390_5 · 418 · 421 · 421_2 · 476_2 · 509_2 · 509_5 · 551_2 · 600_3 · b05_routeedit_initial · design_view_parity · modal_top · 133_1 · 133_2) · test_508_2 · helper_283 지움(모달 전용)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:30:49 +09:00

174 lines
7.0 KiB
Python

"""PLAN 133-1 — B05 계획노선 편집 분석 지형(라이다 · 수치지형도) 고르기.
라이다 있는 프로젝트 → 두 지형 다 고름 · sheet 고르면 dtm_sheet 로 표고 · 라이다 없는 프로젝트 →
고르기 안 보임(지형 하나) · 허용 목록 밖 거절 · 확정 params 는 안 바뀜.
"""
from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from pathlib import Path
import numpy as np
import pytest
from pydantic import ValidationError
from B05_Profile import B05_Profile_Router_Replan as replan
from B05_Profile import B05_Profile_Router_Terrain as terrain_router
from common_util.common_util_surface_confirmation import (
SHEET_ANALYSIS_FILTER,
analysis_terrain_params,
analysis_terrain_sources,
)
from common_util.common_util_surface_sampler import build_surface_sampler, check_surface_keys
ROOT = Path(__file__).resolve().parents[2]
LAS_PARAMS = {"source_filter": "csf", "method": "dtm", "smooth": True, "contour_interval_m": 1.0}
SHEET_PARAMS = {
"source_filter": SHEET_ANALYSIS_FILTER,
"method": "dtm",
"smooth": True,
"contour_interval_m": 1.0,
}
LAS_Z = 100.0
SHEET_Z = 250.0
def _dtm(path: Path, z: float) -> None:
x = np.arange(0.0, 11.0)
y = np.arange(0.0, 11.0)
np.savez(
path,
x=x,
y=y,
z=np.full((len(y), len(x)), z),
valid_mask=np.ones((len(y), len(x)), dtype=bool),
)
def _project(tmp_path: Path, *, las: bool, sheet: bool) -> Path:
models = tmp_path / "B04_PreProcess" / "models"
models.mkdir(parents=True)
if las:
_dtm(models / "dtm_csf_smooth.npz", LAS_Z)
if sheet: # B04 처럼 원본 · 평활 둘 다(평활 쪽을 읽음)
_dtm(models / f"dtm_{SHEET_ANALYSIS_FILTER}.npz", SHEET_Z - 1.0)
_dtm(models / f"dtm_{SHEET_ANALYSIS_FILTER}_smooth.npz", SHEET_Z)
return tmp_path
def _patch_db(monkeypatch, project_root: Path, params: dict) -> None:
class _Pool:
@asynccontextmanager
async def acquire(self):
yield object()
async def stored(_connection, _project_id):
return "x"
async def confirmed(_connection, _project_id):
return dict(params)
monkeypatch.setattr(terrain_router, "get_db_pool", lambda: _Pool())
monkeypatch.setattr(terrain_router, "get_project_storage_relative_path", stored)
monkeypatch.setattr(terrain_router, "get_surface_confirmation_params", confirmed)
monkeypatch.setattr(terrain_router, "resolve_stored_project_path", lambda _s: project_root)
async def no_model(_connection, _project_id, _params): # 격자 받을 자리(344-1)는 따로 시험
return None
monkeypatch.setattr(terrain_router, "_design_model", no_model)
PID = "1bb0f876-983f-4ca5-9cc7-f59ee1f77e0a"
def _elevations(terrain: str) -> dict:
request = terrain_router.ElevationRequest(points=[(5.0, 5.0)], terrain=terrain)
return asyncio.run(terrain_router.read_route_elevations(PID, request))
def test_라이다_있는_프로젝트_두_지형_다_고름(tmp_path, monkeypatch):
root = _project(tmp_path, las=True, sheet=True)
models = root / "B04_PreProcess" / "models"
assert analysis_terrain_sources(LAS_PARAMS, models) == ["las", "sheet"]
_patch_db(monkeypatch, root, LAS_PARAMS)
import B04_PreProcess.B04_PreProcess_Service as service
async def models_in_db(*args): # sheet 고르기 = DB 완료 모델도 있어야(505-2 5d)
params = {"source_filter": SHEET_ANALYSIS_FILTER}
return [{"id": 2, "status": "COMPLETE", "model_type": "DTM", "generation_params": params}]
monkeypatch.setattr(service, "list_surface_models", models_in_db)
answer = asyncio.run(terrain_router.read_terrain_sources(PID))
assert answer["sources"] == ["las", "sheet"]
def test_sheet_고르면_dtm_sheet_로_표고(tmp_path, monkeypatch):
root = _project(tmp_path, las=True, sheet=True)
_patch_db(monkeypatch, root, LAS_PARAMS)
assert _elevations("las")["z"] == [LAS_Z]
assert _elevations("sheet")["z"] == [SHEET_Z]
# 기본값 = 지금 설계 지형(확정 지표면 · 133-2)
request = terrain_router.ElevationRequest(points=[(5.0, 5.0)])
assert request.terrain is None
def test_확정_params_는_안_바뀜():
params = dict(LAS_PARAMS)
picked = analysis_terrain_params(params, "sheet")
assert picked["source_filter"] == SHEET_ANALYSIS_FILTER == "sheet_laplace"
assert picked["method"] == "dtm" and picked["smooth"] is True
assert params == LAS_PARAMS
assert analysis_terrain_params(params, "las") is params
def test_라이다_없는_프로젝트_고르기_안_보임(tmp_path, monkeypatch):
root = _project(tmp_path, las=False, sheet=True)
models = root / "B04_PreProcess" / "models"
assert analysis_terrain_sources(SHEET_PARAMS, models) == ["sheet"]
# 확정이 이미 수치지형도 — 어느 값을 받아도 같은 서피스
assert analysis_terrain_params(SHEET_PARAMS, "sheet") is SHEET_PARAMS
_patch_db(monkeypatch, root, SHEET_PARAMS)
assert asyncio.run(terrain_router.read_terrain_sources(PID))["sources"] == ["sheet"]
# 허용 목록에 sheet_* 가 들어가 LAS 없는 프로젝트 지반고가 읽힘(54-22 뒤 막혀 있던 길)
assert _elevations("las")["z"] == [SHEET_Z]
def test_수치지형도_서피스_없으면_라이다만(tmp_path):
root = _project(tmp_path, las=True, sheet=False)
assert analysis_terrain_sources(LAS_PARAMS, root / "B04_PreProcess" / "models") == ["las"]
@pytest.mark.parametrize("bad", ["lidar", "SHEET", "../x", ""])
def test_허용_목록_밖_거절(bad):
with pytest.raises(ValidationError):
terrain_router.ElevationRequest(points=[(0.0, 0.0)], terrain=bad)
with pytest.raises(ValidationError):
replan.RouteInitialRequest(terrain=bad)
@pytest.mark.parametrize("bad", ["sheet", "sheet_../x", "sheet_LAPLACE", "dtm_sheet_laplace"])
def test_sheet_필터도_허용_목록_검사(bad, tmp_path):
with pytest.raises(ValueError):
check_surface_keys(bad)
with pytest.raises(ValueError):
build_surface_sampler(tmp_path, bad, "dtm", True)
check_surface_keys("sheet_laplace")
def test_화면_고르기_배선():
page = ROOT / "B05_Profile"
chrome = (page / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
main = (page / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
api = (page / "B05_Profile_Api_Replan.ts").read_text(encoding="utf-8")
initial_api = (page / "B05_Profile_Api_RouteInitial.ts").read_text(encoding="utf-8")
# 626-1 — 평면 도구줄 지형 칩(공용 · 고르기 = 확인창 → 설계 지형 바꿈) · 옛 이름 칩 · 추천 창 고르기는 걷음
assert 'data-slot="terrain-name"' not in chrome and "terrain: terrain.root" in chrome
assert "chrome.terrain.load(sources)" in main
assert "analysisTerrain" not in api and "analysisTerrain" not in initial_api
assert "body: JSON.stringify(request)," in initial_api
replan_fn = api.split("export async function replanRoute")[1].split("export ")[0]
assert "terrain" not in replan_fn