diff --git a/common_util/common_util_calc_runner.py b/common_util/common_util_calc_runner.py new file mode 100644 index 000000000..b54a08f8e --- /dev/null +++ b/common_util/common_util_calc_runner.py @@ -0,0 +1,37 @@ +"""계산 등록부 파이썬 실행기(PLAN 472-3 3-B) — 등록부 id 를 받아 서버 Node 입구 하나를 부름. + +등록부 = `A00_Common/design/logic/design_calc_registry.ts` · 입구 = `design_calc_node.ts` +(`build:design-calc`). 부름은 `common_util_node_bundle.run_bundle_json` 한 길 · 계산 없음. +`NODE_IDS` = 등록부에서 실행기가 `node:` 인 줄(같은지 시험 `test_472_3_calc_runner.py`). +입력 꼴 = 입구 머리(`detail` · `route_points` · `context` · `excavation`). +""" + +from __future__ import annotations + +from collections.abc import Sequence +from pathlib import Path +from typing import Any + +from common_util.common_util_node_bundle import run_bundle_json + +ROOT = Path(__file__).resolve().parents[1] +BUNDLE = ROOT / "config" / "design_calc_node" / "design_calc_node.js" +NPM_SCRIPT = "build:design-calc" +NODE_IDS = frozenset( + { + "structure.layouts", + "masshaul.cumulative", + "haul.plan", + "excavation.trench", + "corridor.mesh", + } +) + + +def run_calc(ids: Sequence[str], inputs: dict[str, Any]) -> dict[str, Any] | None: + """id 들을 한 번에 돌려 `{id: 값}` · 묶음 실패는 None · Node 실행기 아닌 id 는 ValueError.""" + unknown = [item for item in ids if item not in NODE_IDS] + if unknown: + raise ValueError(f"Node 실행기 아닌 id: {', '.join(unknown)}") + output = run_bundle_json(BUNDLE, NPM_SCRIPT, {"run": list(ids), "inputs": inputs}) + return output.get("results") if isinstance(output, dict) else None diff --git a/resources/tester/test_472_3_calc_runner.py b/resources/tester/test_472_3_calc_runner.py new file mode 100644 index 000000000..98f3323ec --- /dev/null +++ b/resources/tester/test_472_3_calc_runner.py @@ -0,0 +1,70 @@ +"""PLAN 472-3 3-B (요구 273) — 파이썬 실행기 `common_util_calc_runner` → Node 입구 → 기존 묶음 값. + +못박는 것 + - `NODE_IDS` = 등록부 `node:` 줄 = 입구 표(`available`). + - 고정 자료 `fixtures/golden_472` 로 `run_calc` 값 = [저장] 재계산이 부르는 server-calc 묶음 값 + (`Server_Calc_Prebuild` BUNDLE · 면적 · 다단 벽 · 누가토량 · 배분 · 끝값 −17,570.7). + - Node 실행기 아닌 id(파이썬 · 브라우저 실행기)는 ValueError. + + ./venv/Scripts/python.exe -m pytest resources/tester/test_472_3_calc_runner.py -q +""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + +import pytest + +from common_util import common_util_calc_runner as runner + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[1] +FIXTURE = HERE / "fixtures" / "golden_472" +REGISTRY = ROOT / "A00_Common" / "design" / "logic" / "design_calc_registry.ts" + +pytestmark = pytest.mark.skipif( + not (ROOT / "config" / "node_modules" / "vite").is_dir(), + reason="프론트엔드 의존성 미설치", +) + + +def _fixture(name: str): + return json.loads((FIXTURE / name).read_text(encoding="utf-8")) + + +def _context() -> dict: + ctx = _fixture("context.json") + keys = ("earthwork_conversion", "haul_equipment_limits", "natural_spoil_min_ground_slope") + return {k: ctx[k] for k in keys} + + +def test_NODE_IDS_등록부_입구와_같음() -> None: + src = REGISTRY.read_text(encoding="utf-8") + assert set(re.findall(r'runner: "node:([a-z_.]+)"', src)) == runner.NODE_IDS + out = runner.run_bundle_json(runner.BUNDLE, runner.NPM_SCRIPT, {"run": [], "inputs": {}}) + assert set(out["available"]) == runner.NODE_IDS + + +def test_실행기_값_server_calc_와_같음() -> None: + from B06_Section.B06_Section_Server_Calc_Prebuild import _NPM_SCRIPT, BUNDLE + from common_util.common_util_node_bundle import run_bundle_json + + sc = run_bundle_json( + BUNDLE, _NPM_SCRIPT, {"detail": _fixture("detail.json"), "context": _context()} + ) + got = runner.run_calc( + ["structure.layouts", "masshaul.cumulative", "haul.plan"], + {"detail": _fixture("detail.json"), "context": _context()}, + ) + assert got["structure.layouts"]["areas"] == sc["areas"] and sc["areas"] + assert got["structure.layouts"]["extra_walls"] == sc["extra_walls"] + merged = {**got["masshaul.cumulative"], **got["haul.plan"]} + assert merged == sc["mass_haul"] + assert merged["final_cumulative_m3"] == -17570.7 + + +def test_Node_실행기_아닌_id_는_ValueError() -> None: + with pytest.raises(ValueError, match="stations.list"): + runner.run_calc(["masshaul.cumulative", "stations.list"], {})