diff --git a/A00_Common/design/logic/design_calc_registry.ts b/A00_Common/design/logic/design_calc_registry.ts index f692b269a..958a1ef98 100644 --- a/A00_Common/design/logic/design_calc_registry.ts +++ b/A00_Common/design/logic/design_calc_registry.ts @@ -439,8 +439,8 @@ export const CALC_REGISTRY: readonly CalcEntry[] = [ runner: `py:${B06}B06_Section_Engine_Tilt.py#attach_underdrains`, until: "472-4", pair: `${B06}B06_Section_Engine_Tilt.py#attach_underdrains`, - mirror: [], - note: "거울 시험 없음 — 옮기기 전 먼저", + mirror: ["test_472_4_underdrain_mirror"], + note: "얹기 = GET 상세(파이썬) · 알려진 차 = 옛 underdrain 걷음 TS 만 · 기울임은 cross.design 안", }, { id: "cross.fit", diff --git a/resources/tester/helper_472_4_underdrain_entry.ts b/resources/tester/helper_472_4_underdrain_entry.ts new file mode 100644 index 000000000..425117255 --- /dev/null +++ b/resources/tester/helper_472_4_underdrain_entry.ts @@ -0,0 +1,5 @@ +/* 472-4 C6 거울 헬퍼 — 맹암거 얹기 TS 짝(vite ssr) · test_472_4_underdrain_mirror.py 가 JSON 으로 받음. */ +export { + attachUnderdrainSpecs, + underdrainSpecsFrom, +} from "../../common_util/common_util_structure_underdrain"; diff --git a/resources/tester/test_472_4_underdrain_mirror.py b/resources/tester/test_472_4_underdrain_mirror.py new file mode 100644 index 000000000..947c16d13 --- /dev/null +++ b/resources/tester/test_472_4_underdrain_mirror.py @@ -0,0 +1,120 @@ +"""PLAN 472-4 ② C6 (요구 273) — 맹암거 얹기 거울: TS `common_util_structure_underdrain` ↔ 파이썬 +`B06_Section_Engine_Tilt.underdrains_from` · `attach_underdrain_specs`. + +못박는 것 + - 구조물 목록 → 제원(높이 · 경사 · 빈 값 · 0 · 음수 · 글 · 맹암거 아닌 종류) 두 쪽 같음. + - 기준 측점(±0.02 m) 얹기 · 얹은 수 같음. + - 알려진 차 하나 — 옛 `underdrain` 이 남은 측점: TS 는 걷음 · 파이썬은 둠 + (서버는 파일에서 새로 읽어 옛 값 없음 · Node 로 옮길 때 한 벌). + + ./venv/Scripts/python.exe -m pytest resources/tester/test_472_4_underdrain_mirror.py -q +""" + +from __future__ import annotations + +import copy +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +from B05_Profile.B05_Profile_Structures_Schema import StructureInstance +from B06_Section.B06_Section_Engine_Tilt import attach_underdrain_specs, underdrains_from + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[1] + +pytestmark = pytest.mark.skipif( + not (ROOT / "config" / "node_modules" / "vite").is_dir(), + reason="프론트엔드 의존성 미설치", +) + + +def _drain(at: float, **options) -> dict: + return { + "type_id": "cross_underdrain", + "placement": "point", + "chainage_m": at, + "options": options, + } + + +STRUCTURES = [ + _drain(40.0, culvert_height_m=1.2, culvert_slope_pct=3), + _drain(80.0, culvert_height_m="", culvert_slope_pct="2.5"), + _drain(120.0, culvert_height_m=0, culvert_slope_pct=-1), + _drain(160.0), + {"type_id": "berm", "placement": "interval", "start_m": 0.0, "end_m": 50.0, "options": {}}, +] +SECTIONS = [ + {"chainage_m": 40.01}, + {"chainage_m": 79.99}, + {"chainage_m": 120.0}, + {"chainage_m": 160.03}, # 0.02 밖 + {"chainage_m": 200.0, "underdrain": {"chainage_m": 200.0}}, # 옛 값 — 알려진 차 +] + + +@pytest.fixture(scope="module") +def ts(tmp_path_factory) -> dict: + work = tmp_path_factory.mktemp("u4724") + subprocess.run( # noqa: S603 — 고정 실행 파일 + [ + "node", + str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"), + "build", + "--configLoader", + "runner", + "--ssr", + "../resources/tester/helper_472_4_underdrain_entry.ts", + "--outDir", + str(work), + "--logLevel", + "error", + ], + cwd=ROOT, + check=True, + capture_output=True, + timeout=300, + ) + bundle = work / "bundle.mjs" + shutil.copy(work / "helper_472_4_underdrain_entry.js", bundle) + job = json.dumps({"structures": STRUCTURES, "sections": SECTIONS}) + script = ( + f"const m = await import({json.dumps(bundle.as_uri())});" + f"const job = {job};" + "const specs = m.underdrainSpecsFrom(job.structures);" + "const count = m.attachUnderdrainSpecs(job.sections, specs);" + "console.log(JSON.stringify({specs, count, sections: job.sections}));" + ) + out = subprocess.run( # noqa: S603 + ["node", "--input-type=module", "-e", script], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + return json.loads(out.stdout) + + +def _python() -> tuple[list[dict], int, list[dict]]: + specs = underdrains_from([StructureInstance(**item) for item in STRUCTURES]) + sections = copy.deepcopy(SECTIONS) + return specs, attach_underdrain_specs(sections, specs), sections + + +def test_제원_같음(ts) -> None: + specs, _count, _sections = _python() + assert ts["specs"] == specs + + +def test_얹기_같음_알려진_차_하나(ts) -> None: + _specs, count, sections = _python() + assert ts["count"] == count == 3 + assert ts["sections"][:4] == sections[:4] + # 알려진 차 — 옛 underdrain: TS 걷음 · 파이썬 둠 + assert "underdrain" not in ts["sections"][4] and "underdrain" in sections[4]