feat(B03): 초기 업로드부터 계획노선 폴리라인을 노선 정본으로 사용
자동 체인 2.7 단계 추가 — 초기 폴리라인을 세운 뒤 노선을 다시 읽어 그것을 BP·경유점·EP 로 세우고 algorithm=as_planned 로 넘김. 전에는 폴리라인 파일만 만들고 노선은 원본 점군으로 풀었음. 그래서 종횡단 측점·유토곡선·3D 코리도가 곡선 없는 점군 위에 섰음. 실측(용화 3건): DB 노선 정점 331·323 vs 초기 폴리라인 145. 노선을 한 번이라도 편집한 프로젝트만 145 로 맞아 있었음. 자체검증 — 2.7 재판독이 145 정점을 돌려주고 BP·EP 좌표가 expected_route.csv 첫·끝 줄과 같음. pytest 409 passed. 계측 로그(_log_steps) 동봉 — 재확정 체인 단계별 경과시간용. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ WF1(지표면 분석·자동 확정)이 끝나면 사용자가 화면에 없어
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
@@ -25,6 +26,18 @@ from fastapi.responses import JSONResponse
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _log_steps(title: str, marks: list[tuple[str, float]]) -> None:
|
||||
"""단계별 경과시간을 한 줄로 남긴다 — 어디서 시간을 쓰는지 보려는 계측용."""
|
||||
total = (marks[-1][1] - marks[0][1]) or 1e-9
|
||||
parts = [
|
||||
f"{name} {marks[i][1] - marks[i - 1][1]:.1f}s"
|
||||
f"({(marks[i][1] - marks[i - 1][1]) / total * 100:.0f}%)"
|
||||
for i, (name, _) in enumerate(marks)
|
||||
if i
|
||||
]
|
||||
logger.info("[계측] %s 총 %.1fs = %s", title, total, " | ".join(parts))
|
||||
|
||||
|
||||
def _planned_route_points_in_project_crs(
|
||||
project_root: Path,
|
||||
surface: dict[str, Any] | None = None,
|
||||
@@ -70,6 +83,7 @@ async def _prepare_drainage_pipes_and_reprofile(
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_system import SECTION_CROSS_HALF_WIDTH_M
|
||||
|
||||
marks = [("시작", time.perf_counter())]
|
||||
try:
|
||||
# 1) 배수유역 분석(30초 내외). 저장분이 있으면 그대로 쓴다.
|
||||
# 저장분은 노선이 바뀌었는지 보지 않으므로, 노선을 다시 푼 뒤에는 refresh로 부른다.
|
||||
@@ -82,6 +96,8 @@ async def _prepare_drainage_pipes_and_reprofile(
|
||||
)
|
||||
return False
|
||||
|
||||
marks.append(("배수유역 분석", time.perf_counter()))
|
||||
|
||||
# 2) 기본 관(도로 × 상류 세류선) + 최대 간격 자동 보충을 정본으로 저장한다.
|
||||
pipes = await put_pipe_points(project_id, None)
|
||||
if isinstance(pipes, JSONResponse):
|
||||
@@ -93,6 +109,8 @@ async def _prepare_drainage_pipes_and_reprofile(
|
||||
return False
|
||||
pipe_count = int(pipes.get("saved_count") or 0)
|
||||
|
||||
marks.append(("관 지점 확정", time.perf_counter()))
|
||||
|
||||
# 3) 저장된 관 자리를 물려 계획선을 다시 산출한다. 반폭은 이미 쓰던 값을 유지한다.
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
@@ -110,6 +128,8 @@ async def _prepare_drainage_pipes_and_reprofile(
|
||||
regenerated.status_code,
|
||||
)
|
||||
return False
|
||||
marks.append(("종횡단 측점 재생성", time.perf_counter()))
|
||||
_log_steps("배수·관·측점", marks)
|
||||
logger.info(
|
||||
"자동 설계 체인 배수유역·배관 정착 계획선 완료: project_id=%s route_id=%s 관=%d개",
|
||||
project_id,
|
||||
@@ -211,12 +231,24 @@ async def run_auto_design_chain(
|
||||
# 「화면을 안 열면 값이 없다」가 되므로 초기값은 서버가 낸다(0-4 원칙).
|
||||
await _ensure_initial_polyline(project_id, project_root, points)
|
||||
|
||||
# 2.7) 노선의 기준을 **초기 폴리라인**으로 갈아 끼운다(2026-09-06 사용자 지시).
|
||||
# 다시 읽으면 `load_design_route` 가 방금 만든 초기 폴리라인을 집는다
|
||||
# (읽는 순서: 수정본 → 초기 폴리라인 → 예상노선). 종횡단 측점·유토곡선·
|
||||
# 3D 코리도가 전부 이 노선에서 나오므로, 여기서 갈아 끼우지 않으면 화면에는
|
||||
# 곡선 없는 원본 점군(용화: 3.3m 간격 331점)이 그대로 선다.
|
||||
polyline_points = _planned_route_points_in_project_crs(project_root, defaults, route_range)
|
||||
if polyline_points and len(polyline_points) >= 2:
|
||||
points = polyline_points
|
||||
|
||||
# 3) B05 경로 계산
|
||||
request = RouteSolveRequest(
|
||||
filter_key=str(defaults["source_filter"]),
|
||||
method=str(defaults["method"]),
|
||||
smooth=bool(defaults["smooth"]),
|
||||
surface_model_id=surface_model_id,
|
||||
# 계획노선을 **그대로** 쓴다 — 격자 재탐색은 이 선을 자기 제약으로 다시 풀어
|
||||
# 곡선을 뭉개거나 통째로 막는다(재확정 체인과 같은 이유, 0-10).
|
||||
algorithm="as_planned",
|
||||
bp=RoutePoint(**points[0]),
|
||||
ep=RoutePoint(**points[-1]),
|
||||
cp=[
|
||||
@@ -397,6 +429,7 @@ async def run_redesign_chain(
|
||||
|
||||
pool = get_db_pool()
|
||||
project_root: Path | None = None
|
||||
marks = [("시작", time.perf_counter())]
|
||||
try:
|
||||
async with pool.acquire() as connection:
|
||||
latest = await get_latest_route(connection, project_id)
|
||||
@@ -437,6 +470,8 @@ async def run_redesign_chain(
|
||||
)
|
||||
return "노선 제어점(BP·EP)이 없어 재계산할 수 없습니다."
|
||||
|
||||
marks.append(("준비·사용자입력 회수", time.perf_counter()))
|
||||
|
||||
# 2) B05 재계산 — 지표면 관련 값만 새 확정 선택으로 교체, 나머지는 사용자 저장분.
|
||||
request = RouteSolveRequest(
|
||||
filter_key=str(selection.get("source_filter") or params.get("filter_key")),
|
||||
@@ -491,6 +526,7 @@ async def run_redesign_chain(
|
||||
)
|
||||
return f"노선 재계산 실패({solve_result.status_code}): {reason}"
|
||||
new_route_id = int(solve_result.route_id)
|
||||
marks.append(("노선 재계산(solve)", time.perf_counter()))
|
||||
logger.info(
|
||||
"재확정 체인 B05 재계산 완료: project_id=%s %s→%s",
|
||||
project_id,
|
||||
@@ -514,10 +550,13 @@ async def run_redesign_chain(
|
||||
confirm_result.status_code,
|
||||
)
|
||||
return f"노선 확정 실패({confirm_result.status_code})"
|
||||
marks.append(("B05 확정", time.perf_counter()))
|
||||
# 노선이 바뀌었으므로 배수유역·관 지점도 새 노선 기준으로 다시 만든다 — 옛 관 자리로
|
||||
# 계획선을 앉히면 전부 어긋나고, 저장분은 노선 변경을 스스로 알지 못한다.
|
||||
await _prepare_drainage_pipes_and_reprofile(project_id, new_route_id, refresh=True)
|
||||
|
||||
marks.append(("배수·관·측점 재생성", time.perf_counter()))
|
||||
|
||||
# 5) 이제 측점이 새 노선 기준으로 다 섰다 — 옛 사용자 설계를 **누가거리로** 얹는다.
|
||||
# 묶어 쓰므로 행 수와 무관하게 왕복 두 번이다(`merge_cross_section_designs`).
|
||||
try:
|
||||
@@ -546,6 +585,8 @@ async def run_redesign_chain(
|
||||
)
|
||||
logger.info("재확정 체인 설계 이월: project_id=%s %d건", project_id, carried)
|
||||
|
||||
marks.append(("설계 이월", time.perf_counter()))
|
||||
|
||||
old_options = ((old_longitudinal or {}).get("data") or {}).get("options") or {}
|
||||
section_request = (
|
||||
SectionConfirmRequest(standard_cross_section=old_options["standard_cross_section"])
|
||||
@@ -562,6 +603,8 @@ async def run_redesign_chain(
|
||||
sections_result.status_code,
|
||||
)
|
||||
return f"종횡단 확정 실패({sections_result.status_code})"
|
||||
marks.append(("B06 확정(서버 재계산 포함)", time.perf_counter()))
|
||||
_log_steps("재확정 체인", marks)
|
||||
logger.info(
|
||||
"재확정 체인 완료: project_id=%s route %s→%s (설계 %d건 이월)",
|
||||
project_id,
|
||||
|
||||
Reference in New Issue
Block a user