199 lines
8.7 KiB
Python
199 lines
8.7 KiB
Python
"""B06 종횡단 생성 FastAPI 라우터."""
|
|
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
|
from B05_wf2_Route.B05_wf2_Route_Repository import get_latest_route
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Engine import run_section_generation
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Engine_Section import SectionGenerationOptions
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Repository import (
|
|
confirm_sections_for_route,
|
|
create_longitudinal_section,
|
|
delete_sections_for_route,
|
|
get_longitudinal_section,
|
|
insert_cross_sections,
|
|
)
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Schema import (
|
|
SectionConfirmResponse,
|
|
SectionGenerateRequest,
|
|
SectionGenerateResponse,
|
|
SectionSummaryResponse,
|
|
)
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
from common_util.common_util_workflow_state import complete_stage, fail_stage, start_stage
|
|
from config.config_db import get_db_pool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/projects", tags=["B06 Profile Cross"])
|
|
|
|
|
|
def _build_options(request: SectionGenerateRequest) -> SectionGenerationOptions:
|
|
"""요청의 옵션(미지정은 config 기본값)으로 SectionGenerationOptions를 만든다."""
|
|
defaults = SectionGenerationOptions()
|
|
return SectionGenerationOptions(
|
|
station_interval_m=request.station_interval_m or defaults.station_interval_m,
|
|
cross_half_width_m=request.cross_half_width_m or defaults.cross_half_width_m,
|
|
cross_sample_interval_m=request.cross_sample_interval_m or defaults.cross_sample_interval_m,
|
|
long_sample_interval_m=request.long_sample_interval_m or defaults.long_sample_interval_m,
|
|
include_endpoint=defaults.include_endpoint,
|
|
)
|
|
|
|
|
|
@router.post("/{project_id}/sections/generate", response_model=SectionGenerateResponse)
|
|
async def generate_sections(
|
|
project_id: UUID, request: SectionGenerateRequest
|
|
) -> SectionGenerateResponse | JSONResponse:
|
|
"""확정 경로에서 종횡단을 생성·저장하고 DB에 기록한다."""
|
|
pool = get_db_pool()
|
|
try:
|
|
params = {
|
|
"route_id": request.route_id,
|
|
"filter_key": request.filter_key,
|
|
"method": request.method,
|
|
"smooth": request.smooth,
|
|
"station_interval_m": request.station_interval_m,
|
|
"cross_half_width_m": request.cross_half_width_m,
|
|
"cross_sample_interval_m": request.cross_sample_interval_m,
|
|
"long_sample_interval_m": request.long_sample_interval_m,
|
|
"crs": request.crs,
|
|
}
|
|
async with pool.acquire() as connection:
|
|
async with connection.cursor() as cursor:
|
|
await start_stage(cursor, str(project_id), 3, params)
|
|
await connection.commit()
|
|
|
|
stored_path = await get_project_storage_relative_path(connection, project_id)
|
|
project_root = Path(resolve_stored_project_path(stored_path))
|
|
|
|
latest = await get_latest_route(connection, project_id)
|
|
if not latest or latest["id"] != request.route_id:
|
|
return JSONResponse(
|
|
status_code=404,
|
|
content={"status": "error", "message": "대상 경로를 찾을 수 없습니다."},
|
|
)
|
|
route_data_path = latest["route_data_path"]
|
|
|
|
options = _build_options(request)
|
|
design = await asyncio.to_thread(
|
|
run_section_generation,
|
|
project_root,
|
|
route_data_path,
|
|
request.filter_key,
|
|
request.method,
|
|
request.smooth,
|
|
options=options,
|
|
crs=request.crs,
|
|
)
|
|
|
|
await connection.begin()
|
|
try:
|
|
await delete_sections_for_route(connection, request.route_id)
|
|
longitudinal_id = await create_longitudinal_section(
|
|
connection,
|
|
project_id=project_id,
|
|
route_id=request.route_id,
|
|
data=design["longitudinal"]["data"],
|
|
longitudinal_file_path=design["longitudinal"]["file_path"],
|
|
)
|
|
await insert_cross_sections(
|
|
connection,
|
|
project_id=project_id,
|
|
route_id=request.route_id,
|
|
sections=design["cross_sections"],
|
|
)
|
|
async with connection.cursor() as cursor:
|
|
await complete_stage(cursor, str(project_id), 3)
|
|
await connection.commit()
|
|
except Exception:
|
|
await connection.rollback()
|
|
raise
|
|
|
|
return SectionGenerateResponse(
|
|
project_id=str(project_id),
|
|
route_id=request.route_id,
|
|
longitudinal_id=longitudinal_id,
|
|
cross_section_count=len(design["cross_sections"]),
|
|
length_m=design["longitudinal"]["data"]["length_m"],
|
|
longitudinal_file_path=design["longitudinal"]["file_path"],
|
|
)
|
|
except LookupError as exc:
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await fail_stage(cursor, str(project_id), 3, str(exc))
|
|
await connection.commit()
|
|
return JSONResponse(status_code=404, content={"status": "error", "message": str(exc)})
|
|
except FileNotFoundError as exc:
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await fail_stage(cursor, str(project_id), 3, str(exc))
|
|
await connection.commit()
|
|
return JSONResponse(status_code=404, content={"status": "error", "message": str(exc)})
|
|
except (OSError, ValueError) as exc:
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await fail_stage(cursor, str(project_id), 3, str(exc))
|
|
await connection.commit()
|
|
return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)})
|
|
except Exception as exc:
|
|
logger.exception("B06 종횡단 생성 실패: project_id=%s", project_id)
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await fail_stage(cursor, str(project_id), 3, str(exc))
|
|
await connection.commit()
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "종횡단 생성 처리 중 오류가 발생했습니다."},
|
|
)
|
|
|
|
|
|
@router.get("/{project_id}/sections/{route_id}", response_model=SectionSummaryResponse)
|
|
async def get_sections(project_id: UUID, route_id: int) -> SectionSummaryResponse | JSONResponse:
|
|
"""경로의 종단면 요약을 조회한다."""
|
|
pool = get_db_pool()
|
|
try:
|
|
async with pool.acquire() as connection:
|
|
longitudinal = await get_longitudinal_section(connection, project_id, route_id)
|
|
return SectionSummaryResponse(
|
|
project_id=str(project_id), route_id=route_id, longitudinal=longitudinal
|
|
)
|
|
except Exception:
|
|
logger.exception("B06 종횡단 조회 실패: project_id=%s", project_id)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "종횡단 조회 중 오류가 발생했습니다."},
|
|
)
|
|
|
|
|
|
@router.post("/{project_id}/sections/{route_id}/confirm", response_model=SectionConfirmResponse)
|
|
async def confirm_sections(
|
|
project_id: UUID, route_id: int
|
|
) -> SectionConfirmResponse | JSONResponse:
|
|
"""경로의 종횡단면을 확정(CONFIRMED)한다."""
|
|
pool = get_db_pool()
|
|
try:
|
|
async with pool.acquire() as connection:
|
|
existing = await get_longitudinal_section(connection, project_id, route_id)
|
|
if not existing:
|
|
return JSONResponse(
|
|
status_code=404,
|
|
content={"status": "error", "message": "확정할 종횡단이 없습니다."},
|
|
)
|
|
await connection.begin()
|
|
try:
|
|
await confirm_sections_for_route(connection, route_id)
|
|
async with connection.cursor() as cursor:
|
|
await complete_stage(cursor, str(project_id), 3)
|
|
await connection.commit()
|
|
except Exception:
|
|
await connection.rollback()
|
|
raise
|
|
return SectionConfirmResponse(project_id=str(project_id), route_id=route_id)
|
|
except Exception:
|
|
logger.exception("B06 종횡단 확정 실패: project_id=%s", project_id)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "종횡단 확정 처리 중 오류가 발생했습니다."},
|
|
)
|