"""사토장 — 횡단 단면 셈 지움 · 남은 사토 값 정리 (PLAN 380-1 · 요구 241-8). ⚠ 잠그는 것 ① 사토장이 놓여도 [저장]·[확정] 서버 재계산(`enforce_design_lines` → `_enforce_stored_designs`)이 사토 단면을 세우지 않는다 — 횡단도에 그림 없음 · 상세 CAD 글만. ② 옛 저장분에 남은 `spoil_fill_*` 값(노선 성토를 뺀 꼴)은 [저장] 한 번에 지워지고 노선 성토(`fill_area_m2`)가 사토장 없는 값으로 돌아온다. """ from __future__ import annotations import json import sys from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402 from B06_Section.B06_Section_Router_Design import enforce_design_lines # noqa: E402 from B06_Section.B06_Section_Server_Calc_Prebuild import _enforce_stored_designs # noqa: E402 def _samples() -> list[dict]: """우측(-offset)이 높고 좌측(+offset)이 낮은 지반 — 좌가 성토 쪽.""" return [ {"offset_m": round(offset, 3), "elevation_m": round(100.0 - 0.5 * offset, 4), "valid": True} for offset in [x * 0.5 for x in range(-30, 31)] ] def _plain() -> dict: return compute_cross_design( _samples(), 99.8, ground_type="soil", section_mode="right_cut", ditch_side="right" ) _LONGITUDINAL = { "design_profiles": [ {"samples": [{"chainage_m": float(c), "elevation_m": 99.8} for c in range(0, 201, 20)]} ] } def _write_site(root: Path) -> None: (root / "B05_Profile" / "route").mkdir(parents=True, exist_ok=True) (root / "B05_Profile" / "route" / "structures.json").write_text( json.dumps( { "revision": 1, "structures": [ { "structure_id": "sp-1", "type_id": "spoil_bank", "placement": "site", "chainage_m": 120.0, "options": {"capacity_m3": 400.0, "side": "좌"}, } ], }, ensure_ascii=False, ), encoding="utf-8", ) def _leftover() -> dict: """옛 저장분 꼴 — 사토장 몫 1 ㎡ 를 노선 성토에서 뺀 채 `spoil_fill_*` 가 남음.""" design = _plain() return { **design, "fill_area_m2": round(design["fill_area_m2"] - 1.0, 4), "spoil_fill_area_m2": 7.0, "spoil_fill_side": "left", "spoil_fill_width_m": 3.0, "spoil_fill_line": [{"offset_m": 3.0, "elevation_m": 99.0}], "spoil_fill_replaced_fill_m2": 1.0, "spoil_fill_capacity_m3": 400.0, "spoil_fill_structure_id": "sp-1", } def test_설계_결과에_사토_칸_없음() -> None: assert not [key for key in _plain() if key.startswith("spoil_fill")] def test_사토장이_놓여도_저장_재계산이_사토_단면을_안_세움(tmp_path: Path) -> None: _write_site(tmp_path) sections = [ {"chainage_m": c, "samples": _samples(), "design": _plain()} for c in (100.0, 120.0, 140.0) ] enforce_design_lines(_LONGITUDINAL, sections, tmp_path) _enforce_stored_designs(_LONGITUDINAL, sections, tmp_path, None) for section in sections: assert not [key for key in section["design"] if key.startswith("spoil_fill")] def test_옛_사토_값은_저장_한_번에_지워지고_성토_돌아옴(tmp_path: Path) -> None: _write_site(tmp_path) plain_fill = _plain()["fill_area_m2"] assert plain_fill > 1.0 # 성토가 실제로 서는 지반이라야 「돌아옴」이 뜻을 가짐 sections = [{"chainage_m": 120.0, "samples": _samples(), "design": _leftover()}] assert enforce_design_lines(_LONGITUDINAL, sections, tmp_path) == 1 design = sections[0]["design"] assert not [key for key in design if key.startswith("spoil_fill")] assert design["fill_area_m2"] == pytest.approx(plain_fill, abs=1e-6)