fix(511-B /calc 경로 막기): 계산 창구가 브라우저 파일 경로를 안 씀 — 단계 격자 = {model_id · smooth · design} → 서버가 그 프로젝트 storage 안 DTM 격자 경로로 갈음(dtm_grid_npz · read_surface_grid 와 한 벌 · storage 밖 = 거절) · 경로 칸(stages.grid.path · stations.grid) = 400 · 창구 묶음 안 파일 읽기 = gridAt 하나 · 시험 test_511_b_calc_path_guard — 요구 320-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 14:38:16 +09:00
co-authored by Claude Opus 5.5
parent 578b48f7ab
commit a7e2da2b9e
3 changed files with 224 additions and 21 deletions
+37
View File
@@ -5,6 +5,7 @@
브라우저가 계산 자리 판(`calc_sites.json`)에서 묶음을 「서버」로 둔 때만 부름
(`design_calc_server.ts`).
권한 = 프로젝트 접근(`protected_with_company`) · 호출량 = `main.py` 미들웨어 · 본문 상한 `MAX_BODY`.
파일 경로 = 서버가 프로젝트 id 로 정함(그 storage 안 DTM 격자) · 입력에 경로 칸 = 400.
실행 = 늘 떠 있는 Node 하나(`common_util_calc_runner.run_calc_served`) ·
정본 저장 길은 이 창구를 안 씀.
"""
@@ -17,6 +18,7 @@ from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from common_util.common_util_calc_runner import SERVER_ONLY_IDS, run_calc_served
from config.config_db import get_db_pool
router = APIRouter(prefix="/api/projects", tags=["B06 Calc Server"])
@@ -28,6 +30,38 @@ def _error(status: int, message: str) -> JSONResponse:
return JSONResponse(status_code=status, content={"status": "error", "message": message})
async def _server_grid(project_id: UUID, inputs: dict) -> str | None:
"""파일 경로는 브라우저 값을 안 씀 — 단계 격자 = `{model_id, smooth, design}` → 이 프로젝트
storage 안 DTM 격자 경로(서버가 정함) · 경로 칸(`stages.grid.path` · `stations.grid`) = 거절."""
stations = inputs.get("stations")
if isinstance(stations, dict) and "grid" in stations:
return "격자 경로는 서버가 정함"
stages = inputs.get("stages")
grid = stages.get("grid") if isinstance(stages, dict) else None
if grid is None:
return None
if not isinstance(grid, dict) or "path" in grid:
return "격자 경로는 서버가 정함 — {model_id, smooth, design}"
model_id, design = grid.get("model_id"), grid.get("design", "las")
if (
not isinstance(model_id, int)
or isinstance(model_id, bool)
or design not in ("las", "sheet")
):
return "격자 = {model_id: 정수, smooth, design: las | sheet}"
from B05_Profile.B05_Profile_Router_Terrain import dtm_grid_npz
from common_util.common_util_surface_sampler import dtm_grid_file
async with get_db_pool().acquire() as connection:
try:
npz = await dtm_grid_npz(connection, project_id, model_id, bool(grid.get("smooth")))
except LookupError as exc:
return str(exc)
path = await asyncio.to_thread(dtm_grid_file, npz)
stages["grid"] = {"path": str(path), "design": design}
return None
@router.post("/{project_id}/calc", response_model=None)
async def run_calc_endpoint(project_id: UUID, request: Request) -> dict | JSONResponse:
declared = request.headers.get("content-length")
@@ -50,6 +84,9 @@ async def run_calc_endpoint(project_id: UUID, request: Request) -> dict | JSONRe
return _error(400, "inputs = 객체")
if SERVER_ONLY_IDS.intersection(run):
return _error(400, "서버 측점 생성은 창구 밖")
problem = await _server_grid(project_id, inputs)
if problem:
return _error(400, problem)
if "stations.list" in run and isinstance(inputs.get("stations"), dict):
# 심는 측점 · 카드 이름 표 = 서버 구조물 정의(서버 측점 생성과 같은 표 · 511-B B-2)
from B05_Profile.B05_Profile_Engine_Sections_Node import structure_table