fix(B05,B06): 최소곡선반지름 조회가 없는 컬럼을 읽던 것 · 재계산 중단 사유 로그
보조 창이 노선 편집 [확인] 실측에서 둘을 잡음.
1. projects 에 design_speed_kph / terrain_type 컬럼이 없음(있는 것은 road_type 뿐).
매 요청마다 OperationalError(1054) 가 ERROR 로그로 남고 기본값 폴백으로 넘어갔음.
- 임도 종류는 projects.road_type, 설계속도·지형은 워크플로 stage 2 params 에서 읽음.
- 산식은 이미 있던 B05_Profile_Engine_Grade.legal_plan_radius_min_m 를 씀 —
내가 같은 표를 다시 짜 두었던 것을 지움(중복 제거).
2. 재확정 체인이 solve_route 400 으로 끊길 때 상태 코드만 남겨 원인을 못 짚었음.
본문(사유)까지 로그에 남김. 노선 편집 [확인]이 조용히 끊겨 배수유역·관은 새 노선으로
가고 종횡단만 옛 노선에 남는 어긋남이 굳었던 자리임.
recompute 의 상세 조회와 DB 두 건도 asyncio.gather 로 묶음(원격 DB 왕복 약 12ms/질의).
시험 409 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -443,10 +443,17 @@ async def run_redesign_chain(
|
||||
)
|
||||
solve_result: Any = await solve_route(project_id, request)
|
||||
if isinstance(solve_result, JSONResponse):
|
||||
# 사유까지 남긴다 — 상태 코드만으로는 무엇이 막았는지 못 짚는다(2026-09-06
|
||||
# 노선 편집 [확인]이 조용히 400 으로 끊겨 종횡단만 옛 노선에 남았음).
|
||||
try:
|
||||
reason = bytes(solve_result.body).decode("utf-8", "replace")[:300]
|
||||
except Exception: # noqa: BLE001 — 로그용이라 실패해도 흐름을 막지 않는다
|
||||
reason = "(본문 없음)"
|
||||
logger.error(
|
||||
"재확정 체인 중단(B05 재계산 실패): project_id=%s status=%s",
|
||||
"재확정 체인 중단(B05 재계산 실패): project_id=%s status=%s reason=%s",
|
||||
project_id,
|
||||
solve_result.status_code,
|
||||
reason,
|
||||
)
|
||||
return
|
||||
new_route_id = int(solve_result.route_id)
|
||||
|
||||
@@ -122,31 +122,44 @@ def _ensure_expected_route(project_root: Path) -> str:
|
||||
async def _min_plan_radius_m(project_id: UUID) -> float:
|
||||
"""이 프로젝트에 적용할 법정 최소곡선반지름(m) — 임도 종류·설계속도·지형으로 고른다.
|
||||
|
||||
값의 출처는 지식DB(`01_임도/02_상세설계/평면선형.md`, 별표2 Ⅰ.2.다)이고 코드에서는
|
||||
`config_system_design` 이 그대로 들고 있다. 프로젝트 설정을 못 읽으면 가장 완화된
|
||||
조건(설계속도 20·특수지형)으로 떨어진다 — 막지 않고 위반 표시만 하기 때문이다.
|
||||
값의 출처는 지식DB(`01_임도/02_상세설계/평면선형.md`, 별표2 Ⅰ.2.다)이고 산식은 이미
|
||||
`B05_Profile_Engine_Grade.legal_plan_radius_min_m` 에 있다 — 여기서 다시 짜지 않는다.
|
||||
|
||||
읽는 자리 — 임도 종류는 `projects.road_type`, 설계속도·지형은 **워크플로 stage 2 params**
|
||||
(노선 풀기 요청이 남긴 값)다. `projects` 에는 설계속도·지형 칸이 없다(2026-09-06 확인:
|
||||
있는 것은 `road_type` 뿐). 못 읽으면 가장 완화된 조건으로 떨어진다 — 막지 않고 위반
|
||||
표시만 하기 때문이다.
|
||||
"""
|
||||
from B05_Profile.B05_Profile_Engine_Grade import resolve_design_speed
|
||||
from config.config_system_design import FOREST_ROAD_PROFILE_CRITERIA
|
||||
import aiomysql
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Grade import legal_plan_radius_min_m, resolve_design_speed
|
||||
from common_util.common_util_workflow_state import get_workflow_state
|
||||
|
||||
table = FOREST_ROAD_PROFILE_CRITERIA["min_plan_radius_m"]
|
||||
grade_class, design_speed, terrain = "work", None, "special"
|
||||
try:
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"SELECT road_type, design_speed_kph, terrain_type FROM projects WHERE id = %s",
|
||||
(str(project_id),),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
grade_class = str(row[0] or grade_class)
|
||||
design_speed = int(row[1]) if row[1] else None
|
||||
terrain = "normal" if str(row[2] or "").lower() == "normal" else "special"
|
||||
async with pool.acquire() as connection:
|
||||
async with connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"SELECT road_type FROM projects WHERE id = %s", (str(project_id),)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if row and row[0]:
|
||||
grade_class = str(row[0])
|
||||
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
||||
state = await get_workflow_state(cursor, str(project_id))
|
||||
stage2 = next(
|
||||
(s for s in (state or {}).get("stages", []) if int(s.get("stage_no", -1)) == 2), None
|
||||
)
|
||||
params = (stage2 or {}).get("params") or {}
|
||||
speed = params.get("design_speed_kph")
|
||||
if isinstance(speed, (int, float)):
|
||||
design_speed = int(speed)
|
||||
if params.get("terrain_type") in ("normal", "special"):
|
||||
terrain = str(params["terrain_type"])
|
||||
except Exception: # noqa: BLE001 — 설정을 못 읽어도 폴리라인화는 이어 간다
|
||||
logger.exception("최소곡선반지름 설정을 못 읽어 기본값을 씁니다: %s", project_id)
|
||||
speed = resolve_design_speed(grade_class, design_speed)
|
||||
return float(table.get(speed, table[20])[terrain])
|
||||
return legal_plan_radius_min_m(resolve_design_speed(grade_class, design_speed), terrain)
|
||||
|
||||
|
||||
def _write_planned_polyline(path: Path, points: list[tuple[float, float]], radius_m: float) -> dict:
|
||||
|
||||
@@ -37,7 +37,7 @@ from B06_Section.B06_Section_Repository import (
|
||||
from B06_Section.B06_Section_Repository_Bulk import merge_cross_section_designs
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_db import get_db_pool, run_with_connection
|
||||
from config.config_system import (
|
||||
EARTHWORK_CONVERSION_FACTORS,
|
||||
EARTHWORK_HAUL_EQUIPMENT_LIMITS_M,
|
||||
@@ -99,18 +99,20 @@ async def _recompute(project_id: UUID | str, route_id: int) -> int:
|
||||
from B06_Section.B06_Section_Router import get_section_detail
|
||||
|
||||
project_uuid = UUID(str(project_id))
|
||||
response = await get_section_detail(project_uuid, route_id)
|
||||
# 상세 만들기(파일 읽기 위주)와 DB 두 건은 서로 기다릴 이유가 없다 — 같이 보낸다.
|
||||
# 원격 DB 라 순차로 내면 왕복이 그대로 더해진다(질의 하나 약 12ms, 2026-09-06 실측).
|
||||
pool = get_db_pool()
|
||||
response, stored_path, longitudinal_row = await asyncio.gather(
|
||||
get_section_detail(project_uuid, route_id),
|
||||
run_with_connection(get_project_storage_relative_path, project_uuid),
|
||||
run_with_connection(get_longitudinal_section, project_uuid, route_id),
|
||||
)
|
||||
payload = getattr(response, "model_dump", None)
|
||||
if payload is None: # JSONResponse = 실패
|
||||
logger.warning("서버 재계산: 종횡단 상세를 못 받음 (route_id=%s)", route_id)
|
||||
return 0
|
||||
detail = payload(mode="json")
|
||||
sections = detail.get("cross_sections") or []
|
||||
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
stored_path = await get_project_storage_relative_path(connection, project_uuid)
|
||||
longitudinal_row = await get_longitudinal_section(connection, project_uuid, route_id)
|
||||
project_root = Path(resolve_stored_project_path(stored_path))
|
||||
from B06_Section.B06_Section_Router_Design import stored_standard_cross_section
|
||||
|
||||
|
||||
Reference in New Issue
Block a user