change(B03): 업로드/분석 알림 메일을 초기 설계 완료 시점 한 통으로 통합

같은 업로드 한 번에 업로드 완료 메일과 지표면 분석 완료 메일이 두 통 도착했다.
사용자가 실제로 화면을 볼 수 있는 시점은 초기 설계(B04 전처리 + B05 종단 + B06 횡단)까지
끝난 때 하나뿐이므로 그 시점에 통합 메일 한 통만 보낸다.

- send_initial_analysis_complete_email 신설: 지표면 요약 + 노선 연장/측점 수 +
  종단설계 화면 링크. 자동 체인 미실행/실패 시 전처리 요약만 싣고 안내 문구 전환
- run_auto_design_chain이 요약(route_id/length_m/cross_section_count) 반환
- WF1 서비스: 체인 실행 -> 통합 메일 순으로 재배치
- 업로드 직후 메일 발송 호출 제거(일반·청크 두 경로). 발송 함수는 프로젝트 생성 전
  임시 보관함 안내용으로 남겨 두고 주석으로 용도 명시

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 11:30:41 +09:00
co-authored by Claude Fable 5
parent 6e0a6bcba3
commit 336335c611
4 changed files with 106 additions and 50 deletions
+16 -6
View File
@@ -52,11 +52,16 @@ def _planned_route_points_in_project_crs(project_root: Path) -> list[dict[str, f
return [{"x": x, "y": y} for x, y in points]
async def run_auto_design_chain(project_id: UUID, surface_model_id: int | None = None) -> None:
async def run_auto_design_chain(
project_id: UUID, surface_model_id: int | None = None
) -> dict[str, Any] | None:
"""B05 기본 경로 계산·확정 → B06 기본 횡단 설계 확정을 기본값으로 이어 실행한다.
WF1 자동 확정 직후 같은 백그라운드 태스크에서 호출된다. 어떤 단계가 실패해도
예외를 밖으로 던지지 않는다 — 로그와 각 단계의 workflow 상태 기록으로 남긴다.
성공하면 초기 분석 완료 메일에 실을 요약(노선 id·연장·측점 수)을 돌려주고,
건너뛰거나 실패하면 None을 돌려준다.
"""
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from B05_Profile.B05_Profile_Repository import get_latest_route
@@ -79,14 +84,14 @@ async def run_auto_design_chain(project_id: UUID, surface_model_id: int | None =
project_id,
existing.get("id"),
)
return
return None
# 2) 계획노선 CSV → BP/EP/경유점. 없으면 자동 경로를 세울 근거가 없다.
project_root = Path(resolve_stored_project_path(stored_path))
points = _planned_route_points_in_project_crs(project_root)
if not points:
logger.warning("자동 설계 체인 중단(계획노선 CSV 없음): project_id=%s", project_id)
return
return None
# 3) B05 경로 계산 — WF1 자동 확정과 같은 config 기본값을 쓴다.
defaults = surface_confirmation_defaults()
@@ -109,7 +114,7 @@ async def run_auto_design_chain(project_id: UUID, surface_model_id: int | None =
project_id,
solve_result.status_code,
)
return
return None
route_id = int(solve_result.route_id)
logger.info(
"자동 설계 체인 B05 경로 계산 완료: project_id=%s route_id=%s length=%.1fm",
@@ -127,7 +132,7 @@ async def run_auto_design_chain(project_id: UUID, surface_model_id: int | None =
project_id,
confirm_result.status_code,
)
return
return None
# 5) B06 횡단 설계 확정 — 미지정 측점을 기본값으로 채워 저장. stage 3은
# IN_PROGRESS(스텝바 노란 표시)로 남겨 사용자 검토·확정을 기다린다.
@@ -141,12 +146,17 @@ async def run_auto_design_chain(project_id: UUID, surface_model_id: int | None =
route_id,
sections_result.status_code,
)
return
return None
logger.info(
"자동 설계 체인 완료(B05·B06 기본값 확정): project_id=%s route_id=%s",
project_id,
route_id,
)
return {
"route_id": route_id,
"length_m": float(solve_result.total_length_m or 0.0),
"cross_section_count": solve_result.cross_section_count,
}
except Exception:
# 체인은 업로드·WF1 흐름의 부가 작업이다 — 어떤 예외도 밖으로 던지지 않는다.
logger.exception("자동 설계 체인 실패: project_id=%s", project_id)