diff --git a/B03_FileInput/B03_FileInput_Service_Chain.py b/B03_FileInput/B03_FileInput_Service_Chain.py index 14127036..cd3052e9 100644 --- a/B03_FileInput/B03_FileInput_Service_Chain.py +++ b/B03_FileInput/B03_FileInput_Service_Chain.py @@ -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) diff --git a/B05_Profile/B05_Profile_Router_Replan.py b/B05_Profile/B05_Profile_Router_Replan.py index 61e7902a..783ba8fe 100644 --- a/B05_Profile/B05_Profile_Router_Replan.py +++ b/B05_Profile/B05_Profile_Router_Replan.py @@ -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: diff --git a/B06_Section/B06_Section_Server_Calc_Prebuild.py b/B06_Section/B06_Section_Server_Calc_Prebuild.py index 306559e3..78645c13 100644 --- a/B06_Section/B06_Section_Server_Calc_Prebuild.py +++ b/B06_Section/B06_Section_Server_Calc_Prebuild.py @@ -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