From eec29a90df7fe246e76a7e4fcca37865442aaf32 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 7 Sep 2026 00:37:53 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05,B06):=20=EC=B5=9C=EC=86=8C=EA=B3=A1?= =?UTF-8?q?=EC=84=A0=EB=B0=98=EC=A7=80=EB=A6=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EB=8A=94=20=EC=BB=AC=EB=9F=BC=EC=9D=84=20?= =?UTF-8?q?=EC=9D=BD=EB=8D=98=20=EA=B2=83=20=C2=B7=20=EC=9E=AC=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EC=A4=91=EB=8B=A8=20=EC=82=AC=EC=9C=A0=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 보조 창이 노선 편집 [확인] 실측에서 둘을 잡음. 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) --- B03_FileInput/B03_FileInput_Service_Chain.py | 9 +++- B05_Profile/B05_Profile_Router_Replan.py | 49 ++++++++++++------- .../B06_Section_Server_Calc_Prebuild.py | 16 +++--- 3 files changed, 48 insertions(+), 26 deletions(-) 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