Files
Aislo/resources/tester/test_b05_pipe_clearance_switch.py
T
eomsangdonandClaude Opus 5.5 11c75f022a refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌
- 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음)
- 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음
- 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나
- 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정
- 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400
- 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명
- 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것)
- 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터)
- 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:35:12 +09:00

169 lines
6.6 KiB
Python

"""B05 횡단배수 최소고 강제 스위치(2026-09-01 사용자 지시).
배수 자리가 종단 계획고를 잡아당겨 제어가 어렵다는 지적에 따라, 최소고 적용을
`enforce_pipe_clearance` 하나로 여닫게 했다. **기본은 해제**다. 여기서는
① 기본이 해제인지
② 요청·저장값이 스위치를 켜는지
③ 켜고 끔에 따라 계획선 표고가 실제로 달라지는지 (산식은 그대로)
④ 화면(TS) 편집 가드도 같은 스위치를 보는지
를 확인한다. 산식 자체(관경+토피 등)는 `test_b05_min_cover_rules.py`가 잠근다.
"""
import sys
from pathlib import Path
import numpy as np
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402
from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402
design_pipe_anchored_profile,
)
from B05_Profile.B05_Profile_Schema import RouteSolveRequest # noqa: E402
def _options(**kwargs):
return resolve_grade_options("trunk", **kwargs)
# ── ① 기본 해제 ──────────────────────────────────────────────────────────────
def test_default_is_released():
"""기본은 해제 — 자동 전처리가 배수 자리마다 계획고를 들어 올리지 않는다."""
assert _options().enforce_pipe_clearance is False
def test_as_dict_carries_the_switch():
"""저장·복원 경로(criteria/grade_options)에 스위치가 실린다."""
assert _options().as_dict()["enforce_pipe_clearance"] is False
assert (
_options(requested={"enforce_pipe_clearance": True}).as_dict()["enforce_pipe_clearance"]
is True
)
# ── ② 요청 → 저장 → 기본 순서 ────────────────────────────────────────────────
def test_requested_turns_it_on():
assert _options(requested={"enforce_pipe_clearance": True}).enforce_pipe_clearance is True
def test_stored_is_used_when_request_is_silent():
"""재생성처럼 요청이 비어 오는 경로는 확정 당시 저장값을 따른다."""
assert _options(stored={"enforce_pipe_clearance": True}).enforce_pipe_clearance is True
def test_request_wins_over_stored():
"""사용자가 방금 끈 것이 옛 저장값을 이긴다."""
resolved = _options(
requested={"enforce_pipe_clearance": False},
stored={"enforce_pipe_clearance": True},
)
assert resolved.enforce_pipe_clearance is False
def test_schema_defaults_to_none_and_ships_the_flag():
"""스키마 기본은 None(미지정) — 저장값을 덮어쓰지 않는다."""
request = RouteSolveRequest(
filter_key="ptd",
bp={"x": 0.0, "y": 0.0},
ep={"x": 100.0, "y": 0.0},
)
assert request.enforce_pipe_clearance is None
assert "enforce_pipe_clearance" in request.grade_options()
# ── ③ 계획선 표고가 실제로 달라진다 ──────────────────────────────────────────
def _flat_longitudinal(length_m: float = 400.0, step: float = 20.0) -> dict:
"""지반고가 일정한 종단 — 최소고 적용 여부만 표고 차이로 드러난다."""
chainages = np.arange(0.0, length_m + step, step)
return {
"length_m": float(length_m),
"samples": [
{"chainage_m": float(c), "elevation_m": 100.0, "valid": True} for c in chainages
],
"stations": [
{"chainage_m": float(c), "kind": "regular", "elevation_m": 100.0} for c in chainages
],
}
def _elevation_at(profile: dict, chainage_m: float) -> float:
samples = profile["samples"]
xs = [float(s["chainage_m"]) for s in samples]
ys = [float(s["elevation_m"]) for s in samples]
return float(np.interp(chainage_m, xs, ys))
@pytest.mark.parametrize("anchor_m", [200.0])
def test_released_profile_stays_on_the_ground(anchor_m):
"""해제(기본): 배관 자리 계획고가 지반고를 그대로 통과한다."""
_, entry = design_pipe_anchored_profile(
_flat_longitudinal(),
_options(),
[anchor_m],
station_interval_m=20.0,
pipe_clearances=None,
)
assert _elevation_at(entry, anchor_m) == pytest.approx(100.0, abs=0.05)
@pytest.mark.parametrize("anchor_m,clearance_m", [(200.0, 1.5)])
def test_enforced_profile_is_lifted_by_the_clearance(anchor_m, clearance_m):
"""강제: 배관 자리 계획고가 시설 여유만큼 들린다(Ø1000 → +1.5m)."""
_, entry = design_pipe_anchored_profile(
_flat_longitudinal(),
_options(requested={"enforce_pipe_clearance": True}),
[anchor_m],
station_interval_m=20.0,
pipe_clearances={anchor_m: clearance_m},
)
assert _elevation_at(entry, anchor_m) == pytest.approx(100.0 + clearance_m, abs=0.05)
def test_criteria_round_trip_keeps_the_switch():
"""계획선에 기록된 criteria로 스위치가 되살아난다(재편집 경로)."""
_, entry = design_pipe_anchored_profile(
_flat_longitudinal(),
_options(requested={"enforce_pipe_clearance": True}),
[200.0],
station_interval_m=20.0,
pipe_clearances={200.0: 1.5},
)
assert entry["criteria"]["enforce_pipe_clearance"] is True
# ── ④ 화면 가드도 같은 스위치를 본다 ─────────────────────────────────────────
RENDER_SOURCE = (PROJECT_ROOT / "B05_Profile" / "B05_Profile_UI_Profile_Render.ts").read_text(
encoding="utf-8"
)
SECTIONS_SOURCE = (PROJECT_ROOT / "B05_Profile" / "B05_Profile_Engine_Sections.py").read_text(
encoding="utf-8"
)
def test_edit_guard_is_conditional():
"""편집 차단 가드가 스위치를 먼저 본다 — 해제면 막지 않는다.
가드는 2026-09-02 에 `_Profile_MinCover.blocksMinCover` 로 옮겼다(모든 편집이
지나는 `applyEdits` 한 곳에서 부른다). 스위치는 그 인자 `enforced` 로 들어온다.
"""
guard = (PROJECT_ROOT / "B05_Profile" / "B05_Profile_UI_Profile_MinCover.ts").read_text(
encoding="utf-8"
)
assert "if (!base || !alignment || !enforced || !targets.length) return false;" in guard
def test_section_generation_passes_clearances_only_when_enabled():
"""자동설계는 켜졌을 때만 여유값을 넘긴다."""
assert "grade_options.enforce_pipe_clearance" in SECTIONS_SOURCE