feat(B06): 소단 = 좌측 깊이 칸 숨김 · 횡단도 소단 고름 ▲▼ 만 · 측점 · 쪽마다 깊이 patch · 고름 조각 키 · 좌측 소단 상자 켬(194-1 · 194-2)

- 정의 「상세 내용」 = 폭만 · depth_m 표시 끔(옛 구간 값 = 기본값으로 읽음 · 새 기본 1 m)
- 짝: BermSpec 쪽마다 깊이 · side_berm ↔ sideBerm · 설계 결과 berm_depth_{side}_m 되싣기(파이썬 ↔ TS)
- 저장: patch berm_depth_{side}_m(≥ 0) · 재계산 살림 목록 짝(RD ↔ CR) · 세션 bermdepth
- 서버 [저장] 다시 셈 = 구조물 정본 소단 구간(load_berm_spans) + 측점 깊이 · 구간 지우면 소단 없음
- 고름: 소단 수평 단 = 높이만(▲ 0 에서 멈춤 · ▼ 사면 끝 앞 멈춤 · ◀▶ 막힘) · 사면 = 가로 · 노면 = 종단
- 고름을 조각 키(갈래 + 쪽 + 차례)로 기억 — 옮겨 다시 그려도 같은 조각
- 새 시험 test_194_1_berm_depth + helper · test_178_2 · test_54_26 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
2026-10-10 13:59:43 +09:00
co-authored by Claude Opus 5.5
parent e442777cc8
commit cb7a140c10
24 changed files with 1090 additions and 69 deletions
@@ -120,6 +120,70 @@ def load_wall_structures(project_root: Path) -> list[dict[str, Any]]:
return []
def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, float]]] | None:
"""구조물 정본의 소단 구간 `[(시작, 끝, 제원)]` — [저장] 서버 다시 셈의 소단 원천(194-1).
짝: TS `bermSpansFromStructures`(같은 규칙 — 폭 0 = 없음 · 깊이 빈 값 = 기본 1.0 · 0 미만 = 0).
못 읽으면 None(부르는 쪽이 저장분 `design.berm` 으로 물러섬).
"""
from common_util.common_util_cross_berm import (
BERM_DEFAULT_DEPTH_M,
BERM_DEFAULT_INTERVAL_M,
BERM_DEFAULT_SLOPE_DEG,
BERM_DEFAULT_WIDTH_M,
)
def number(options: dict[str, Any], key: str, fallback: float) -> float:
try:
value = float(options.get(key))
except (TypeError, ValueError):
return fallback
return value if math.isfinite(value) and value >= 0 else fallback
try:
spans: list[tuple[float, float, dict[str, float]]] = []
for item in load_structures(str(project_root))[1]:
if item.type_id != BERM_TYPE_ID:
continue
anchor = item.chainage_m if item.chainage_m is not None else item.start_m
start = item.start_m if item.start_m is not None else anchor
end = item.end_m if item.end_m is not None else anchor
if start is None or end is None:
continue
options = item.options or {}
width = number(options, "width_m", BERM_DEFAULT_WIDTH_M)
if width <= 0:
continue
raw = options.get("depth_m")
try:
depth = BERM_DEFAULT_DEPTH_M if raw in (None, "") else max(float(raw), 0.0)
except (TypeError, ValueError):
depth = BERM_DEFAULT_DEPTH_M
if not math.isfinite(depth):
depth = BERM_DEFAULT_DEPTH_M
spec = {
"width_m": width,
"interval_m": number(options, "interval_m", BERM_DEFAULT_INTERVAL_M),
"slope_deg": number(options, "slope_deg", BERM_DEFAULT_SLOPE_DEG),
"depth_m": depth,
}
spans.append((float(min(start, end)), float(max(start, end)), spec))
return spans
except Exception: # noqa: BLE001 — 정본을 못 읽으면 저장분으로 물러섬
logger.exception("B06 소단 구간을 읽지 못했습니다 (저장분 소단을 씀)")
return None
def berm_span_at(
spans: list[tuple[float, float, dict[str, float]]], chainage_m: float
) -> dict[str, float] | None:
"""그 측점을 덮는 소단 제원 — 겹치면 먼저 놓은 것(TS `bermSpecAt` 짝)."""
for start, end, spec in spans:
if start - 1e-6 <= chainage_m <= end + 1e-6:
return spec
return None
def attach_wall_structures(project_root: Path, cross_sections: list[dict[str, Any]]) -> int:
"""구간 안 측점의 횡단 dict에 `revetment` 키를 얹는다. 얹은 개수를 돌려준다.