B05 페이지 초안
This commit is contained in:
@@ -7,12 +7,10 @@ from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import aiomysql
|
||||
from fastapi import APIRouter, File, Form, UploadFile
|
||||
from fastapi import APIRouter, Depends, File, Form, UploadFile
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from B03_FileInput.B03_FileInput_Email import (
|
||||
send_analysis_completion_email,
|
||||
send_analysis_error_email,
|
||||
send_file_upload_complete_email,
|
||||
)
|
||||
from B03_FileInput.B03_FileInput_Engine import (
|
||||
@@ -44,20 +42,17 @@ from B03_FileInput.B03_FileInput_Schema import (
|
||||
UploadFinalizeRequest,
|
||||
UploadStatusResponse,
|
||||
)
|
||||
from B03_FileInput.B03_FileInput_Service_WF1 import trigger_wf1_analysis_and_email
|
||||
from common_util.common_util_auth import verify_session
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from common_util.common_util_workflow import load_project_workflow
|
||||
from common_util.common_util_workflow_state import (
|
||||
complete_stage,
|
||||
fail_stage,
|
||||
get_workflow_state,
|
||||
start_stage,
|
||||
)
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_system import (
|
||||
SEND_ANALYSIS_COMPLETION_EMAIL,
|
||||
SURFACE_MODEL_PRECOMPUTE,
|
||||
SURFACE_MODEL_SOURCE_FILTERS,
|
||||
UPLOAD_CHUNK_SIZE_BYTES,
|
||||
UPLOAD_MAX_FILES,
|
||||
)
|
||||
@@ -144,147 +139,11 @@ async def _send_upload_complete_notification(
|
||||
)
|
||||
|
||||
|
||||
async def trigger_wf1_analysis_and_email(
|
||||
*,
|
||||
project_id: UUID,
|
||||
input_file_id: int,
|
||||
) -> None:
|
||||
"""WF1 분석 실행, DB 저장, 완료/오류 이메일 발송을 백그라운드에서 수행한다.
|
||||
|
||||
실행 흐름:
|
||||
1. 프로젝트 저장 경로와 입력 파일 정보를 조회한다.
|
||||
2. 무거운 WF1 분석은 워커 스레드에서 실행한다.
|
||||
3. 분석 결과를 단일 DB 트랜잭션으로 저장한다.
|
||||
4. 완료 이메일은 SEND_ANALYSIS_COMPLETION_EMAIL 설정이 켜진 경우에만 발송한다.
|
||||
5. 분석 또는 DB 저장 실패 시 오류 이메일을 발송한다.
|
||||
"""
|
||||
pool = get_db_pool()
|
||||
project_info: dict[str, Any] | None = None
|
||||
try:
|
||||
source_filters = list(SURFACE_MODEL_SOURCE_FILTERS)
|
||||
methods = list(SURFACE_MODEL_PRECOMPUTE)
|
||||
params = {
|
||||
"input_file_id": str(input_file_id),
|
||||
"source_filters": source_filters,
|
||||
"methods": methods,
|
||||
"force": False,
|
||||
}
|
||||
|
||||
async with pool.acquire() as connection:
|
||||
async with connection.cursor() as cursor:
|
||||
await start_stage(cursor, str(project_id), 1, params)
|
||||
await connection.commit()
|
||||
|
||||
stored_path = await get_project_storage_relative_path(connection, project_id)
|
||||
project_info = await _get_project_notification_info(connection, project_id)
|
||||
if not project_info or not project_info.get("user_email"):
|
||||
logger.warning("WF1 분석 이메일 수신자 없음: project_id=%s", project_id)
|
||||
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Repository import get_input_file
|
||||
|
||||
input_file = await get_input_file(connection, project_id, input_file_id)
|
||||
|
||||
project_root = Path(resolve_stored_project_path(stored_path))
|
||||
las_path = project_root / Path(str(input_file["raw_file_path"]))
|
||||
if not las_path.is_file():
|
||||
raise FileNotFoundError("원본 LAS/LAZ 파일을 찾을 수 없습니다.")
|
||||
|
||||
logger.info(
|
||||
"WF1 백그라운드 분석 시작: project_id=%s input_file_id=%s",
|
||||
project_id,
|
||||
input_file_id,
|
||||
)
|
||||
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Engine import run_surface_analysis
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Router import (
|
||||
save_surface_analysis_to_db,
|
||||
write_surface_progress,
|
||||
)
|
||||
|
||||
# 업로드 자동 분석도 progress.json에 진행률을 기록한다 (PLAN C-4)
|
||||
write_surface_progress(project_root, 5, "analyzing", "WF1 분석을 시작합니다.")
|
||||
|
||||
def _on_progress(percent: int, stage: str, message: str) -> None:
|
||||
write_surface_progress(project_root, percent, stage, message)
|
||||
|
||||
logger.info("WF1 분석 엔진 시작: las_path=%s", las_path)
|
||||
analysis_result = await asyncio.to_thread(
|
||||
run_surface_analysis,
|
||||
project_root,
|
||||
las_path,
|
||||
source_filters=source_filters,
|
||||
methods=methods,
|
||||
force=False,
|
||||
on_progress=_on_progress,
|
||||
)
|
||||
logger.info("WF1 분석 엔진 완료: 모델 %d개 생성됨", len(analysis_result.get("models", [])))
|
||||
|
||||
logger.info("WF1 분석 결과 DB 저장 시작: project_id=%s", project_id)
|
||||
async with pool.acquire() as connection:
|
||||
await connection.begin()
|
||||
try:
|
||||
await save_surface_analysis_to_db(
|
||||
connection,
|
||||
project_id=project_id,
|
||||
input_file_id=input_file_id,
|
||||
analysis_result=analysis_result,
|
||||
source_filters=source_filters,
|
||||
)
|
||||
async with connection.cursor() as cursor:
|
||||
await complete_stage(cursor, str(project_id), 1)
|
||||
await connection.commit()
|
||||
logger.info("WF1 분석 결과 DB 저장 완료: project_id=%s", project_id)
|
||||
except Exception as e:
|
||||
logger.exception("WF1 분석 결과 DB 저장 실패: %s", e)
|
||||
await connection.rollback()
|
||||
raise
|
||||
|
||||
write_surface_progress(
|
||||
project_root,
|
||||
100,
|
||||
"awaiting_confirmation",
|
||||
"WF1 분석이 완료되었습니다. 사용할 모델을 확정하세요.",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"SEND_ANALYSIS_COMPLETION_EMAIL=%s, project_info=%s",
|
||||
SEND_ANALYSIS_COMPLETION_EMAIL,
|
||||
bool(project_info),
|
||||
)
|
||||
if SEND_ANALYSIS_COMPLETION_EMAIL and project_info and project_info.get("user_email"):
|
||||
logger.info("WF1 완료 이메일 발송 시작: to=%s", project_info["user_email"])
|
||||
await send_analysis_completion_email(
|
||||
project_id=project_id,
|
||||
project_name=str(project_info.get("project_name") or project_id),
|
||||
to_email=str(project_info["user_email"]),
|
||||
analysis_result=analysis_result,
|
||||
)
|
||||
logger.info("WF1 완료 이메일 발송 완료: project_id=%s", project_id)
|
||||
else:
|
||||
logger.info(
|
||||
"WF1 완료 이메일 발송 스킵: SEND_ANALYSIS_COMPLETION_EMAIL=%s, has_email=%s",
|
||||
SEND_ANALYSIS_COMPLETION_EMAIL,
|
||||
project_info and project_info.get("user_email") is not None,
|
||||
)
|
||||
logger.info("WF1 백그라운드 분석 완료: project_id=%s", project_id)
|
||||
except Exception as exc:
|
||||
logger.exception("WF1 백그라운드 분석 실패: project_id=%s", project_id)
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await fail_stage(cursor, str(project_id), 1, str(exc))
|
||||
await connection.commit()
|
||||
if project_info and project_info.get("user_email"):
|
||||
await send_analysis_error_email(
|
||||
project_id=project_id,
|
||||
project_name=str(project_info.get("project_name") or project_id),
|
||||
to_email=str(project_info["user_email"]),
|
||||
error_message=str(exc),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{project_id}/files", response_model=FileUploadResponse)
|
||||
async def upload_project_files(
|
||||
project_id: UUID,
|
||||
files: list[UploadFile] = File(...),
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
) -> FileUploadResponse | JSONResponse:
|
||||
"""프로젝트 입력 파일을 저장·분석하고 DB 메타데이터를 기록한다."""
|
||||
if not files or len(files) > UPLOAD_MAX_FILES:
|
||||
@@ -391,6 +250,7 @@ async def upload_project_files(
|
||||
trigger_wf1_analysis_and_email(
|
||||
project_id=project_id,
|
||||
input_file_id=point_cloud_result.input_file_id,
|
||||
user_role=str(session["role"]),
|
||||
),
|
||||
task_name=f"b04-wf1-auto-{project_id}",
|
||||
)
|
||||
@@ -525,6 +385,7 @@ async def upload_project_chunk(
|
||||
async def finalize_project_upload(
|
||||
project_id: UUID,
|
||||
payload: UploadFinalizeRequest,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
) -> FileUploadResponse | JSONResponse:
|
||||
"""청크 업로드를 최종 병합하고 input_files 메타데이터를 기록한다."""
|
||||
pool = get_db_pool()
|
||||
@@ -617,6 +478,7 @@ async def finalize_project_upload(
|
||||
trigger_wf1_analysis_and_email(
|
||||
project_id=project_id,
|
||||
input_file_id=result.input_file_id,
|
||||
user_role=str(session["role"]),
|
||||
),
|
||||
task_name=f"b04-wf1-auto-{project_id}",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user