refactor(511-B B-4 기본 설계 셋 Node · preview 걷음): run_calc = 늘 떠 있는 Node(못 띄우면 새 프로세스) · 기본 설계 셋(체인 · 노선 저장 이월 · [측점 만들기] · GET 빈 측점 · B05 확정 측구 방향 역반영) = 측점 묶음 Node cross.design 한 번 · 포장 · 기울임 · 세월교 하강 = TS facilityRanges(cross.design facilities · Node 못 부르면 파이썬 구간 짝) · /cross-design/preview 걷음(창구 · 스키마 · recompute_designs_for_alignment · 화면 refreshFromServer) · crossOptions = logic/B06_Section_Cross_Save.ts(다음 단계 [저장] 횡단 한 바퀴 짝 · 아직 안 부름) · 시험 266 · 475_3 · masshaul_single_source 기대 맞춤 — 요구 320-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 14:18:09 +09:00
co-authored by Claude Opus 5.5
parent f6abe881a2
commit f40ff98819
14 changed files with 496 additions and 543 deletions
+43 -146
View File
@@ -11,7 +11,6 @@ from typing import Any
from B05_Profile.B05_Profile_Engine_Sections import find_cross_file
from B06_Section.B06_Section_Engine_Design import (
ROUTE_CARRY_KEYS,
compute_cross_design,
curve_widening_args,
stored_shoulder_extend,
stored_tilt,
@@ -42,20 +41,6 @@ from common_util.common_util_cross_berm import (
from common_util.common_util_route_profile import design_elevation_from_longitudinal
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
PREVIEW_DESIGN_FIELDS = (
"ground_type",
"roadbed_width_m",
"cut_area_m2",
"cut_soil_area_m2",
"cut_rock_area_m2",
"cut_rock_kind",
"fill_area_m2",
"fill_ground_slope",
"design_elevation_m",
"slope_unclosed",
)
# 사용자 조작값 — 다시 계산해도 그대로 승계한다. **이 목록이 서버의 유일한 한 벌이다.**
#
# 브라우저 쪽 짝은 `B06_Section_Cross_Refresh.ts` 의 `PRESERVED_KEYS` 다(+ `status`·
@@ -375,34 +360,37 @@ def attach_default_designs(
project_root: Path | None = None,
standard: dict[str, Any] | None = None,
) -> None:
"""설계 없는 측점 = 기본값 프리뷰 — Node 묶음 한 번(TS 짝 · 시설 구간 TS · 511-B B-4)."""
modes = default_section_modes(longitudinal)
pavement = pavement_suggestions(longitudinal)
paved_ranges = pavement_ranges(project_root) if project_root else []
tilts = tilt_ranges(project_root)
ford_drops = ford_surface_drops(project_root)
jobs: list[tuple[dict[str, Any], float, DesignJob]] = []
for section in cross_sections:
if section.get("design"):
continue
try:
chainage = float(section.get("chainage_m", 0.0))
suggested = pavement.get(round(chainage, 3), False)
design = compute_cross_design(
section.get("samples", []),
design_elevation_from_longitudinal(longitudinal, chainage),
ground_type="ripping_rock",
section_mode=modes.get(round(chainage, 3), "left_cut"),
# 포장은 사용자 구간 지정만 켠다 — 경사 제안은 경고로만 남는다(2026-08-28).
paved=paved_at(chainage, paved_ranges),
standard=standard,
rock_boundary_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
surface_drop_m=ford_drop_at(chainage, ford_drops),
**curve_widening_args(section),
**tilt_args(chainage, tilts),
)
design.update(status="provisional", pavement_suggested=suggested)
section["design"] = design
except (ValueError, KeyError):
chainage = float(section.get("chainage_m", 0.0))
options = dict(
ground_type="ripping_rock",
section_mode=modes.get(round(chainage, 3), "left_cut"),
# 포장은 사용자 구간 지정만 켠다 — 경사 제안은 경고로만 남는다(2026-08-28) · 구간 = TS
rock_boundary_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
**curve_widening_args(section),
)
elevation = design_elevation_from_longitudinal(longitudinal, chainage)
jobs.append((section, chainage, (section.get("samples", []), elevation, options)))
if not jobs:
return
designs = compute_cross_designs(
[job for *_rest, job in jobs],
standard,
(project_root, [c for _s, c, _j in jobs]) if project_root else None,
)
for (section, chainage, _job), design in zip(jobs, designs, strict=True):
if design is None:
continue
design.update(
status="provisional", pavement_suggested=pavement.get(round(chainage, 3), False)
)
section["design"] = design
def stored_cut_slope(stored: dict[str, Any]) -> float | None:
@@ -445,96 +433,6 @@ def stored_berm(stored: dict[str, Any]) -> BermSpec | None:
return berm_from(stored.get("berm"), stored)
def recompute_designs_for_alignment(
longitudinal: dict[str, Any],
cross_sections: list[dict[str, Any]],
stored_designs: list[dict[str, Any]],
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,
cut_slope_ratios: dict[str, float] | None = None,
) -> None:
modes = default_section_modes(longitudinal)
pavement = pavement_suggestions(longitudinal)
paved_ranges = pavement_ranges(project_root) if project_root else []
tilts = tilt_ranges(project_root)
ford_drops = ford_surface_drops(project_root)
stored_by_chainage = {
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:
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:
parsed = berm_from(spec, spec if isinstance(spec, dict) else {}) # 쪽 깊이도(194-1)
if parsed is not None:
session_berms[round(float(raw_key), 3)] = parsed
except (TypeError, ValueError):
continue
for section in cross_sections:
chainage = float(section.get("chainage_m", 0.0))
key = round(chainage, 3)
stored = stored_by_chainage.get(key) or {}
suggested = pavement.get(key, False)
try:
design = compute_cross_design(
section.get("samples", []),
design_elevation_from_longitudinal(longitudinal, chainage),
ground_type=str(stored.get("ground_type") or "ripping_rock"),
section_mode=str(stored.get("section_mode") or modes.get(key, "left_cut")),
ditch_side=stored.get("ditch_side"),
ditch_type=str(stored.get("ditch_type") or "standard"),
paved=paved_at(chainage, paved_ranges),
standard=standard,
rock_boundary_offset_m=session_offsets.get(
key,
stored.get("rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M),
),
two_stage_slope=bool(stored.get("two_stage_slope", False)),
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"),
ditch_choice=stored.get("ditch_choice"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
berm=session_berms.get(key) or stored_berm(stored),
**curve_widening_args(section),
**stored_shoulder_extend(stored), # 노견 확장(139-6)
**tilt_args(chainage, tilts),
)
except (ValueError, KeyError):
continue
design.update(status="provisional", pavement_suggested=suggested)
if stored.get("display_half_width_m") is not None:
design["display_half_width_m"] = stored["display_half_width_m"]
if stored.get("inlet_structure") is not None:
design["inlet_structure"] = stored["inlet_structure"]
if stored.get("basin_adjust") is not None:
design["basin_adjust"] = stored["basin_adjust"]
if stored.get("ford_adjust") is not None:
design["ford_adjust"] = stored["ford_adjust"]
if stored.get("box_adjust") is not None:
design["box_adjust"] = stored["box_adjust"]
section["design"] = design
def compute_default_designs(
project_root: Path,
longitudinal_file_path: str,
@@ -562,10 +460,7 @@ def compute_default_designs(
cross_dir = longitudinal_path.parent.parent / "cross_sections"
default_modes = default_section_modes(longitudinal)
pavement = pavement_suggestions(longitudinal)
paved_ranges = pavement_ranges(root)
tilts = tilt_ranges(root)
ford_drops = ford_surface_drops(root)
results: list[tuple[float, dict[str, Any]]] = []
entries: list[tuple[float, dict[str, Any], DesignJob]] = []
for chainage_m in chainages:
cross = (crosses or {}).get(round(chainage_m, 3))
cross_path = find_cross_file(cross_dir, chainage_m)
@@ -577,12 +472,9 @@ def compute_default_designs(
samples = cross.get("samples")
if not isinstance(samples, list):
continue
suggested = pavement.get(round(chainage_m, 3), False)
user = (carried or {}).get(round(chainage_m, 3)) or {}
rock_offset = user.get("rock_boundary_offset_m")
design = compute_cross_design(
samples,
design_elevation_from_longitudinal(longitudinal, chainage_m),
options = dict(
ground_type=str(user.get("ground_type") or "ripping_rock"),
# 손 상단측(같은 자리로 넘긴 단면 유형 · 측구 방향 · 요구 275-3) · 없으면 상단측
section_mode=user.get("section_mode")
@@ -594,27 +486,32 @@ def compute_default_designs(
),
ditch_side=user.get("ditch_side"),
ditch_type=str(user.get("ditch_type") or "standard"),
# 포장은 사용자 구간 지정만 켠다 — 경사 제안은 경고로만 남는다(2026-08-28).
paved=paved_at(chainage_m, paved_ranges),
standard=standard,
# 포장 · 기울임 · 세월교 하강 = 구간 · 관 지점(TS `facilityRanges` · 511-B B-4)
rock_boundary_offset_m=(
STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M if rock_offset is None else rock_offset
),
two_stage_slope=bool(user.get("two_stage_slope", False)),
cut_slope_ratio=stored_cut_slope(user),
ditch_choice=user.get("ditch_choice"),
surface_drop_m=ford_drop_at(chainage_m, ford_drops),
berm=stored_berm(user),
**curve_widening_args(cross),
**stored_shoulder_extend(user), # 노선 저장이 넘긴 사면 쉬프트(417)
**tilt_args(chainage_m, tilts),
)
for key in ("berm", "cut_slope_ratio_user", *ROUTE_CARRY_KEYS):
if user.get(key) is not None:
design[key] = user[key]
design["status"] = "provisional"
design["pavement_suggested"] = suggested
results.append((chainage_m, design))
elevation = design_elevation_from_longitudinal(longitudinal, chainage_m)
except (ValueError, KeyError, OSError, json.JSONDecodeError):
continue
entries.append((chainage_m, user, (samples, elevation, options)))
designs = compute_cross_designs(
[job for *_rest, job in entries], standard, (root, [c for c, _u, _j in entries])
)
results: list[tuple[float, dict[str, Any]]] = []
for (chainage_m, user, _job), design in zip(entries, designs, strict=True):
if design is None:
continue # 계획고 부재 등 — 조용히 건너뜀(확정을 막지 않음)
for key in ("berm", "cut_slope_ratio_user", *ROUTE_CARRY_KEYS):
if user.get(key) is not None:
design[key] = user[key]
design["status"] = "provisional"
design["pavement_suggested"] = pavement.get(round(chainage_m, 3), False)
results.append((chainage_m, design))
return results