fix(449 B05 저장): 관 정본 PUT 은 세션 초안 한 번 · 노선 저장 뒤 실패면 초안을 새 노선 칸으로 · 측점 맞춤 실패 알림

- 433-7 배수판 메모리 다시 PUT 걷음(판 안 선 [저장] 빈 목록 PUT 막음) · [확정] 초안 없음 = 칸 없이 다시 셈 · 판은 저장분 다시 받기만
- 433-12 노선 저장 뒤 다른 차례 실패 = 0 칸 계획고 → 새 노선 칸 · 횡단 초안 넘김(저장 없이 · 두 번 안 넘김) · 새 노선으로 다시 세움
- 433-6 관 PUT 끝 측점 맞춤 실패 = 응답 stations_failed · 알림
- 시험 test_449_save_pipes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 21:29:23 +09:00
co-authored by Claude Opus 5.5
parent d0779af302
commit 33201b14ef
11 changed files with 328 additions and 36 deletions
+10 -2
View File
@@ -588,6 +588,8 @@ export interface DetailBasinResponse {
upstream_lonlat: Array<Array<[number, number]>>;
/** [저장] 때 못 만든 구조물 조합 사본 폴더(142-2 · `*` = 통째 실패). */
snapshot_failed?: string[];
/** 관 PUT 끝 측점 맞춤 실패(433-6 · 관 저장은 그대로). */
stations_failed?: boolean;
/** 지문(342-3) — 지금 노선 · 관 자리. */
route_signature?: string;
pipe_signature?: string;
@@ -645,15 +647,21 @@ export async function resetDetailPipePoints(projectId: string): Promise<DetailBa
/** 관 매설 지점을 정본으로 확정하고 세부유역 산출물까지 남긴다(모델 확정 시점). */
export async function saveDetailPipePoints(
projectId: string,
points: DetailPipeInput[],
points: DetailPipeInput[] | null,
recompute = false,
): Promise<DetailBasinResponse> {
// [확정](recompute) = 낡았으면 서버가 지금 노선으로 배수유역부터 다시 셈(347-2) — 오래 걸림.
// points null = 칸 없이 — 서버 저장분 그대로(433-7).
const saved = await requestJson<DetailBasinResponse>(
`/projects/${projectId}/drainage/pipe-points${recompute ? "?recompute=true" : ""}`,
{ method: "PUT", body: JSON.stringify({ points }) },
{ method: "PUT", body: JSON.stringify(points ? { points } : {}) },
recompute ? API_ANALYSIS_TIMEOUT_MS : API_TIMEOUT_MS,
);
noteSnapshotFailed(saved.snapshot_failed);
if (saved.stations_failed)
showToast(
"관은 저장했지만 관 자리 측점을 못 맞췄습니다 — 횡단 「측점 없는 관」 알림에서 [측점 만들기]를 눌러 주세요.",
"error",
);
return saved;
}
@@ -571,11 +571,12 @@ async def delete_pipe_points(project_id: UUID) -> dict[str, Any] | JSONResponse:
return _payload(project_id, context, detail, points, False, stale)
async def _stand_pipe_stations(project_id: UUID) -> None:
async def _stand_pipe_stations(project_id: UUID) -> bool:
"""관 PUT 끝 — 측점을 지금 관 자리로 다시 세움(`put_plus_stations` 처럼 · 부르는 길 모두 · 428).
노선 [저장] 뒤 관 차례가 화면 누가거리로 관을 덮으면 서버가 노선 저장 때 세운 측점과 어긋남 —
여기서 맞추지 않으면 「측점이 없는 구조물」 · 옛 자리 카드가 남음. 실패해도 관 저장은 그대로.
여기서 맞추지 않으면 「측점이 없는 구조물」 · 옛 자리 카드가 남음. 실패해도 관 저장은 그대로 ·
실패 = 거짓(응답 `stations_failed` 로 알림 · 433-6).
"""
from B06_Section.B06_Section_Router_Stations import ensure_structure_stations
@@ -583,6 +584,8 @@ async def _stand_pipe_stations(project_id: UUID) -> None:
await ensure_structure_stations(project_id)
except Exception:
logger.exception("관 저장: 측점 맞춤 실패(저장은 그대로): project_id=%s", project_id)
return False
return True
@router.put("/{project_id}/drainage/pipe-points", response_model=None)
@@ -627,8 +630,9 @@ async def put_pipe_points(
failed = await asyncio.to_thread(
safe_sync, resolve_stored_project_path(context.stored_path), importer(session)
)
await _stand_pipe_stations(project_id)
stood = await _stand_pipe_stations(project_id)
result = _payload(project_id, context, detail, points, True, stale)
result["saved_count"] = saved
result["snapshot_failed"] = failed # 못 만든 구조물 조합 복사본(142-2) — [저장] 알림
result["stations_failed"] = not stood # 측점 맞춤 실패(433-6) — [저장] 알림
return result