feat(511-C 유토 배분 E2 · 판 haul 줄 · 서버만 넷 까닭): /haul-plan 창구 = 늘 떠 있는 Node haul.view(computeHaulPlan 화면 꼴 · design_calc_node.ts 입구만 · 시험 길 표에 두면 브라우저 첫 묶음에 실림) · [저장] 정본 면적 · 유토곡선 · 배분 = 늘 떠 있는 Node 셋(structure.layouts · masshaul.cumulative · haul.plan · 못 부르면 새 프로세스) · 옛 server-calc 묶음 = 시험 대조만 · 판 haul 줄 엶(요구 321 · 기본 server = 지금 /haul-plan 그대로 · 판 파일 바뀜 0 · both = 배분 모듈 dynamic import 해 브라우저 셈 · 정적 import 0) · 서버만 G3 D1 D2 N1 = 줄마다 까닭 글(DB 씀 · 래스터 · D1 래스터 위 · 큰 지형 파일) · 거울 test_511_c_haul_node(프로젝트 일곱 × 문맥 넷 × 그림 · 수량 + 끝 경우 여섯 = 새 길 · 옛 묶음 · 브라우저 함수 0 차 · [저장] 정본 golden 문맥 셋 0 차) — 요구 320-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 15:42:35 +09:00
co-authored by Claude Opus 5.5
parent 70a24c2416
commit 5cada4bbd5
18 changed files with 397 additions and 63 deletions
+25 -11
View File
@@ -5,8 +5,9 @@
멈추면 그 결과를 여기로 보내 배분을 받아 쥔다. 그래서 유토곡선 패널을 펼치는 순간이
즉시가 된다(미리 받아 뒀으므로).
**계산은 한 벌이다** — 화면이 쓰던 TS 를 Node 진입점(`B06_Section_Server_Calc_Node.ts`)으로
그대로 돌린다(CLAUDE.md 5장). 파이썬으로 옮기면 저장 정본과 화면 값이 갈린다.
**계산은 한 벌이다** — 화면이 쓰던 TS(`computeHaulPlan`)를 늘 떠 있는 Node 시험 길 `haul.view`
(`design_calc_node_trial.ts` · 511-C)로 그대로 돌린다(CLAUDE.md 5장). 파이썬으로 옮기면 저장 정본과
화면 값이 갈린다.
정본을 만들지 않는다 — 이 응답은 **표시 전용**이다. 저장 정본은 [저장]·[확정] 뒤
`recompute_server_side` 가 따로 낸다.
@@ -28,21 +29,19 @@ from fastapi.responses import JSONResponse
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from B06_Section.B06_Section_Server_Calc_Prebuild import (
BUNDLE,
_mass_haul_context,
conversion_factors_for,
haul_inputs_for,
haul_limits_for,
)
from common_util.common_util_calc_runner import run_calc_served
from common_util.common_util_derived_store import read_derived, write_derived
from common_util.common_util_node_bundle import run_bundle_json
from common_util.common_util_storage import resolve_stored_project_path
from config.config_db import run_with_connection
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B06 Profile Cross"])
_NPM_SCRIPT = "build:server-calc"
# 측점 수 상한 — 정상 노선은 수백 곳이다. 그보다 크면 비정상 요청으로 본다.
_MAX_POINTS = 5000
# 열쇠(누가토량 결과 + 계수 · 장비 경계) → 배분 — 같은 열쇠면 Node 를 다시 안 띄움
@@ -64,6 +63,26 @@ def _keep(key: str, plan: Any) -> None:
_KEPT.popitem(last=False)
def _run_view(result: dict[str, Any], context: dict[str, Any]) -> dict[str, Any] | None:
"""화면 꼴 배분(그림용 · 잔진동 거름) — 옛 server-calc `haul_plan_for` 갈래와 같은 인자."""
options = {
key: context.get(key)
for key in (
"collected_stone_deduction_m3",
"collected_stone_by_ground_m3",
"collected_stone_ground_unknown_m3",
"structure_spoil_m3",
"structure_spoil_points",
)
}
options["conversion"] = context.get("earthwork_conversion")
options["drawing"] = True
args = [result, context.get("haul_equipment_limits"), options]
answer = run_calc_served(["haul.view"], {"args": {"haul.view": args}})
results = answer.get("results") if isinstance(answer, dict) else None
return {"haul_plan": results["haul.view"]} if isinstance(results, dict) else None
@router.post("/{project_id}/sections/{route_id}/haul-plan", response_model=None)
async def compute_haul_plan(
project_id: UUID,
@@ -114,12 +133,7 @@ async def compute_haul_plan(
_keep(key, stored["value"])
return JSONResponse(content={"status": "success", "haul_plan": stored["value"]})
try:
output = await asyncio.to_thread(
run_bundle_json,
BUNDLE,
_NPM_SCRIPT,
{"haul_plan_for": result, "context": context},
)
output = await asyncio.to_thread(_run_view, result, context)
except Exception:
logger.exception("유토 배분 계산 실패: project_id=%s route_id=%s", project_id, route_id)
return JSONResponse(