배분·운반거리 산식(_balance 523 + _settle 235 = 758줄)을 브라우저 번들에서 빼기 위한
배선. 계산은 여전히 한 벌 — 서버가 같은 TS 를 Node 로 돈다(CLAUDE.md 5장).
- POST /projects/{id}/sections/{route_id}/haul-plan 신설
(B06_Section_Router_HaulPlan.py). 브라우저가 낸 누가토량 결과를 받아 배분만 돌려줌.
화면이 쓰는 꼴 그대로 내보내 그리기 코드를 안 건드림. 표시 전용 — 정본은 저장 때 따로.
- B05_Profile_Api_HaulPlan.ts 신설 — 편집이 멈추면(400ms) 조용히 받아 두는 선반입기.
늦게 온 응답은 버림(최신 요청만 채택). 못 받아도 곡선 자체는 그대로 보임.
- common_util_mass_haul.massHaulPayload 가 배분을 값으로 물던 것을 끊음(extra 인자).
이 커플링 때문에 화면에서 안 불러도 번들에 남았음.
- 저장 경로는 곡선도 배분도 안 만듦 — 서버가 정본을 내므로 balloon 위치만 보냄.
- 죽은 파일 B06_Section_UI_Section_View_MassHaul.ts(236줄) 삭제.
자체검증(공용 브라우저, 용화 route 149) — 엔드포인트 200 · 196ms.
서버 배분과 브라우저 computeHaulPlan 결과가 JSON 문자열까지 동일(blocks 1 · steps 2 ·
spoil/borrow/hauled/transferred/fill_total 전부 일치). 시험 400 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Api_HaulPlan.ts
|
|
* 유토 **배분**(평형선·운반거리·장비)을 서버에서 미리 받아 두는 자리.
|
|
*
|
|
* 왜 서버인가(2026-09-06 사용자 확정) — 배분 산식은 노하우가 몰린 자리라 브라우저 번들에
|
|
* 남기지 않는다. 화면은 누가토량까지만 스스로 내고(`common_util_mass_haul`), 그 결과를
|
|
* 여기로 보내 배분을 받아 쥔다. 계산은 여전히 한 벌이다 — 서버가 같은 TS 를 Node 로 돈다.
|
|
*
|
|
* **조용히 따라오게 한다** — 편집이 멈추면 뒤에서 물어 두므로, 유토곡선 패널을 펼치는
|
|
* 순간에는 이미 도착해 있다. 늦게 온 응답은 버린다(최신 요청만 채택).
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import type { HaulPlan } from "@util/common_util_mass_haul_balance";
|
|
|
|
/** 서버가 돌려주는 배분 한 벌 — **화면이 쓰는 꼴 그대로**라 그리기 코드가 손대지 않는다.
|
|
* `import type` 이라 배분 모듈이 번들에 실리지 않는다(빌드에서 지워진다). */
|
|
export type HaulPlanPayload = HaulPlan | null;
|
|
|
|
/** 편집이 멈춘 것으로 볼 시간(ms). 계획고를 연속으로 누르는 동안은 안 보낸다. */
|
|
const SETTLE_MS = 400;
|
|
/** 배분 계산 대기 상한 — Node 실행 200ms 대라 넉넉히 잡는다. */
|
|
const TIMEOUT_MS = 20000;
|
|
|
|
export interface HaulPlanPrefetch {
|
|
/** 새 누가토량 결과가 나왔음을 알린다 — 잠잠해지면 서버에 물어본다. */
|
|
schedule: (result: unknown) => void;
|
|
/** 지금 쥐고 있는 배분. 아직 못 받았으면 null. */
|
|
current: () => HaulPlanPayload;
|
|
/** 화면을 떠날 때 예약을 지운다. */
|
|
dispose: () => void;
|
|
}
|
|
|
|
/**
|
|
* 배분 선반입기를 만든다. `onReady` 는 값이 새로 도착했을 때만 불린다 —
|
|
* 부르는 쪽은 그때 곡선을 다시 그리면 된다.
|
|
*/
|
|
export function createHaulPlanPrefetch(
|
|
projectId: string,
|
|
routeId: () => number | undefined,
|
|
onReady: () => void,
|
|
): HaulPlanPrefetch {
|
|
let timer = 0;
|
|
let sequence = 0;
|
|
let plan: HaulPlanPayload = null;
|
|
let pending: unknown = null;
|
|
|
|
async function send(result: unknown, seq: number): Promise<void> {
|
|
const route = routeId();
|
|
if (!route) return;
|
|
const controller = new AbortController();
|
|
const abort = window.setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
try {
|
|
const response = await fetch(
|
|
`${API_BASE_URL}/projects/${projectId}/sections/${route}/haul-plan`,
|
|
{
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ result }),
|
|
signal: controller.signal,
|
|
},
|
|
);
|
|
if (!response.ok) return;
|
|
const payload = (await response.json()) as { haul_plan?: HaulPlanPayload };
|
|
// 늦게 온 응답은 버린다 — 그 사이 사용자가 계획고를 더 만졌을 수 있다.
|
|
if (seq !== sequence) return;
|
|
plan = payload.haul_plan ?? null;
|
|
onReady();
|
|
} catch {
|
|
// 배분을 못 받아도 곡선 자체는 그대로 보인다 — 화면을 막지 않는다.
|
|
} finally {
|
|
window.clearTimeout(abort);
|
|
}
|
|
}
|
|
|
|
return {
|
|
schedule(result) {
|
|
pending = result;
|
|
sequence += 1;
|
|
const seq = sequence;
|
|
window.clearTimeout(timer);
|
|
timer = window.setTimeout(() => void send(pending, seq), SETTLE_MS);
|
|
},
|
|
current: () => plan,
|
|
dispose() {
|
|
window.clearTimeout(timer);
|
|
sequence += 1; // 남아 있는 응답을 모두 무효로 만든다.
|
|
},
|
|
};
|
|
}
|