fix(B05·B06): 노선 고친 [저장] 계획고 = 서버 옮김 위에 사용자 손댐만 (PLAN 498-2 · 요구 411~417)
- 브라우저 — 노선 바뀐 [저장]의 계획고 본문 = 묶음 계획고 대 저장분 옮김(같은 carryEdits · 이 초안 첫 기준)의 다른 열쇠 · 지운 열쇠만(`gradeTouch` · `routeGradeTouch`) · 못 가르면 옛 길(노선 0 칸 통째) - 서버 — `alignment_cleared` 가 오면 관 · 구조물 · 추가 측점 차례 뒤 새 종단 편집(서버 옮김) 위에 손댐만 얹어 한 번 저장(`touched_alignment` · 얹기 실패도 되돌림 안) - follow 옮김 측점 = stations 단계와 같은 추가 측점(`plus` 넘김) - 재현: 꼬리 추가 측점 ▲ 뒤 앞 교점 끌기 [저장] = 990 m 편집 버림 · ▲ 잃음 · 최대 −0.50 m → 고친 뒤 버림 0 · 끌 때 = 저장 뒤 - 시험: test_498_2_grade_touch 새로 · helper_design_store_save 에 묶음 창구 가짜 하나(옛 길) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,7 +36,11 @@ from B05_Profile.B05_Profile_Schema import (
|
||||
)
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureSaveRequest
|
||||
from B06_Section import B06_Section_Router_DesignSave_Progress as progress
|
||||
from B06_Section.B06_Section_Router_DesignSave_Route import RouteCarryRequest, StepFailed
|
||||
from B06_Section.B06_Section_Router_DesignSave_Route import (
|
||||
AlignmentCleared,
|
||||
RouteCarryRequest,
|
||||
StepFailed,
|
||||
)
|
||||
from B06_Section.B06_Section_Router_Stations import PlusStationsRequest
|
||||
from B06_Section.B06_Section_Schema import SectionConfirmRequest
|
||||
from common_util import common_util_save_guard as guard
|
||||
@@ -70,6 +74,9 @@ class DesignSaveRequest(BaseModel):
|
||||
route: RouteSaveRequest | None = None
|
||||
#: 노선 바뀐 저장의 옛 노선 세션 칸(측점 열쇠 넘김 입력)
|
||||
carry: RouteCarryRequest | None = None
|
||||
#: 노선 바뀐 저장의 계획고 지운 열쇠 — 오면 `alignment` = 사용자 손댐(서버 옮김 위에 얹음 ·
|
||||
#: 498-2) · 없으면 `alignment` 통째(옛 길)
|
||||
alignment_cleared: AlignmentCleared | None = None
|
||||
|
||||
|
||||
def _body(response: Any) -> dict[str, Any]:
|
||||
@@ -115,7 +122,6 @@ async def _run_steps(
|
||||
get_pipe_points,
|
||||
put_pipe_points,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Router import save_profile_alignment
|
||||
from B05_Profile.B05_Profile_Router_Lifecycle import confirm_latest_route, save_page_settings
|
||||
from B05_Profile.B05_Profile_Structures_Router import write_structures
|
||||
from B06_Section.B06_Section_Router_Confirm import confirm_sections, save_sections
|
||||
@@ -175,6 +181,8 @@ async def _run_steps(
|
||||
# 계획고 초안 = 새 노선에(기하 이월 · 서버가 없는 자리 버림)
|
||||
if request.alignment is not None:
|
||||
request.alignment = request.alignment.model_copy(update={"route_id": route_id})
|
||||
# 노선 바뀜 + 손댐 = 서버 옮김(관 · 구조물 · 추가 측점 차례 뒤 새 종단 편집) 위에 손댐만(498-2)
|
||||
touched = moved and request.alignment_cleared is not None
|
||||
if request.settings is not None:
|
||||
await step("settings", save_page_settings(project_id, request.settings))
|
||||
if request.pipes is not None or request.confirm:
|
||||
@@ -192,7 +200,12 @@ async def _run_steps(
|
||||
if request.plus_stations is not None:
|
||||
await step("plusStations", put_plus_stations(project_id, request.plus_stations))
|
||||
if request.alignment is not None:
|
||||
await step("alignment", save_profile_alignment(project_id, request.alignment))
|
||||
alignment = request.alignment
|
||||
if touched:
|
||||
alignment = moving.touched_alignment(
|
||||
project_id, route_id, alignment, request.alignment_cleared
|
||||
)
|
||||
await step("alignment", _save_alignment(project_id, alignment))
|
||||
if request.culvert_options:
|
||||
stored = await step("culvertOptions", get_pipe_points(project_id))
|
||||
points = _patched_points(stored, request.culvert_options)
|
||||
@@ -241,6 +254,15 @@ async def _run_steps(
|
||||
return out
|
||||
|
||||
|
||||
async def _save_alignment(project_id: UUID, alignment: Any) -> Any:
|
||||
"""계획고 차례 — 본문(또는 본문을 내는 약속 · 노선 바뀐 손댐 얹기 · 498-2)을 저장."""
|
||||
from B05_Profile.B05_Profile_Router import save_profile_alignment
|
||||
|
||||
if not isinstance(alignment, ProfileAlignmentSaveRequest):
|
||||
alignment = await alignment
|
||||
return await save_profile_alignment(project_id, alignment)
|
||||
|
||||
|
||||
async def _save_stages(project_id: str) -> None:
|
||||
"""B05 · B06 한 본문 [저장] — 단계 2 · 3(실패면 한 본문 되돌림이 단계 행도 되살림)."""
|
||||
from common_util.common_util_workflow_state import mark_stage
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
노선 저장(`/route/replan` 체인 · 관 · 구조물 · 추가 측점 좌표 따라감 · 종횡단 · 계획고 ·
|
||||
횡단 이월)
|
||||
→ 관 · 구조물 초안 다시 얹기(Node `facility.rebase` · 서버 따라감 위에)
|
||||
→ 6-A 차례(새 노선) → 횡단 = 옛 노선 세션 칸을 새 측점에 넘긴 patch(Node `stations.carry`)
|
||||
→ 6-A 차례(새 노선 · 계획고 = 서버 옮김 위에 사용자 손댐만 · 498-2)
|
||||
→ 횡단 = 옛 노선 세션 칸을 새 측점에 넘긴 patch(Node `stations.carry`)
|
||||
|
||||
브라우저가 하던 다시 얹기(`rebasePipeDraft` · `carryAfterRouteSave`)와 같은 TS 를 서버가 부름 —
|
||||
브라우저는 노선 초안 + 사용자 칸만 보냄. 손 상단측은 안 넘김(요구 274 · 새 지형이 정함).
|
||||
@@ -36,6 +37,15 @@ class RouteCarryRequest(BaseModel):
|
||||
pipe_ids: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class AlignmentCleared(BaseModel):
|
||||
"""노선 바뀐 [저장]에서 사용자가 지운 계획고 열쇠(498-2 · 브라우저 `gradeTouch`)."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
station_offsets: list[str] = Field(default_factory=list)
|
||||
curve_radii: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class StepFailed(Exception):
|
||||
def __init__(self, step: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
@@ -121,3 +131,46 @@ async def carry_cross(
|
||||
**(carry or RouteCarryRequest()).model_dump(mode="json"),
|
||||
}
|
||||
return _node(["stations.carry"], {"carry": inputs}, "cross")
|
||||
|
||||
|
||||
def overlay_edits(
|
||||
carried: dict[str, Any], touch: dict[str, Any], cleared: AlignmentCleared
|
||||
) -> dict[str, dict[str, float]]:
|
||||
"""서버 옮김 편집 위에 사용자 손댐만(498-2) — 지운 · 고친 열쇠 자리(0.006 m 안)의 옮김 값을 걷고
|
||||
손댐 값을 얹음. 손대지 않은 옮김 열쇠는 서버 측점 그대로(브라우저 열쇠로 안 덮음)."""
|
||||
from B05_Profile.B05_Profile_Engine_Grade_Alignment import SAME_STATION_SNAP_M
|
||||
|
||||
out: dict[str, dict[str, float]] = {}
|
||||
for field in ("station_offsets", "curve_radii"):
|
||||
merged = {str(key): float(value) for key, value in (carried.get(field) or {}).items()}
|
||||
mine = {str(key): float(value) for key, value in (touch.get(field) or {}).items()}
|
||||
for key in [*getattr(cleared, field), *mine]:
|
||||
for near in [k for k in merged if abs(float(k) - float(key)) < SAME_STATION_SNAP_M]:
|
||||
del merged[near]
|
||||
out[field] = {**merged, **mine}
|
||||
return out
|
||||
|
||||
|
||||
async def touched_alignment(
|
||||
project_id: UUID, route_id: int, touch: Any, cleared: AlignmentCleared
|
||||
) -> Any:
|
||||
"""노선 바뀐 [저장] 계획고 본문(498-2) = 새 종단 편집(노선 차례 서버 옮김) ⊕ 사용자 손댐."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
||||
from B05_Profile.B05_Profile_Schema import ProfileAlignmentSaveRequest
|
||||
from B06_Section.B06_Section_Repository import get_longitudinal_section
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from config.config_db import get_db_pool
|
||||
|
||||
async with get_db_pool().acquire() as connection:
|
||||
row = await get_longitudinal_section(connection, project_id, route_id)
|
||||
stored = await get_project_storage_relative_path(connection, project_id)
|
||||
if not row:
|
||||
raise StepFailed("alignment", "새 노선 종단을 못 읽었습니다")
|
||||
path = Path(resolve_stored_project_path(stored)) / str(row["longitudinal_file_path"])
|
||||
longitudinal = json.loads(path.read_text(encoding="utf-8"))
|
||||
carried = (longitudinal.get("profile_alignment") or {}).get("edits") or {}
|
||||
edits = overlay_edits(carried, touch.edits() if touch is not None else {}, cleared)
|
||||
return ProfileAlignmentSaveRequest(route_id=route_id, **edits)
|
||||
|
||||
Reference in New Issue
Block a user