걷은 함수(운영 부름 0 · 전수 확인 = 화면 · 서버 · 체인 · 추천 엔진 · B07 · 시험) - common_util/common_util_excavation.py 통째(126 줄 · pipe_trench_width_m · wall_trench_area_m2 · wall_trench_width_m · 서버 = Node excavation.trench) - Engine_Grade_Carry.carry_edits(Node carry_seat 겉) · Route_Save_Parts.seat_route_moved(시험 헬퍼 helper_475_1_moved 로 옮김) - Engine_Tilt.attach_underdrains · Engine_Structures_Wall.attach_wall_structures · load_wall_structures - Engine_Grade_Alignment.tangent_elevation(부름 0) · station_key.dedupe_station_keys · loose_chainage 줄 수 전 → 후: excavation.py 126 → 0 · Grade_Carry 178 → 157 · Save_Parts 289 → 269 · Wall 221 → 197 · Tilt 115 → 112 · station_key 141 → 122 · Alignment 657 → 654 남는 짝(머리 「남는 까닭」 한 줄) — Grade · Grade_Profile · Alignment · CurveLaw(2~4 순위 폴백 · 추천) · NodeEdits · SampleExtend · Router_Design · Ranges · Engine_Design · Areas · cross_berm(DTM 아닌 지표면 · Node 못 부를 때 · B07 · 추천) · Tilt · Wall · Culvert(B04 B07 B08) · route_follow · structure_station · station_key · route_polyline(745 줄 · 까닭 = 등록부 R1 줄) 등록부 — until 칸 걷음(G1 = py 표본기 남김 · 까닭 note) · C6 pair = attach_underdrain_specs · F8 pair null 알려진 차 — golden KNOWN 셋 그대로 나타남(R7 표본 출처 · W0 저장 때 경고 · X2 입력 선 자릿수 · 까닭 고침) · underdrain 알려진 차 까닭 · 511_b5 KNOWN_ARC 그대로 계산 자리 창 — 45 줄 · 서버만 아닌 41(바꿀 수 있음 40 + F9) · 서버만 넷(G3 D1 D2 N1 줄마다 까닭) · 옛 까닭 「짝 모으는 중 · B-4」 글 걷음 test_322_3_flow 빨강 — 헬퍼가 @config/config_frontend 흉내(511-A design_calc_server import) 시험 기준 — 걷은 짝 거울 = 걷기 전 파이썬 값 고정(excavation · station_key) · Node carry_seat · attach_underdrain_specs · wall_specs_from 검증: 고친 대상 시험 97 파일 562 통과 · 21 건너뜀 · typecheck 0 · 700 줄 넘는 새 파일 없음 — 요구 320-4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
196 lines
8.1 KiB
Python
196 lines
8.1 KiB
Python
"""288 맹암거 횡단 형상(요구 183-3 · 9).
|
||
|
||
못박는 것
|
||
- 288-1 맹암거 기준 측점 = 측구 없음 · 노면 = 표준 경사로 지대 낮은 쪽 한 경사
|
||
(물넘이 286 · 287 길 — 서버 `tilt_ranges` · `tilt_args` · TS 짝 `tilted` · 암거 경사는
|
||
노면에 안 감 371-3) · 제원 얹기 서버 `attach_underdrain_specs` ↔ TS `attachUnderdrainSpecs`
|
||
같은 값 · 형상 = 노면을 암거 높이만큼 아래로 옵셋 → 성토 쪽은 성토 사면과 만남 ·
|
||
절토 쪽은 절토 사면을 늘여 만남 · 그 안 돌 무늬 · 높이 비면 안 그림.
|
||
- 371-3 암거 경사 = 형상 하단 경계 — 높은 쪽 노면 끝 − 높이에서 암거 경사로 낮은 쪽으로 내림.
|
||
- 288-2 맹암거 형상 누름 = 그 구조물 id 고름(구조물 목록 창구 · 알약 · 폼 · 측점선 따라옴) · 물넘이와 같은
|
||
판정면(보라 손잡이 아래 · 끌기 = 고름 아님 · 287-5) · 노면공 포장 범위 고름과 따로.
|
||
|
||
진짜 TS 는 `helper_288_underdrain.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
ROOT = HERE.parents[1]
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
import B06_Section.B06_Section_Engine_Tilt as tilt # noqa: E402
|
||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance # noqa: E402
|
||
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
|
||
|
||
STRUCTURES = [
|
||
{
|
||
"structure_id": "ud-1",
|
||
"type_id": "cross_underdrain",
|
||
"placement": "point",
|
||
"chainage_m": 40.0,
|
||
"options": {"culvert_height_m": 0.8, "culvert_slope_pct": 4},
|
||
},
|
||
{ # 경사 · 높이 빈 맹암거 — 표준 횡단경사 · 형상 없음
|
||
"structure_id": "ud-2",
|
||
"type_id": "cross_underdrain",
|
||
"placement": "point",
|
||
"chainage_m": 80.0,
|
||
"options": {},
|
||
},
|
||
{
|
||
"structure_id": "x-1",
|
||
"type_id": "cross_drain_exposed",
|
||
"placement": "point",
|
||
"chainage_m": 60.0,
|
||
},
|
||
]
|
||
SECTIONS = [{"chainage_m": c} for c in (20.0, 40.0, 60.0, 80.0)]
|
||
|
||
pytestmark = pytest.mark.skipif(
|
||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||
reason="프론트엔드 의존성 미설치",
|
||
)
|
||
|
||
|
||
@pytest.fixture()
|
||
def project(monkeypatch, tmp_path) -> Path:
|
||
items = [StructureInstance(**s) for s in STRUCTURES]
|
||
monkeypatch.setattr(tilt, "load_structures", lambda _root: (1, items))
|
||
monkeypatch.setattr(tilt, "load_culvert_sets", lambda _root: {})
|
||
return tmp_path
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def ts(tmp_path_factory) -> dict:
|
||
source = tmp_path_factory.mktemp("s288") / "in.json"
|
||
source.write_text(
|
||
json.dumps({"structures": STRUCTURES, "sections": SECTIONS}), encoding="utf-8"
|
||
)
|
||
proc = subprocess.run(
|
||
["node", str(HERE / "helper_288_underdrain.cjs"), str(source)],
|
||
capture_output=True,
|
||
text=True,
|
||
encoding="utf-8",
|
||
timeout=60,
|
||
)
|
||
assert proc.returncode == 0, proc.stderr
|
||
return json.loads(proc.stdout)
|
||
|
||
|
||
def test_맹암거_자리_기울기_서버(project):
|
||
ranges = tilt.tilt_ranges(project)
|
||
# 371-3 — 암거 경사(4 %)는 노면에 안 감 · 노면 = 표준 경사
|
||
assert ranges == [(40.0, 40.0, None), (80.0, 80.0, None)]
|
||
assert tilt.tilt_args(40.0, ranges) == {"tilted": True, "tilt_pct": None}
|
||
assert tilt.tilt_args(80.01, ranges) == {"tilted": True, "tilt_pct": None}
|
||
assert tilt.tilt_args(60.0, ranges) == {"tilted": False, "tilt_pct": None}
|
||
|
||
|
||
def test_제원_얹기_서버_TS_같은_값(project, ts):
|
||
sections = [dict(s) for s in SECTIONS]
|
||
attached = tilt.attach_underdrain_specs(sections, tilt.load_underdrains(project))
|
||
assert attached == 2 == ts["attached"]
|
||
assert [s.get("underdrain") for s in sections] == ts["sections"]
|
||
assert ts["sections"][1] == {
|
||
"structure_id": "ud-1",
|
||
"chainage_m": 40.0,
|
||
"height_m": 0.8,
|
||
"slope_pct": 4.0,
|
||
}
|
||
|
||
|
||
def test_맹암거_측점_설계_측구_없음_낮은_쪽_기울기():
|
||
samples = [
|
||
{"offset_m": o / 2, "elevation_m": round(100.0 + 0.35 * o / 2, 4), "valid": True}
|
||
for o in range(-24, 25)
|
||
]
|
||
kwargs = {"ground_type": "soil", "section_mode": "left_cut", "ditch_side": "left"}
|
||
got = compute_cross_design(samples, 100.4, tilted=True, tilt_pct=None, **kwargs)
|
||
standard = compute_cross_design(samples, 100.4, **kwargs)["cross_slope_pct"]
|
||
assert got["ditch_enabled"] is False and got["cross_slope_pct"] == standard
|
||
left, right = (got["road_edges"][k]["elevation_m"] for k in ("left", "right"))
|
||
assert left > right # 지반이 좌로 오름 = 우가 낮음
|
||
|
||
|
||
def test_형상_옵셋선_성토_절토와_만남(ts):
|
||
outline = [(p["offset"], p["elevation"]) for p in ts["outline"]]
|
||
expected = [
|
||
(3, 100.15), # 좌 노면 끝
|
||
(-3, 99.85), # 우 노면 끝
|
||
(-4.632653, 98.768367), # 우 성토 사면 ∩ 옵셋선
|
||
(1.846154, 99.092308), # 좌 절토 사면을 늘인 선 ∩ 옵셋선
|
||
]
|
||
assert len(outline) == len(expected)
|
||
for got, want in zip(outline, expected):
|
||
assert got == pytest.approx(want, abs=1e-5)
|
||
assert ts["noHeight"] is None
|
||
assert ts["drew"] is True and ts["stones"] > 0
|
||
|
||
|
||
def test_하단_경계는_암거_경사(ts):
|
||
"""371-3 — 노면 5 % · 암거 3 % · 높이 1 m: 하단 = 좌(높은 쪽) 끝 99.15 에서 3 % 로 우로 내림."""
|
||
outline = [(p["offset"], p["elevation"]) for p in ts["sloped"]]
|
||
expected = [
|
||
(3, 100.15),
|
||
(-3, 99.85),
|
||
(-4.391304, 98.928261), # 우 성토 사면 ∩ 하단(99.15 − 0.03 × 7.391)
|
||
(1.872180, 99.116165), # 좌 절토 사면을 늘인 선 ∩ 하단
|
||
]
|
||
assert len(outline) == len(expected)
|
||
for got, want in zip(outline, expected):
|
||
assert got == pytest.approx(want, abs=1e-5)
|
||
|
||
|
||
def test_양측절토_늘인_노면_원지반_교점에서_끊음(ts):
|
||
"""356-1 — 낮은 쪽 노면 끝도 절토(tilt_reach) = reach 에서 수직으로 끊음 · 너머 원지반 따라가지 않음."""
|
||
outline = [(p["offset"], p["elevation"]) for p in ts["bothCut"]]
|
||
expected = [
|
||
(3, 100.15),
|
||
(-3, 99.85),
|
||
(-4, 99.8),
|
||
(-4.5, 99.775), # 늘인 노면 ∩ 원지반(tilt_reach)
|
||
(-4.5, 98.775), # reach 아래 옵셋선
|
||
(1.846154, 99.092308),
|
||
]
|
||
assert len(outline) == len(expected)
|
||
for got, want in zip(outline, expected):
|
||
assert got == pytest.approx(want, abs=1e-5)
|
||
|
||
|
||
def test_횡단도_B07_이_맹암거를_그림():
|
||
view = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts").read_text(encoding="utf-8")
|
||
assert (
|
||
"appendUnderdrainOverlay(plotLayer, section.underdrain, section.design, x, toDisplayY);"
|
||
in view
|
||
)
|
||
b07 = (ROOT / "B07_DesignDetail" / "B07_DesignDetail_Cross_Overlay.ts").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
assert "appendUnderdrainOverlay(svg, section.underdrain, design, x, y);" in b07
|
||
refresh = (ROOT / "B06_Section" / "B06_Section_Cross_Refresh.ts").read_text(
|
||
encoding="utf-8"
|
||
) # 셈 몫 = logic/B06_Section_Cross_Refresh_Calc.ts(511-B B-4 나눔)
|
||
refresh += (ROOT / "B06_Section" / "logic" / "B06_Section_Cross_Refresh_Calc.ts").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
assert "tilted: Boolean(section.ford_pavement || section.underdrain)," in refresh
|
||
|
||
|
||
def test_맹암거_형상_누름은_그_구조물_고름(ts):
|
||
assert ts["bodyId"] == "ud-1" # 둘레 = 형상 고름 표식
|
||
assert ts["routed"] == [["pipe"], [ts["eventName"], "ud-1", True]]
|
||
view = (ROOT / "B06_Section" / "B06_Section_UI_Section_View.ts").read_text(encoding="utf-8")
|
||
assert "grid.addEventListener(SHAPE_PICK_EVENT, (event) => {" in view
|
||
assert "if (id) pavementSelectListener?.(id);" in view # = structuresPanel.selectStructure(id)
|
||
cross = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts").read_text(encoding="utf-8")
|
||
assert 'revetOffset && shapePickRoute(svg, (at) => toggleRevet("pipe", at)),' in cross
|