perf(B05/B06): 진입 시 중복 서버 호출 두 건 제거
- 확정 지표면: 목록(surface/models)으로 id 를 찾고 3D 단계에서 surface/confirmed 를 또 부르던 것을 확정 응답 한 번으로 합침. 모델 id·범위가 그 응답에 다 있다. - 종횡단 설정값(sections/context): ④ 계산 결과로 보고 세션에 담음. B05·B06 을 오갈 때마다 다시 묻지 않는다. 노선 캐시를 버리는 자리에서 함께 버린다. 검증: tsc --noEmit 통과, pytest 389 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,10 @@ export const STATE_REGISTRY = {
|
||||
/* ── ④ 계산 결과 ─────────────────────────────────────────────────────── */
|
||||
/** 노선·종단 최신 응답. 페이지를 오갈 때 이 값으로 먼저 그린다. */
|
||||
latest: { bucket: "result", scope: "project", legacy: (p) => `b05:latest:${p}` },
|
||||
/** 종횡단 설정값(config 상수 + 프로젝트 임도 종류 + 노선 id). B05·B06 이 같은 값을
|
||||
* 받아 쓰므로 화면을 오갈 때마다 다시 묻지 않는다(2026-09-06 호출 정리).
|
||||
* 노선이 바뀌면 `latest` 와 함께 버린다. */
|
||||
"section-context": { bucket: "result", scope: "project" },
|
||||
/** 횡단 상세 응답 — 예전에는 메모리에만 있어 페이지를 떠나면 사라졌다. */
|
||||
"section-detail": { bucket: "result", scope: "route" },
|
||||
/** 서버가 준 규격 횡단 기본값 — 사용자가 만든 값이 아니라 **기억해 둔 서버 값**이다. */
|
||||
|
||||
@@ -323,4 +323,6 @@ export function writeRouteLatestCache(projectId: string, value: RouteLatestRespo
|
||||
|
||||
export function clearRouteLatestCache(projectId: string): void {
|
||||
clearState("latest", projectId);
|
||||
// 설정값에 노선 id 가 실려 있어 함께 버려야 옛 노선을 가리키지 않는다(2026-09-06).
|
||||
clearState("section-context", projectId);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
} from "../A00_Common/b_workflow_nav";
|
||||
import {
|
||||
fetchConfirmedSurface,
|
||||
listSurfaceModels,
|
||||
type SurfaceModelSummary,
|
||||
type SurfaceConfirmedResponse,
|
||||
} from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import {
|
||||
fetchLatestRoute,
|
||||
@@ -169,7 +168,10 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
* 선택은 3D·그래프·사이드바가 서로를 갱신하므로, `selectionSyncing` 가드로 재진입을 막아
|
||||
* 무한 재귀(스택 오버플로우·프리즈)를 방지한다.
|
||||
*/
|
||||
let confirmedSurface: SurfaceModelSummary | null = null;
|
||||
/** 확정 지표면 — 모델 id 와 3D 로딩에 필요한 범위만 들고 있는다(2026-09-06 호출 정리).
|
||||
* 예전에는 목록(`listSurfaceModels`)으로 id 를 찾고 3D 단계에서 `surface/confirmed`
|
||||
* 를 또 불러 요청이 두 번 나갔다. 확정 응답 하나에 둘 다 들어 있다. */
|
||||
let confirmedSurface: SurfaceConfirmedResponse | null = null;
|
||||
let latest: RouteLatestResponse | null = null;
|
||||
let roadWidths = DEFAULT_ROAD_WIDTHS;
|
||||
let currentSectionDetail: SectionDetailResponse | null = null;
|
||||
@@ -685,9 +687,9 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
latest = latestResponse;
|
||||
advanceLoading("확정 지표면 모델을 확인하는 중…");
|
||||
|
||||
// ③ 확정 지표면 모델 목록.
|
||||
const models = await listSurfaceModels(activeProjectId);
|
||||
confirmedSurface = models.models.find((model) => model.status === "CONFIRMED") ?? null;
|
||||
// ③ 확정 지표면 — 모델 id·범위를 한 번에 받는다.
|
||||
const confirmed = await fetchConfirmedSurface(activeProjectId);
|
||||
confirmedSurface = confirmed.model_id === null ? null : confirmed;
|
||||
if (!confirmedSurface) {
|
||||
// 새 자료가 올라와 옛 결과가 지워진 상태 — 여기서 보여 줄 게 없다.
|
||||
leaveForDashboard();
|
||||
@@ -716,16 +718,15 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
const [surface, current] = [confirmedSurface, latest];
|
||||
if (!surface || !current) return;
|
||||
try {
|
||||
// 가장자리만 받는다 — 포인트클라우드 전체(수십 MB)는 안 받는다.
|
||||
const confirmed = await fetchConfirmedSurface(activeProjectId);
|
||||
if (!confirmed.bounds) throw new Error("지표면 범위 정보를 찾을 수 없습니다.");
|
||||
// 범위는 ③에서 받아 둔 확정 응답에 이미 들어 있다 — 다시 부르지 않는다.
|
||||
if (!surface.bounds) throw new Error("지표면 범위 정보를 찾을 수 없습니다.");
|
||||
await viewer.loadSurface(
|
||||
activeProjectId,
|
||||
surface.id,
|
||||
surface.model_id as number,
|
||||
current.surface_params.method,
|
||||
current.surface_params.smooth,
|
||||
current.surface_params.contour_interval_m,
|
||||
toBounds(confirmed.bounds),
|
||||
toBounds(surface.bounds),
|
||||
);
|
||||
renderLatest(current); // 지형이 올라온 뒤에야 마커·측점선이 지형 위에 놓인다.
|
||||
if (currentSectionDetail) renderStationLines(currentSectionDetail);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import type { SurfaceModelSummary } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import {
|
||||
clearRouteLatestCache,
|
||||
confirmRoute,
|
||||
@@ -46,7 +45,8 @@ function L(key: keyof typeof ui_locales): string {
|
||||
export interface PageActionContext {
|
||||
projectId: string;
|
||||
latest: () => RouteLatestResponse | null;
|
||||
confirmedSurface: () => SurfaceModelSummary | null;
|
||||
/** 확정 지표면 응답 — 모델 id 는 `model_id` 다(2026-09-06 호출 정리). */
|
||||
confirmedSurface: () => { model_id: number | null } | null;
|
||||
routeReady: () => boolean;
|
||||
viewer: () => ReturnType<typeof createRouteViewer>;
|
||||
panel: () => ReturnType<typeof createRoutePanel>;
|
||||
@@ -77,7 +77,7 @@ export async function solveRouteAction(ctx: PageActionContext): Promise<void> {
|
||||
filter_key: latest.surface_params.source_filter,
|
||||
method: latest.surface_params.method,
|
||||
smooth: latest.surface_params.smooth,
|
||||
surface_model_id: confirmedSurface.id,
|
||||
surface_model_id: confirmedSurface.model_id ?? undefined,
|
||||
algorithm: values.algorithm,
|
||||
bp: routePoint(points.bp),
|
||||
ep: routePoint(points.ep),
|
||||
@@ -149,7 +149,7 @@ export async function tempSaveAction(ctx: PageActionContext): Promise<void> {
|
||||
filter_key: latest?.surface_params.source_filter,
|
||||
method: latest?.surface_params.method,
|
||||
smooth: latest?.surface_params.smooth,
|
||||
surface_model_id: ctx.confirmedSurface()?.id,
|
||||
surface_model_id: ctx.confirmedSurface()?.model_id ?? undefined,
|
||||
irregular_stations: ctx
|
||||
.bridge()
|
||||
.irregularStations()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* 기존 호출 코드가 깨지지 않도록 여기서 그대로 다시 내보낸다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
||||
import type {
|
||||
CrossDesign,
|
||||
@@ -58,10 +59,21 @@ async function requestJson<T>(path: string, init: RequestInit): Promise<T> {
|
||||
}
|
||||
|
||||
/** 최신 확정 경로와 B04 확정값, config 기반 생성 기본값을 조회한다. */
|
||||
/** 종횡단 설정값 — ④ 계산 결과라 세션에 담아 두고 화면을 오갈 때마다 다시 묻지 않는다
|
||||
* (2026-09-06 호출 정리). 노선이 바뀌면 `clearSectionContextCache` 로 버린다. */
|
||||
export async function fetchSectionContext(projectId: string): Promise<SectionContextResponse> {
|
||||
return requestJson<SectionContextResponse>(`/projects/${projectId}/sections/context`, {
|
||||
method: "GET",
|
||||
});
|
||||
const cached = readState<SectionContextResponse>("section-context", projectId);
|
||||
if (cached) return cached;
|
||||
const fresh = await requestJson<SectionContextResponse>(
|
||||
`/projects/${projectId}/sections/context`,
|
||||
{ method: "GET" },
|
||||
);
|
||||
writeState("section-context", fresh, projectId);
|
||||
return fresh;
|
||||
}
|
||||
|
||||
export function clearSectionContextCache(projectId: string): void {
|
||||
clearState("section-context", projectId);
|
||||
}
|
||||
|
||||
/** 경로의 종단면 요약을 조회한다. */
|
||||
|
||||
Reference in New Issue
Block a user