fix(B03): 파일 교체 후 다시 업로드할 수 있게 — 끝난 카드 [파일 선택] + 확정 대기 관문

- 완료 카드에서도 [파일 선택]을 보임. 올리는 중에만 감춤. 교체 확인 모달과
  교체 업로드 규칙은 이미 있던 것을 그대로 씀.
- 업로드 관문이 전처리 1단계 IN_PROGRESS 를 무조건 「분석 중」으로 읽어,
  자동 확정이 보류된 프로젝트는 영영 새 자료를 받지 못했음. 전처리 진행 파일이
  awaiting_confirmation·completed·failed 로 끝나 있으면 받도록 고침.
  실제로 도는 중(analyzing)이면 그대로 막음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 22:42:57 +09:00
co-authored by Claude Opus 5
parent 057ba34f32
commit 5666086c6e
2 changed files with 38 additions and 3 deletions
+3 -2
View File
@@ -699,8 +699,9 @@
display: none;
}
.b03-file__card--uploading .b03-file__card-select,
.b03-file__card--completed .b03-file__card-select {
/* 올리는 중에만 [파일 선택]을 감춘다. 끝난 카드에도 남겨 두어야 파일 하나를 갈아
끼우고 [파일 업로드]로 다시 돌릴 수 있다(2026-09-04 사용자 지시). */
.b03-file__card--uploading .b03-file__card-select {
display: none;
}
+35 -1
View File
@@ -260,7 +260,41 @@ async def is_analysis_running(cursor: aiomysql.DictCursor, project_id: str) -> b
if not row:
return False
state = row["state"] if isinstance(row, dict) else row[0]
return str(state) == "IN_PROGRESS"
if str(state) != "IN_PROGRESS":
return False
# 자동 확정이 보류되면 1단계는 사용자가 B04에서 모델을 고를 때까지 IN_PROGRESS 로
# 남는다 — 계산은 이미 끝난 상태다. 그 사이에도 새 자료는 받아야 한다
# (2026-09-04 사용자 지시 — 같은 프로젝트로 전처리를 되풀이 시험).
return not await _surface_run_settled(cursor, project_id)
# 계산이 끝나 사용자의 다음 조작을 기다리는 진행 단계 — 도는 중이 아니다.
_SETTLED_SURFACE_STAGES = frozenset({"awaiting_confirmation", "completed", "failed"})
async def _surface_run_settled(cursor: aiomysql.DictCursor, project_id: str) -> bool:
"""전처리 진행 파일이 「끝났음」으로 적혀 있는가. 파일이 없으면 판단하지 않는다."""
from pathlib import Path
from B04_PreProcess.B04_PreProcess_Router_Progress import read_surface_progress
from common_util.common_util_storage import resolve_stored_project_path
await cursor.execute(
"SELECT storage_path FROM projects WHERE id = %s AND deleted_at IS NULL",
(project_id,),
)
row = await cursor.fetchone()
if not row:
return False
storage_path = row["storage_path"] if isinstance(row, dict) else row[0]
if not storage_path:
return False
try:
progress = read_surface_progress(Path(resolve_stored_project_path(str(storage_path))))
except (OSError, ValueError):
return False
stage = str((progress or {}).get("current_stage") or "")
return stage in _SETTLED_SURFACE_STAGES
async def reset_stages_after_input_change(cursor: aiomysql.DictCursor, project_id: str) -> None: