feat(횡단): 소단을 미리보기·재계산에 실어 화면에 계단이 서게 함 (계획서 3-9)

계단이 값에만 있던 것을 화면까지 연결함.

배선 — 세션 열쇠 `berm`(측점키 → 폭·간격·기울기)을 등록표에 두고,
`readBermSession` 으로 읽어 ① 브라우저 재계산(`refreshCrossDesigns`)과
② 서버 미리보기(`cross-design/preview` 의 `berms`) 양쪽에 실음.
암 경계선 오프셋과 같은 길이라 「계획선을 고치면 계단이 사라지는」 일이 없음.

실화면 확인(8001·5174, `/api/health` `stale:false`) — 측점 4120.0m 에 소단을 놓고
계획고를 한 칸 올렸다 내려 전 구간 재계산을 태움.
· 폭 0.5m · 간격 3.0m → 절토 6.84 → 8.10㎡ (토사 2.42→3.24 · 암 4.42→4.86)
· 폭 1.0m · 간격 2.0m → 절토 6.84 → 12.96㎡, 횡단도에 **계단이 눈으로 보임**
· 소단을 안 놓은 옆 측점(4100.0m)은 3.77㎡ 그대로 — 놓은 곳만 달라짐
· 되돌린 뒤 6.84㎡ 로 복귀. [저장]·[확정] 안 눌렀으므로 정본은 그대로.

`cut_slope_segments` 신설 — 절토 사면을 경사 구간별로 쪼갠 목록(파이썬·TS 짝).
법정 경사 검사가 읽을 값임(다른 창 요청). 소단이 서면 사면 전체를 하나로 재는
「실효 경사」가 완만해져 **위반이 사라진 것처럼** 보이므로(폭 1.0·간격 2 이면 설계 1:1 이
실효 1:1.71), 검사는 소단을 뺀 구간 자체를 봐야 함. 실측 — 소단을 놓아도 구간별 경사비는
1.0 그대로 나옴. 평탄부(소단)와 지반 만난 뒤 구간은 싣지 않음.

자체검증 — 거울 시험에 사면 구간 대조를 더해 파이썬·TS 일치 확인.
전체 539 passed · 18 skipped. TS 타입 검사 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 15:28:16 +09:00
co-authored by Claude Opus 5
parent 5a6617f278
commit 061d550e55
11 changed files with 203 additions and 4 deletions
+19
View File
@@ -10,6 +10,12 @@ from B05_Profile.B05_Profile_Structures_Repository import load_structures
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
from B06_Section.B06_Section_Engine_Culvert import load_culvert_sets
from B06_Section.B06_Section_Engine_Design import compute_cross_design, curve_widening_args
from common_util.common_util_cross_berm import (
BERM_DEFAULT_INTERVAL_M,
BERM_DEFAULT_SLOPE_DEG,
BERM_DEFAULT_WIDTH_M,
BermSpec,
)
from common_util.common_util_route_profile import design_elevation_from_longitudinal
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
@@ -343,6 +349,7 @@ def recompute_designs_for_alignment(
standard: dict[str, Any] | None,
rock_boundary_offsets: dict[str, float] | None = None,
project_root: Path | None = None,
berms: dict[str, dict[str, float]] | None = None,
) -> None:
modes = default_section_modes(longitudinal)
pavement = pavement_suggestions(longitudinal)
@@ -358,6 +365,17 @@ def recompute_designs_for_alignment(
session_offsets[round(float(raw_key), 3)] = float(offset)
except (TypeError, ValueError):
continue
# 측점별 소단 제원 — 값이 없는 측점은 소단 없음(종전 설계 그대로).
session_berms: dict[float, BermSpec] = {}
for raw_key, spec in (berms or {}).items():
try:
session_berms[round(float(raw_key), 3)] = BermSpec(
width_m=float(spec.get("width_m", BERM_DEFAULT_WIDTH_M)),
interval_m=float(spec.get("interval_m", BERM_DEFAULT_INTERVAL_M)),
slope_deg=float(spec.get("slope_deg", BERM_DEFAULT_SLOPE_DEG)),
)
except (TypeError, ValueError, AttributeError):
continue
for section in cross_sections:
chainage = float(section.get("chainage_m", 0.0))
key = round(chainage, 3)
@@ -380,6 +398,7 @@ def recompute_designs_for_alignment(
two_stage_slope=bool(stored.get("two_stage_slope", True)),
ditch_enabled=stored.get("ditch_enabled"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
berm=session_berms.get(key),
**curve_widening_args(section),
)
except (ValueError, KeyError):