/* ============================================================================= * B05_wf2_Route_Api_Fetch.ts * 2차 워크플로우(경로 설계) API 클라이언트 * * 백엔드 계약 (B05_wf2_Route_Router.py): * POST /api/projects/{project_id}/route/solve → 경로 탐색 + DB 기록 * POST /api/projects/{project_id}/route/confirm → 최신 경로 확정 * * 규칙: * - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지). * - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환. * ========================================================================== */ import { API_BASE_URL, API_TIMEOUT_MS, AUTH_TOKEN_KEY } from "@config/config_frontend"; /** 경로 제어점 (BP/EP/CP) */ export interface RoutePoint { x: number; y: number; order?: number; } /** 경로 탐색 실행 요청 (RouteSolveRequest) */ export interface RouteSolveRequest { filter_key: string; method?: string; smooth?: boolean; surface_model_id?: number | null; algorithm?: string; bp: RoutePoint; ep: RoutePoint; cp?: RoutePoint[]; grade_class?: string; min_curve_radius_m?: number | null; max_uphill_grade?: number | null; max_downhill_grade?: number | null; } /** 경로 탐색 실행 결과 (RouteSolveResponse) */ export interface RouteSolveResponse { status: string; project_id: string; route_id: number; total_length_m: number; metrics: Record; required_points_ok: boolean; route_data_path: string; } /** 경로 확정 결과 (RouteConfirmResponse) */ export interface RouteConfirmResponse { status: string; project_id: string; route_id: number; confirmed: boolean; } /** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환. */ async function requestJson(path: string, init: RequestInit): Promise { const controller = new AbortController(); const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS); const token = localStorage.getItem(AUTH_TOKEN_KEY); try { const response = await fetch(`${API_BASE_URL}${path}`, { ...init, headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init.headers ?? {}), }, signal: controller.signal, }); const payload = (await response.json()) as T & { message?: string }; if (!response.ok) { throw new Error(payload.message ?? `HTTP ${response.status}`); } return payload; } finally { window.clearTimeout(timeoutId); } } /** 경로 탐색을 실행한다 (비용면 생성 → Dijkstra/ridge-valley → GeoJSON 저장). */ export async function solveRoute( projectId: string, request: RouteSolveRequest, ): Promise { return requestJson(`/projects/${projectId}/route/solve`, { method: "POST", body: JSON.stringify(request), }); } /** 프로젝트의 최신 경로를 확정한다. */ export async function confirmRoute(projectId: string): Promise { return requestJson(`/projects/${projectId}/route/confirm`, { method: "POST", }); }