"""B05 설계 지형 바꿈(PLAN 626-1 · 요구 530 · 결정 A④ · D2) — 평면 도구줄 지형 칩. `POST /api/projects/{id}/route/design-terrain` 본문 `{terrain: "las" | "sheet"}` → 설계 지형만 바꾸고(`switch_design_terrain`) 노선 저장 체인을 다시 탐 (종단 · 횡단 · 배수 · 관 · 구조물 따라감 · 계획고 옮김 없음 = 옛 · 새 계획 폴리라인 같음). 노선 · 노드 · 곡선표 = 지금 저장본 그대로(안 씀). 초기값(`initial_snapshot/`) · 추천 저장본 · 예정노선 안 씀(옛 추천 [적용] 길 `as_initial` 대신). [저장]과 같은 프로젝트 잠금 · 떠 두고 실패면 되돌림(`common_util_save_guard`) · 같은 지형 = 바꿀 것 없음. """ import asyncio import logging from typing import Literal from uuid import UUID from fastapi.responses import JSONResponse from pydantic import BaseModel from B05_Profile.B05_Profile_Router_Terrain import router from common_util.common_util_surface_confirmation import ( design_terrain, get_surface_confirmation_params, ) from config.config_db import get_db_pool logger = logging.getLogger(__name__) class DesignTerrainRequest(BaseModel): terrain: Literal["las", "sheet"] @router.post("/{project_id}/route/design-terrain", response_model=None) async def switch_route_design_terrain( project_id: UUID, body: DesignTerrainRequest ) -> dict | JSONResponse: """설계 지형을 바꾸고 뒤 단계를 모두 다시 셈 — 노선 값 · 초기값은 그대로.""" from B05_Profile.B05_Profile_Router_Replan import ( _PROJECT_PATH_MISSING, _planned_line, _project_paths, _recompute, ) from B06_Section.B06_Section_Router_DesignSave import _restore from common_util import common_util_save_guard as guard lock = guard.project_lock(str(project_id)) async with lock: paths = await _project_paths(project_id) if paths is None: return JSONResponse(status_code=404, content=_PROJECT_PATH_MISSING) project_root, stored_path = paths pool = get_db_pool() async with pool.acquire() as connection: now = design_terrain(await get_surface_confirmation_params(connection, str(project_id))) if now == body.terrain: return {"status": "success", "design": now, "changed": False} rows = await guard.snapshot_rows(connection, str(project_id), surface=True) files = await asyncio.to_thread( guard.snapshot_storage, project_root, guard.STORAGE_SCOPE + guard.PIPE_SCOPE ) try: line = await asyncio.to_thread(_planned_line, project_root) result = await _recompute( project_id, project_root, stored_path, None, body.terrain, (line, line), False ) if "error" in result: await _restore(project_id, rows, files) return JSONResponse( status_code=409, content={"status": "error", "message": result["error"]} ) except Exception: await _restore(project_id, rows, files) raise finally: files.discard() logger.info("설계 지형 바꿈: project_id=%s %s → %s", project_id, now, body.terrain) return {"status": "success", "design": body.terrain, "changed": True, **result}