feat(520-4): B05 · B06 페이지 넘김 공용 moveStage — B06 [>] 잠금판 「설계 확정」 서버 저장 차례 n/N 이름 · 배수유역 다시 셈 한 번

- B06 [>] · 단계 판 B07~B09 = moveStage 한 단계(설계 확정) · progress = GET /design/save/progress 1 s 물음 · B03 · B04 로 = moveStage 잠금 길(떠남 확인은 잠금 풀린 뒤)
- 낡음 확인 창 · 화면 side.update() 걷음 — 배수유역은 서버 [확정] 관 차례가 한 번만 셈 · 진행 이름 「배수유역 다시 셈」(basins)
- 서버 _run_steps step() 진행을 메모리(B06_Section_Router_DesignSave_Progress) · 차례 목록 = N · 끝에 비움
- 노선 바뀐 채 [>] = 물음(잠금판 위 · z 1050 > 900) → 다시 세운 페이지 둘째 확정을 저장 줄 밖에서 기다림 → 잠금판이 다시 마운트를 넘어 이어져 이동 · 120 s 넘으면 머묾
- saveSharedDesign 이 참 · 거짓을 돌려줌 · 확정 이동은 부른 쪽(moveStage)
- 낡은 시험 고침 — 512_2(확인 창 → 잠금판 한 단계 · 서버 basins) · 298_3 · 272 · b06_save_channels · b06_save_recompute(글 약속 새 길)
- helper_ts_stub_loader .css = 빈 모듈 — 520-3 ui_template_progress.css import 로 451 · 384_4 · 416 등 helper 시험이 깨짐(브레인 승인)
- 새 시험 test_520_4_save_progress(n/N · 배수유역 자리)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 08:19:22 +09:00
co-authored by Claude Opus 5.5
parent 80615cce2f
commit ed9e014db2
13 changed files with 221 additions and 69 deletions
+47 -3
View File
@@ -35,6 +35,7 @@ from B05_Profile.B05_Profile_Schema import (
RouteConfirmRequest,
)
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_Stations import PlusStationsRequest
from B06_Section.B06_Section_Schema import SectionConfirmRequest
@@ -83,6 +84,15 @@ def _body(response: Any) -> dict[str, Any]:
return dict(response or {})
async def _stale_or_false(stale: Any, project_id: UUID) -> bool:
"""진행 이름 고르기만 — 판정 실패는 저장을 막지 않음(관 차례가 다시 판정)."""
try:
return bool(await stale(project_id))
except Exception:
logger.exception("한 본문 저장 배수유역 낡음 판정 실패: project_id=%s", project_id)
return False
def _patched_points(stored: dict[str, Any], patches: dict[str, dict[str, Any]]) -> list | None:
"""저장된 관 목록에 관 옵션 조각 — 고친 것이 없으면 None(브라우저 `flushCulvertOptions` 짝)."""
points, touched = [], False
@@ -100,7 +110,11 @@ async def _run_steps(
project_id: UUID, request: DesignSaveRequest, session: dict[str, Any] | None
) -> dict[str, Any]:
"""차례대로 안쪽 창구 · 차례 이름째 실패를 던짐 → 차례별 응답(알림 · 화면 맞춤용)."""
from B04_PreProcess.B04_PreProcess_Router_Basins import get_pipe_points, put_pipe_points
from B04_PreProcess.B04_PreProcess_Router_Basins import (
_analysis_stale,
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
@@ -111,8 +125,26 @@ async def _run_steps(
route_id = request.route_id
moved = request.route is not None
before: dict[str, Any] = {}
# 잠금판 진행(520-4) — 이 본문이 거칠 차례 목록 = N
planned = [
name
for name, on in (
("route", moved),
("settings", request.settings is not None),
("pipes", request.pipes is not None or request.confirm),
("structures", request.structures is not None),
("plusStations", request.plus_stations is not None),
("alignment", request.alignment is not None),
("culvertOptions", bool(request.culvert_options)),
("uphill", request.uphill is not None and not moved),
("cross", moved or request.cross is not None),
)
if on
]
progress.begin(project_id, planned)
async def step(name: str, call: Any) -> dict[str, Any]:
async def step(name: str, call: Any, label: str | None = None) -> dict[str, Any]:
progress.mark(project_id, label or name)
try:
out[name] = _body(await call)
except StepFailed as failed:
@@ -146,8 +178,12 @@ async def _run_steps(
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:
# [확정] + 낡은 분석 = 관 차례가 배수유역부터 다시 셈(한 번 · 화면 [업데이트] 없음 · 520-4)
basins = request.confirm and await _stale_or_false(_analysis_stale, project_id)
pipes = await step(
"pipes", put_pipe_points(project_id, request.pipes or {}, session, request.confirm)
"pipes",
put_pipe_points(project_id, request.pipes or {}, session, request.confirm),
"basins" if basins else None,
)
if pipes.get("stations_failed"):
raise StepFailed("pipes", "관 자리 측점을 못 맞췄습니다")
@@ -170,6 +206,7 @@ async def _run_steps(
raise StepFailed("uphill", " · ".join(parts["failed_parts"]) + " 실패")
# 노선 바뀜 = 옛 노선 세션 칸 → 새 측점 patch(Node) · 고침이 없어도 한 번(429 ④)
if moved:
progress.mark(project_id, "cross")
try:
carried = await moving.carry_cross(project_id, route_id, request.carry, before)
except StepFailed:
@@ -251,9 +288,16 @@ async def save_design(
)
finally:
files.discard()
progress.end(project_id)
return JSONResponse(content={"status": "success", "waited": waited, "steps": steps})
@router.get("/{project_id}/design/save/progress")
async def get_design_save_progress(project_id: UUID) -> dict[str, Any]:
"""잠금판 진행 묻기(520-4) — 차례 이름 · n/N · 지난 초 · 안 돌면 active 거짓."""
return progress.snapshot(project_id)
async def _restore(project_id: UUID, rows: dict[str, list], files: guard.StorageSnapshot) -> bool:
"""떠 둔 대로 — 성공 = 참 · 하나라도 못 되돌리면 로그 남기고 거짓(조용히 넘기지 않음)."""
ok = True