From 074d9aec1316934fac6d5f8b2351f043cc01b5a4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 29 Sep 2026 19:28:20 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05):=20=EA=B3=84=ED=9A=8D=EB=85=B8?= =?UTF-8?q?=EC=84=A0=20=ED=8E=B8=EC=A7=91=20=EC=B0=BD=20=E2=80=94=20[?= =?UTF-8?q?=EB=85=B8=EC=84=A0=20=EC=B6=94=EC=B2=9C]=20=EB=AA=A8=EB=8B=AC(?= =?UTF-8?q?=EC=95=8C=EC=95=BD=20=EB=84=B7=20=C2=B7=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EC=A7=80=EB=8F=84=20=C2=B7=20=EB=B9=84?= =?UTF-8?q?=EC=A4=91=20=C2=B7=20=EC=A0=9C=EC=95=BD=20=EC=B9=B8)=20=C2=B7?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=ED=99=94=20=3D=20=EC=A0=84=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B4=88=EA=B8=B0=EA=B0=92=20=EB=85=B8=EC=84=A0=20?= =?UTF-8?q?=C2=B7=20=EC=97=B0=20=EC=83=81=ED=83=9C=EB=A1=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=B6=94=20=EB=BA=8C=20(PLAN=2023-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 도구 막대: 추천 묶음을 빼고 「1m 등고선」 왼쪽에 구분선 + [노선 추천] 하나 · 「연 상태로」 뺌 · 「현재 추천: ○○」 표시 - 모달(_Recommend): 알약 넷(예상노선 추적 기본) · 비중 슬라이더 · 제약 칸 · 미리 보기 지도 · 비교값 · 못 지킨 구간 · [적용] · [닫기] - [초기화]: 재계산 없이 GET /route/plan 의 initial_route(전처리 초기값 노선)를 놓음 - 서버: GET /route/plan 에 initial_route(노드 · 곡선) 추가 - 시험: 헬퍼 · 왕복 시험 갱신(추적 표기 · initial_route · 지도 선) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019BVZd1M6WUf1YEy9gbAE1G --- B05_Profile/B05_Profile_Api_Replan.ts | 2 + B05_Profile/B05_Profile_Api_RouteInitial.ts | 2 +- B05_Profile/B05_Profile_Router_Replan.py | 19 + .../B05_Profile_UI_RouteEdit_Chrome.ts | 8 +- .../B05_Profile_UI_RouteEdit_History.ts | 16 +- .../B05_Profile_UI_RouteEdit_Initial.ts | 337 +++++------------ .../B05_Profile_UI_RouteEdit_Initial_Model.ts | 52 ++- .../B05_Profile_UI_RouteEdit_Recommend.ts | 353 ++++++++++++++++++ .../B05_Profile_UI_Style_RouteEdit.css | 107 ------ .../B05_Profile_UI_Style_RouteRecommend.css | 223 +++++++++++ .../tester/helper_b05_routeedit_initial.cjs | 27 ++ .../tester/test_b05_routeedit_initial.py | 23 +- .../test_b05_routeedit_initial_roundtrip.py | 33 +- 13 files changed, 823 insertions(+), 379 deletions(-) create mode 100644 B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts create mode 100644 B05_Profile/B05_Profile_UI_Style_RouteRecommend.css diff --git a/B05_Profile/B05_Profile_Api_Replan.ts b/B05_Profile/B05_Profile_Api_Replan.ts index b1690721..1b49d5d6 100644 --- a/B05_Profile/B05_Profile_Api_Replan.ts +++ b/B05_Profile/B05_Profile_Api_Replan.ts @@ -68,6 +68,8 @@ export interface RoutePlanResponse { edited: boolean; /** 현재 초기값 — 서버가 [확인] 때 프로젝트에 적어 둔 초기노선 갈래(계획서 20). */ current_initial?: { logic: string; weights: Record | null }; + /** 전처리가 만든 초기 노선(예상노선 추적)의 노드 · 곡선 — [초기화]가 재계산 없이 놓는다. */ + initial_route?: { nodes: RoutePlanNode[]; curves: RoutePlanCurve[] }; } export interface RouteReplanResponse { diff --git a/B05_Profile/B05_Profile_Api_RouteInitial.ts b/B05_Profile/B05_Profile_Api_RouteInitial.ts index 7f8b6653..b846d9e9 100644 --- a/B05_Profile/B05_Profile_Api_RouteInitial.ts +++ b/B05_Profile/B05_Profile_Api_RouteInitial.ts @@ -14,7 +14,7 @@ import { requestJson } from "./B05_Profile_Api_Replan"; /** 갈래 셈 대기 상한 — 노선 여러 벌을 셈해 견주므로 넉넉히 잡는다. */ const INITIAL_TIMEOUT_MS = 5 * 60 * 1000; -/** 로직 이름 — 서버와 같은 낱말을 쓴다. `follow` 는 예상노선 추종(처음 초기값). */ +/** 로직 이름 — 서버와 같은 낱말을 쓴다. `follow` 는 예상노선 추적(처음 초기값). */ export type RouteInitialLogic = "follow" | "earthwork" | "gentle" | "weighted"; /** 비중 계산의 세 비중(%) — 합 100. */ diff --git a/B05_Profile/B05_Profile_Router_Replan.py b/B05_Profile/B05_Profile_Router_Replan.py index 42edf351..1a3e0ac8 100644 --- a/B05_Profile/B05_Profile_Router_Replan.py +++ b/B05_Profile/B05_Profile_Router_Replan.py @@ -267,6 +267,24 @@ def _read_curves(path: Path) -> list[dict]: return curves if isinstance(curves, list) else [] +def _initial_shape(project_root: Path, fallback: list, radius_m: float) -> dict[str, list]: + """전처리가 만든 **초기 노선**의 노드 · 곡선 — 화면 [초기화]가 재계산 없이 놓는 자리. + + 수정본이 있어도 초기 폴리라인 파일은 그대로라 항상 같은 노선이 나온다. + """ + path = planned_route_initial_path(project_root) + saved_nodes = _vertices_of(_nodes_path(path)) + outline = build_planned_polyline( + [(x, y) for x, y in (saved_nodes or _vertices_of(path) or fallback)], + min_radius_m=radius_m, + simplify=not saved_nodes, + ) + return { + "nodes": [node.as_dict() for node in outline.nodes], + "curves": _read_curves(path) or [curve.as_dict() for curve in outline.curves], + } + + def _write_planned_polyline( path: Path, points: list[tuple[float, float]], @@ -516,6 +534,7 @@ async def read_route_plan(project_id: UUID) -> dict[str, Any] | JSONResponse: "violation_count": outline.violation_count, "edited": bool(working), "current_initial": await asyncio.to_thread(read_initial_choice, project_root), + "initial_route": await asyncio.to_thread(_initial_shape, project_root, expected, radius_m), } diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts index c830b3bb..cbeeee06 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts @@ -40,7 +40,11 @@ export function createRouteEditChrome(): RouteEditChrome {