feat(B03): 새 자료로 갈기 전에 「지우고 진행할까」 확인 한 단계 신설
업로드 하나가 그 프로젝트의 설계 산출물·초기값 스냅숏을 되돌릴 수 없게 지운다. 창 넷이 한 프로젝트를 볼 수 있어 말없이 지우면 남의 작업이 사라진다. - `describe_existing_outputs`: 지워질 것을 사람 말로 낸다(전처리·노선·횡단·도면· 수량·원가·초기값). 빈 폴더는 세지 않는다. - 지울 것이 있는데 확인이 없으면 409 `confirm_required` — 무엇이 지워지는지 함께 준다. - 세 갈래(직행·청크·보관함 연결) 전부에 `confirm_replace` 를 붙임. - ⚠ 기본값은 확인 켬(True) — 자동 절차(체인·스크립트)는 물음에 안 걸린다. 사람이 올리는 라우터만 False 로 불러 확인을 받는다. - 화면: 409 를 받으면 「되돌릴 수 없습니다」와 지워질 목록을 보이고 한 번 묻는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
import aiomysql
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from B03_FileInput.B03_FileInput_Email import (
|
||||
send_file_upload_complete_email,
|
||||
@@ -40,6 +41,61 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
_ANALYSIS_RUNNING_MESSAGE = "이 프로젝트는 지금 분석 중입니다. 끝난 뒤에 새 자료를 올려 주세요."
|
||||
|
||||
# 새 자료를 받으면 옛 산출물을 지운다 — 지워질 것이 있으면 **먼저 묻는다**(2026-09-08).
|
||||
# 창 넷이 한 프로젝트를 함께 볼 수 있어, 말없이 지우면 남이 며칠 만든 설계가 사라진다.
|
||||
# 되돌릴 길이 없다(초기값 스냅숏까지 함께 버린다).
|
||||
_OUTPUT_STAGE_LABELS: tuple[tuple[str, str], ...] = (
|
||||
("B04_PreProcess", "지표면·전처리 결과"),
|
||||
("B05_Profile", "노선·종단 설계"),
|
||||
("B06_Section", "횡단 설계"),
|
||||
("B07_DesignDetail", "도면"),
|
||||
("B08_Quantity", "수량 산출"),
|
||||
("B09_Estimation", "원가·내역"),
|
||||
)
|
||||
|
||||
|
||||
class OutputsWouldBeDiscarded(Exception):
|
||||
"""이미 있는 설계 산출물을 지워야 새 자료를 받을 수 있는 상태.
|
||||
|
||||
라우터가 409 로 돌려주고, 사람이 「지우고 진행」을 고르면 `confirm_replace=True`
|
||||
로 다시 들어온다. 자동 절차는 이 예외를 만들지 않는다 — 부를 때 확인을 켜 준다.
|
||||
"""
|
||||
|
||||
def __init__(self, targets: list[str]) -> None:
|
||||
self.targets = targets
|
||||
super().__init__("설계 산출물이 있어 확인이 필요합니다.")
|
||||
|
||||
|
||||
def _confirm_replace_response(exc: "OutputsWouldBeDiscarded") -> JSONResponse:
|
||||
"""「지우고 진행할까?」를 사람에게 묻는 409 — 무엇이 지워지는지 함께 알린다."""
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={
|
||||
"status": "confirm_required",
|
||||
"confirm": "replace_outputs",
|
||||
"message": (
|
||||
"이 프로젝트에는 이미 만들어 둔 결과가 있습니다. 새 자료로 갈면 "
|
||||
"아래가 지워지고 **되돌릴 수 없습니다**: " + " · ".join(exc.targets)
|
||||
),
|
||||
"targets": exc.targets,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def describe_existing_outputs(project_root: Path) -> list[str]:
|
||||
"""새 자료를 받으면 **지워질 것**의 이름을 사람 말로 늘어놓는다. 없으면 빈 목록."""
|
||||
targets: list[str] = []
|
||||
for stage, label in _OUTPUT_STAGE_LABELS:
|
||||
stage_root = project_root / stage
|
||||
if not stage_root.is_dir():
|
||||
continue
|
||||
if any(entry.is_file() for entry in stage_root.rglob("*")):
|
||||
targets.append(label)
|
||||
if (project_root / "initial_snapshot").is_dir():
|
||||
targets.append("초기값 스냅숏(되돌리기의 기준)")
|
||||
return targets
|
||||
|
||||
|
||||
_REQUIRED_FILE_TYPES = frozenset({"prj", "tfw"})
|
||||
_POINT_CLOUD_FILE_TYPES = frozenset({"las", "laz"})
|
||||
# 계획노선은 CSV 또는 shapefile 중 하나면 된다 (2026-08-31 — 원청 정식 노선이 shapefile).
|
||||
@@ -149,7 +205,13 @@ async def _complete_file_input_if_ready(
|
||||
connection: aiomysql.Connection,
|
||||
project_id: UUID,
|
||||
las_free: bool = False,
|
||||
confirm_replace: bool = True,
|
||||
) -> int:
|
||||
"""새 자료로 갈아 끼운다. `confirm_replace=False` 면 지울 것이 있을 때 멈추고 묻는다.
|
||||
|
||||
기본이 `True` 인 까닭 — 자동 절차(체인·스크립트)는 물음에 걸리면 안 된다.
|
||||
사람이 올리는 갈래(라우터)만 `False` 로 불러 확인을 받는다(2026-09-08 사용자 지시).
|
||||
"""
|
||||
file_types, point_cloud_input_id, route_csv_input_id = await get_project_input_readiness(
|
||||
connection, project_id
|
||||
)
|
||||
@@ -172,6 +234,11 @@ async def _complete_file_input_if_ready(
|
||||
gap_message = merge_gap_error(terrain_paths)
|
||||
if gap_message:
|
||||
raise ValueError(gap_message)
|
||||
# ⚠ 여기서부터는 되돌릴 수 없다 — 아래 셋이 파일까지 지운다. 지울 것이 있으면 먼저 묻는다.
|
||||
if not confirm_replace:
|
||||
targets = describe_existing_outputs(project_root)
|
||||
if targets:
|
||||
raise OutputsWouldBeDiscarded(targets)
|
||||
clear_designing(project_root)
|
||||
discard_initial_snapshot(project_root)
|
||||
await purge_project_outputs(connection, str(project_id), project_root)
|
||||
|
||||
Reference in New Issue
Block a user