feat(B06): 절토 경사를 사용자가 넣게 — 카드에 암 절토각(°), 전체는 표준 설정

사용자 지시(2026-09-07): 「대신 경고부분은 삭제해주고 대신 각도를 사용자가 넣을수 있게 반영.
전체 공통으로 변경하는 경우에는 기본값 지정으로 하면 되지만 횡단도 하나만 변경하는 폼은
가져야함 / 개별 횡단도에는 암 절토 각도의 개별 수정 가능해야함」

**화면** — 암 측점 카드 아래에 「암 절토 68.2° ↺」 칸이 섬. 값을 넣으면 그 측점만 사면이
새 경사로 다시 그려지고 절토량도 따라 바뀜. ↺ 는 표준값으로 되돌림. 전체를 바꾸는 자리는
종전대로 좌측 [표준 횡단면 설정]임.

**계산에 넣는 자리는 한 곳씩** — 파이썬 `compute_cross_design(cut_slope_ratio=…)` ·
TS `computeCrossDesign({cutSlopeRatio})` 의 **그룹을 만든 바로 뒤**에서 경사비만 갈아 끼움.
부르는 쪽 9곳에서 표준값을 측점마다 복제하는 방식은 안 씀(한 곳만 빠져도 값이 조용히
사라지는 실패군). 기하·소단 코드는 한 줄도 안 건드림 — 25 확인대로 소단이 그 값을 읽어
서므로 **위치·개수가 새 경사를 저절로 따라옴**.

⚠ **경사는 나르는 값이 아니라 기하 입력임** — 계산 뒤에 키만 베껴 붙이면 설계선은 옛 경사로
그려지고 숫자만 새것이 됨(소단에서 겪은 자리). 그래서 재계산 세 경로(포장 강제·세월교 하강·
선형 재계산)와 브라우저 재계산·서버 프리뷰 **모두 계산 인자로** 넘김.

**되돌리기는 0 을 남김** — 세션에서 지우기만 하면 정본에 남은 옛 사용자 값이 되살아나
표준으로 못 돌아감. 0 = 「표준값을 씀」.

값의 길 — 세션 `cutslope`(등록표 한 줄) → 카드 입력 → [저장]·[확정]에서 `cross_patches`
(`design.cut_slope_ratio_user`) → 정본. `USER_TOUCHED_KEYS` 양쪽에 넣어 **표준을 바꿔도
개별로 고친 측점은 그대로** 둠(사용자 원문 끝줄).

시험 — 새 7건(넣은 경사가 실제로 그려짐 · 무릎 위 토사 경사는 그대로 · 0 은 되돌림 ·
재계산에도 남음 · **계산 전에 넘어감** · 각도↔경사비 · 칸은 암 측점에만),
거울 시험에 「암 절토 경사를 측점에서 바꿈」 한 갈래 추가, 7-3 회귀 한 줄 추가.
전체 **483 passed · 17 skipped**.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 17:52:06 +09:00
co-authored by Claude Opus 5
parent cc428d7e12
commit 6b9c115baa
17 changed files with 391 additions and 3 deletions
+30
View File
@@ -140,6 +140,9 @@ USER_TOUCHED_KEYS = (
"revet_follow_grade",
# 소단 제원 — 사용자가 구간에 놓은 값이라 다시 계산해도 살려 둔다(계획서 3-9).
"berm",
# 측점별 암 절토 경사 — 표준 횡단면 설정을 바꿔도 개별로 고친 측점은 그대로 둔다
# (2026-09-07 사용자 확정: 「사용자가 기본값을 사용하지 않는 값들은 변경되면 안됨」).
"cut_slope_ratio_user",
)
@@ -193,6 +196,7 @@ def enforce_pavement_ranges(
"rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
),
two_stage_slope=bool(design.get("two_stage_slope", True)),
cut_slope_ratio=stored_cut_slope(design),
ditch_enabled=design.get("ditch_enabled"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
berm=stored_berm(design),
@@ -245,6 +249,7 @@ def enforce_ford_surface_drops(
"rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
),
two_stage_slope=bool(design.get("two_stage_slope", True)),
cut_slope_ratio=stored_cut_slope(design),
ditch_enabled=design.get("ditch_enabled"),
surface_drop_m=wanted,
berm=stored_berm(design),
@@ -346,6 +351,18 @@ def attach_default_designs(
continue
def stored_cut_slope(stored: dict[str, Any]) -> float | None:
"""저장분에 남은 측점별 암 절토 경사비 — 없거나 0(표준값으로 되돌림)이면 None.
⚠ 소단과 같은 성격이다 — **경사는 나르는 값이 아니라 기하 입력**이라 계산에 넣어야 한다.
다시 계산한 뒤 키만 베껴 붙이면 설계선은 옛 경사로 나오고 값만 새것이 되어 어긋난다.
"""
value = stored.get("cut_slope_ratio_user")
if isinstance(value, (int, float)) and float(value) > 0:
return float(value)
return None
def stored_berm(stored: dict[str, Any]) -> BermSpec | None:
"""저장분에 남은 소단 제원 — 세션값이 없을 때 쓴다(확정 뒤·다른 PC)."""
spec = stored.get("berm")
@@ -369,6 +386,7 @@ def recompute_designs_for_alignment(
rock_boundary_offsets: dict[str, float] | None = None,
project_root: Path | None = None,
berms: dict[str, dict[str, float]] | None = None,
cut_slope_ratios: dict[str, float] | None = None,
) -> None:
modes = default_section_modes(longitudinal)
pavement = pavement_suggestions(longitudinal)
@@ -378,6 +396,13 @@ def recompute_designs_for_alignment(
round(float(record["chainage_m"]), 3): (record.get("design") or {})
for record in stored_designs
}
# 측점별 암 절토 경사(2026-09-07) — 0 은 「표준값을 씀」이라 저장분을 덮어 지운다.
session_cut_slopes: dict[float, float] = {}
for raw_key, ratio in (cut_slope_ratios or {}).items():
try:
session_cut_slopes[round(float(raw_key), 3)] = float(ratio)
except (TypeError, ValueError):
continue
session_offsets: dict[float, float] = {}
for raw_key, offset in (rock_boundary_offsets or {}).items():
try:
@@ -415,6 +440,11 @@ def recompute_designs_for_alignment(
stored.get("rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M),
),
two_stage_slope=bool(stored.get("two_stage_slope", True)),
cut_slope_ratio=(
(session_cut_slopes[key] or None)
if key in session_cut_slopes
else stored_cut_slope(stored)
),
ditch_enabled=stored.get("ditch_enabled"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
berm=session_berms.get(key) or stored_berm(stored),