"""횡단 설계선 · 면적 · 소단 구간을 서버 Node 입구로(PLAN 472-4 ② · 등록부 `cross.design` · `cross.berm_spans`) — 브라우저와 같은 TS(`common_util_cross_design` · `common_util_berm_spans`). 파이썬은 입력 조립만 — 측점마다 `compute_cross_design` 키를 모아 Node 한 번에 보냄. Node 를 못 부르면 파이썬 짝으로(거울 시험 `test_b06_cross_design_mirror` · `test_272_berm_source` · 알려진 차 = 설계선 점 반올림 동점 1e-4 m — 파이썬 round 짝수 쪽 · TS 위쪽). """ from __future__ import annotations import logging from pathlib import Path from typing import Any from B06_Section.B06_Section_Engine_Design import _resolve_group, compute_cross_design from common_util.common_util_calc_runner import run_calc logger = logging.getLogger(__name__) # 한 측점 입력 = (지반 표본, 계획고, `compute_cross_design` 키 — standard 뺌) DesignJob = tuple[list[dict[str, Any]], float | None, dict[str, Any]] def full_standard(standard: dict[str, Any] | None) -> dict[str, Any]: """config 기본값 위에 저장분을 덮은 표준 횡단 한 벌 — TS 는 config 를 모름(입력으로 받음).""" out: dict[str, Any] = {} for key in ("soil", "rock", "paved"): g = _resolve_group(key, standard) out[key] = { "road_width_m": g["road_width_m"], "shoulder_left_m": g["shoulder_left_m"], "shoulder_right_m": g["shoulder_right_m"], "ditch": { "top_width_m": g["ditch_top_width_m"], "bottom_width_m": g["ditch_bottom_width_m"], "depth_m": g["ditch_depth_m"], }, "ditch_l_type": {"width_m": g["l_ditch_width_m"], "depth_m": g["l_ditch_depth_m"]}, "cross_slope_pct": {"min": g["cross_slope_pct"]}, "fill_slope_ratio": g["fill_slope_ratio"], "cut_slope_ratio": g["cut_slope_ratio"], "pavement_thickness_m": g["pavement_thickness_m"], } return out def _python(job: DesignJob, standard: dict[str, Any] | None) -> dict[str, Any] | None: samples, elevation, options = job try: return compute_cross_design(samples, elevation, standard=standard, **options) except (ValueError, KeyError): return None def compute_cross_designs( jobs: list[DesignJob], standard: dict[str, Any] | None ) -> list[dict[str, Any] | None]: """측점 묶음 횡단 설계 — 차례 그대로 결과 · None = 못 세움(파이썬 ValueError 자리).""" if not jobs: return [] stations = [ { "samples": samples, "design_elevation_m": elevation, "options": { key: (value._asdict() if key == "berm" and value is not None else value) for key, value in options.items() }, } for samples, elevation, options in jobs ] results = run_calc( ["cross.design"], {"cross_design": {"standard": full_standard(standard), "stations": stations}}, ) if results is None: logger.warning("Node 횡단 설계 실패 — 파이썬 짝으로 셈(%d 측점)", len(jobs)) return [_python(job, standard) for job in jobs] return [None if "error" in row else row for row in results["cross.design"]] def berm_specs_at( project_root: Path, chainages: list[float] ) -> list[dict[str, float] | None] | None: """측점마다 소단 제원(구조물 정본 구간) · None = 정본을 못 읽음(부르는 쪽이 저장분 그대로).""" from B05_Profile.B05_Profile_Structures_Repository import load_structures from B06_Section.B06_Section_Engine_Structures_Wall import berm_span_at, load_berm_spans try: structures = [ item.model_dump(include={"type_id", "start_m", "end_m", "chainage_m", "options"}) for item in load_structures(str(project_root))[1] ] except Exception: # noqa: BLE001 — 정본을 못 읽으면 저장분으로 물러섬 logger.exception("B06 소단 구간을 읽지 못했습니다 (저장분 소단을 씀)") return None results = run_calc( ["cross.berm_spans"], {"berm": {"structures": structures, "chainages": chainages}} ) if results is not None: return results["cross.berm_spans"]["at"] logger.warning("Node 소단 구간 실패 — 파이썬 짝으로 셈") spans = load_berm_spans(project_root) return None if spans is None else [berm_span_at(spans, c) for c in chainages]