feat(B03): 재접속 업로드 현황(10) + B05·B06 자동 설계 체인(9)
10. 재접속 현황·완료 표시·재업로드 경고:
- GET /projects/{id}/upload-overview 신설 — 완료 파일 목록(input_files 정본),
중단 청크 세션(진행률), 필수 파일·WF1 분석 완료 여부
- 진입 시 서버 정본으로 슬롯 카드 표시(serverUploaded), localStorage는 보조로 강등
- 파일 재선택 전에도 중단 세션 이어올리기 안내 배너
- 전체 완료 배지 + 완료 슬롯 재업로드 시 교체 확인 모달(승인 시에만 진행)
- 필수 슬롯 검증: 서버 업로드분 있으면 충족 — 단일 파일 교체 업로드 허용
9. 자동 설계 체인 연장 (B03_FileInput_Service_Chain.py):
- WF1 자동 확정 후 같은 백그라운드 태스크에서 ① 계획노선 CSV 기반 B05 기본 경로
계산(solve) ② 경로 확정(stage 2) ③ B06 기본 횡단 설계 확정(stage 3)까지 진행
- 수동 이력 보호: 프로젝트에 경로가 이미 있으면 건너뜀
- 단계별 실패 격리: 실패 단계에서 멈추고 로그·workflow 상태로만 기록
- AUTO_DESIGN_CHAIN_ENABLED config 플래그(기본 True)
typecheck·ruff·B03 unittest(7건) 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,8 @@ from B03_FileInput.B03_FileInput_Repository import (
|
||||
get_project_storage_relative_path,
|
||||
get_upload_session,
|
||||
list_completed_chunk_indexes,
|
||||
list_incomplete_upload_sessions,
|
||||
list_project_input_files,
|
||||
mark_upload_session_completed,
|
||||
mark_upload_session_failed,
|
||||
upsert_upload_chunk,
|
||||
@@ -42,6 +44,9 @@ from B03_FileInput.B03_FileInput_Schema import (
|
||||
FileUploadResponse,
|
||||
UploadedFileResult,
|
||||
UploadFinalizeRequest,
|
||||
UploadOverviewFile,
|
||||
UploadOverviewResponse,
|
||||
UploadOverviewSession,
|
||||
UploadStatusResponse,
|
||||
)
|
||||
from B03_FileInput.B03_FileInput_Service_WF1 import trigger_wf1_analysis_and_email
|
||||
@@ -620,6 +625,66 @@ async def get_project_upload_status(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{project_id}/upload-overview", response_model=UploadOverviewResponse)
|
||||
async def get_project_upload_overview(
|
||||
project_id: UUID,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
) -> UploadOverviewResponse | JSONResponse:
|
||||
"""B03 재접속 시 업로드 현황(정본) — 완료 파일 목록 + 중단 세션 + 완료 여부.
|
||||
|
||||
localStorage 기반 표시는 캐시를 지우거나 다른 PC로 가면 사라진다(2026-08-04 사용자
|
||||
보고). 화면은 진입 시 이 응답을 정본으로 삼고 localStorage는 보조로만 쓴다.
|
||||
"""
|
||||
pool = get_db_pool()
|
||||
try:
|
||||
async with pool.acquire() as connection:
|
||||
files = await list_project_input_files(connection, project_id)
|
||||
sessions = await list_incomplete_upload_sessions(connection, project_id)
|
||||
file_types, point_cloud_id = await get_project_input_readiness(connection, project_id)
|
||||
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
||||
state = await get_workflow_state(cursor, str(project_id))
|
||||
stages = (state or {}).get("stages") or []
|
||||
analysis_complete = any(
|
||||
int(stage.get("stage_no", -1)) == 1 and str(stage.get("state")) == "COMPLETE"
|
||||
for stage in stages
|
||||
)
|
||||
return UploadOverviewResponse(
|
||||
files=[
|
||||
UploadOverviewFile(
|
||||
input_file_id=int(row["id"]),
|
||||
file_type=str(row["file_type"]),
|
||||
original_filename=str(row["original_filename"]),
|
||||
file_size_mb=float(row["file_size_mb"] or 0.0),
|
||||
status=str(row["status"]),
|
||||
uploaded_at=str(row["created_at"]) if row.get("created_at") else None,
|
||||
)
|
||||
for row in files
|
||||
],
|
||||
pending_sessions=[
|
||||
UploadOverviewSession(
|
||||
upload_session_id=str(row["id"]),
|
||||
original_filename=str(row["original_filename"]),
|
||||
file_size_bytes=int(row["file_size_bytes"]),
|
||||
total_chunks=int(row["total_chunks"]),
|
||||
completed_chunks=int(row["completed_chunks"]),
|
||||
progress_percent=round(
|
||||
100.0 * int(row["completed_chunks"]) / max(1, int(row["total_chunks"])), 1
|
||||
),
|
||||
updated_at=str(row["updated_at"]) if row.get("updated_at") else None,
|
||||
)
|
||||
for row in sessions
|
||||
],
|
||||
required_complete=_REQUIRED_FILE_TYPES <= file_types and point_cloud_id is not None,
|
||||
analysis_complete=analysis_complete,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("B03 업로드 현황 조회 실패: project_id=%s", project_id)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "업로드 현황 조회 중 오류가 발생했습니다."},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{project_id}/workflow-state")
|
||||
async def get_project_workflow_state(project_id: str):
|
||||
pool = get_db_pool()
|
||||
|
||||
Reference in New Issue
Block a user