refactor(511-B B-4 기본 설계 셋 Node · preview 걷음): run_calc = 늘 떠 있는 Node(못 띄우면 새 프로세스) · 기본 설계 셋(체인 · 노선 저장 이월 · [측점 만들기] · GET 빈 측점 · B05 확정 측구 방향 역반영) = 측점 묶음 Node cross.design 한 번 · 포장 · 기울임 · 세월교 하강 = TS facilityRanges(cross.design facilities · Node 못 부르면 파이썬 구간 짝) · /cross-design/preview 걷음(창구 · 스키마 · recompute_designs_for_alignment · 화면 refreshFromServer) · crossOptions = logic/B06_Section_Cross_Save.ts(다음 단계 [저장] 횡단 한 바퀴 짝 · 아직 안 부름) · 시험 266 · 475_3 · masshaul_single_source 기대 맞춤 — 요구 320-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 14:18:09 +09:00
co-authored by Claude Opus 5.5
parent f6abe881a2
commit f40ff98819
14 changed files with 496 additions and 543 deletions
-110
View File
@@ -14,7 +14,6 @@ from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse, Response
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from B05_Profile.B05_Profile_Engine_Grade_Profile import rebuild_alignment_profile
from B05_Profile.B05_Profile_Engine_Sections import (
cross_files_in_order,
relabel_pipe_stations,
@@ -35,15 +34,9 @@ from B06_Section.B06_Section_Repository import (
insert_cross_sections,
list_recent_company_projects,
)
from B06_Section.B06_Section_Router_Design import (
PREVIEW_DESIGN_FIELDS as _PREVIEW_DESIGN_FIELDS,
)
from B06_Section.B06_Section_Router_Design import (
attach_default_designs as _attach_default_designs,
)
from B06_Section.B06_Section_Router_Design import (
recompute_designs_for_alignment as _recompute_designs_for_alignment,
)
from B06_Section.B06_Section_Router_Design import (
stored_standard_cross_section as _stored_standard_cross_section,
)
@@ -55,8 +48,6 @@ from B06_Section.B06_Section_Schema import (
CompanyStandardListResponse,
CompanyStandardProject,
CompanyStandardResponse,
CrossDesignPreviewRequest,
CrossDesignPreviewResponse,
HaulEquipmentLimit,
SectionContextResponse,
SectionDetailResponse,
@@ -482,104 +473,3 @@ async def regenerate_route_sections(
status_code=500,
content={"status": "error", "message": "종횡단 재생성 처리 중 오류가 발생했습니다."},
)
def _with_design_choices(
designs: list[dict[str, Any]], choices: dict[str, dict[str, Any]] | None
) -> list[dict[str, Any]]:
"""카드 선택 세션값을 저장분 위에 얹은 행 목록(439) — 뒤 행이 같은 측점 앞 행을 덮음."""
stored = {round(float(r["chainage_m"]), 3): r.get("design") or {} for r in designs}
picked = {round(float(key), 3): choice for key, choice in (choices or {}).items()}
return designs + [
{"chainage_m": key, "design": {**stored.get(key, {}), **choice}}
for key, choice in picked.items()
]
@router.post(
"/{project_id}/sections/{route_id}/cross-design/preview",
response_model=CrossDesignPreviewResponse,
)
async def preview_cross_designs(
project_id: UUID, route_id: int, request: CrossDesignPreviewRequest
) -> CrossDesignPreviewResponse | JSONResponse:
"""계획선 편집 델타로 계획선과 전 측점 횡단 설계를 다시 계산해 돌려준다(저장 없음).
⚠ **평상시 조작 경로가 아니다**(2026-09-03 이후). 계획선을 만지는 동안의 재계산은
브라우저가 직접 한다(`common_util/common_util_cross_design.ts`) — 조작 중 계산이
서버로 나가면 왕복이 조작 속도를 지배하기 때문이다(사용자 확정). 이 엔드포인트는
선형 저장분이 없어 브라우저가 계획고를 풀 수 없는 **옛 데이터 폴백**으로만 남는다
(`B06_Section_Cross_Refresh.refreshCrossDesigns`).
영속화는 각 페이지의 임시저장·확정이 맡는다.
"""
pool = get_db_pool()
try:
async with pool.acquire() as connection:
longitudinal_row = await get_longitudinal_section(connection, project_id, route_id)
if not longitudinal_row:
return JSONResponse(
status_code=404,
content={"status": "error", "message": "종횡단 상세 결과가 없습니다."},
)
stored_path = await get_project_storage_relative_path(connection, project_id)
designs = await get_cross_section_designs(connection, route_id)
source = await get_route_generation_source(connection, project_id, route_id)
designs = _with_design_choices(designs, request.design_choices)
project_root = Path(resolve_stored_project_path(stored_path))
detail = await asyncio.to_thread(
_read_section_detail,
project_root,
str(longitudinal_row["longitudinal_file_path"]),
(source or {}).get("route_data_path"),
)
def rebuild() -> None:
alignment, profile = rebuild_alignment_profile(detail["longitudinal"], request.edits())
detail["longitudinal"]["profile_alignment"] = alignment
detail["longitudinal"]["design_profiles"] = [profile]
_recompute_designs_for_alignment(
detail["longitudinal"],
detail["cross_sections"],
designs,
request.standard_cross_section,
request.rock_boundary_offsets,
project_root,
request.berms,
request.cut_slope_ratios,
)
await asyncio.to_thread(rebuild)
# 계획고를 끄는 동안 오가는 값이라 기본은 **유토곡선이 실제로 읽는 필드만** 싣는다.
# 지반선 샘플·설계선 좌표(design_line)·선형 구조를 다 담으면 1MB가 넘어 편집이 굼떠진다
# (2026-08-03 사용자 지적). 계획선 자체는 화면이 이미 같은 규칙으로 계산해 들고 있다.
# `full_designs`(B06 진입 시 stale 일괄 재계산)일 때만 설계 전체를 1회 싣는다 —
# 측점별 순차 호출 N번보다 왕복 1번이 훨씬 싸다(2026-08-04 사용자 확인).
return CrossDesignPreviewResponse(
designs=[
{
"chainage_m": section.get("chainage_m"),
"design": section["design"]
if request.full_designs
else {
key: section["design"].get(key)
for key in _PREVIEW_DESIGN_FIELDS
if key in section["design"]
},
}
for section in detail["cross_sections"]
if section.get("design")
],
)
except FileNotFoundError as exc:
return JSONResponse(status_code=404, content={"status": "error", "message": str(exc)})
except (OSError, ValueError, json.JSONDecodeError) as exc:
return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)})
except Exception:
logger.exception(
"B06 횡단 설계 프리뷰 실패: project_id=%s route_id=%s", project_id, route_id
)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "횡단 설계 프리뷰 계산 중 오류가 발생했습니다."},
)