github-actions[bot]
chore: sync uc-deliveries Space
f6213fc
import {
clonePlan,
refreshPlan,
refreshServerPlan,
} from './models.mjs';
import { fetchJson, showError } from './ui/components.mjs';
import { renderData } from './ui/data-tables.mjs';
import { createLayout } from './ui/layout.mjs';
import { sameRouteIdentity, syncLifecycleDataset } from './ui/lifecycle.mjs';
import { buildAnalysisBody, buildRecommendationBody } from './ui/modals.mjs';
import { renderMap, renderRouteList, renderSummary, renderTimelines } from './ui/overview.mjs';
const DEFAULT_DEMO = 'PHILADELPHIA';
export async function boot() {
const config = await fetchJson('/sf-config.json');
const backend = SF.createBackend({ baseUrl: '' });
const app = document.getElementById('sf-app');
let currentPlan = null;
let currentRoutes = null;
let currentDemo = DEFAULT_DEMO;
let mapCtrl = null;
let focusedVehicleId = null;
let routeRequestToken = 0;
// Route geometry is only trustworthy for one retained job, snapshot revision,
// and routing mode. This identity prevents stale map lines from surviving
// dataset switches, routing-mode changes, or newer solver snapshots.
let activeRouteIdentity = null;
let activeTab = 'overview';
let mapLocationSignature = null;
const statusBar = SF.createStatusBar({
constraints: [
'all_deliveries_assigned',
'vehicle_capacity',
'delivery_time_windows',
'total_travel_time',
],
});
const layout = createLayout({
app,
config,
statusBar,
actions: {
onSolve: () => loadAndSolve(),
onPause: () => solver.pause().catch(showError),
onResume: () => solver.resume().catch(showError),
onCancel: () => solver.cancel().catch(showError),
onAnalyze: () => openAnalysis(),
},
onTabChange: (tabId) => setActiveTab(tabId),
});
const solver = SF.createSolver({
backend,
statusBar,
onProgress: syncLifecycle,
onPauseRequested: syncLifecycle,
onSolution: async (snapshot, meta) => handleSnapshotEvent(snapshot, meta),
onPaused: async (snapshot, meta) => handleSnapshotEvent(snapshot, meta),
onResumed: syncLifecycle,
onCancelled: async (snapshot, meta) => handleSnapshotEvent(snapshot, meta),
onComplete: async (snapshot, meta) => handleSnapshotEvent(snapshot, meta),
onFailure: async (_message, meta, snapshot, analysis) => {
await handleSnapshotEvent(snapshot, meta);
if (analysis) layout.analysisModal.setBody(buildAnalysisBody(analysis));
},
onAnalysis: (analysis) => layout.analysisModal.setBody(buildAnalysisBody(analysis)),
onError: showError,
});
layout.reloadButton.addEventListener('click', async () => loadDemoData(layout.demoField.select.value));
layout.demoField.select.addEventListener('change', async () => loadDemoData(layout.demoField.select.value));
layout.routingField.select.addEventListener('change', () => {
if (!currentPlan) return;
invalidateRoutes();
currentPlan.routingMode = layout.routingField.select.value;
renderDraftPlan(currentPlan);
});
setActiveTab(activeTab);
await loadDemoData(DEFAULT_DEMO);
function setActiveTab(tabId) {
activeTab = tabId;
Object.entries(layout.panels).forEach(([id, panel]) => {
panel.classList.toggle('is-active', id === tabId);
});
}
async function loadDemoData(demoId) {
currentDemo = demoId;
invalidateRoutes();
if (currentPlan) {
renderMapView();
renderRouteListView();
}
const plan = await fetchJson(`/demo-data/${demoId}`);
plan.routingMode = plan.routingMode || layout.routingField.select.value;
renderServerPlan(plan);
}
async function loadAndSolve() {
if (!currentPlan) return;
invalidateRoutes();
renderDraftPlan({ ...currentPlan, routingMode: layout.routingField.select.value });
await cleanupTerminalJob();
await solver.start(clonePlan(currentPlan));
syncLifecycle();
}
async function cleanupTerminalJob() {
const state = solver.getLifecycleState();
if (!solver.getJobId() || state === 'IDLE' || state === 'PAUSED' || solver.isRunning()) return;
try {
await solver.delete();
} catch (_error) {
// Retained terminal cleanup is opportunistic before starting a new job.
}
}
async function handleSnapshotEvent(snapshot, meta) {
if (snapshot?.solution) {
renderServerPlan(snapshot.solution, meta);
}
await loadRoutes(meta);
syncLifecycle(meta);
}
async function loadRoutes(meta) {
const requested = routeIdentityFrom(meta);
if (!requested || !activeRouteIdentity || !sameRouteIdentity(requested, activeRouteIdentity)) return;
const token = ++routeRequestToken;
try {
const routes = await fetchJson(`/jobs/${requested.jobId}/routes?snapshot_revision=${requested.snapshotRevision}`);
const routingMode = routes.routingMode || requested.routingMode;
if (!routeResponseStillCurrent(token, requested, routingMode)) return;
currentRoutes = { ...routes, jobId: requested.jobId, snapshotRevision: requested.snapshotRevision, routingMode };
renderMapView();
renderRouteListView();
} catch (_error) {
if (token === routeRequestToken && activeRouteIdentity && sameRouteIdentity(requested, activeRouteIdentity)) {
currentRoutes = null;
renderMapView();
renderRouteListView();
}
}
}
function routeResponseStillCurrent(token, requested, routingMode) {
return (
token === routeRequestToken &&
activeRouteIdentity &&
sameRouteIdentity(requested, activeRouteIdentity) &&
currentPlan?.routingMode === requested.routingMode &&
routingMode === requested.routingMode
);
}
async function openAnalysis() {
if (!solver.getJobId()) return;
const analysis = await solver.analyzeSnapshot();
layout.analysisModal.setBody(buildAnalysisBody(analysis));
layout.analysisModal.open();
}
async function openRecommendations(deliveryId) {
if (!currentPlan) return;
const response = await fetch('/recommendations/delivery-insertions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plan: currentPlan, deliveryId, limit: 8 }),
});
if (!response.ok) throw new Error('Failed to load recommendations');
const payload = await response.json();
layout.recommendationModal.setBody(buildRecommendationBody(payload, (previewPlan) => {
renderServerPlan(previewPlan);
layout.recommendationModal.close();
}));
layout.recommendationModal.open();
}
function renderDraftPlan(plan) {
renderPlan(plan, refreshPlan);
}
function renderServerPlan(plan, meta = null) {
invalidateRoutes();
renderPlan(plan, refreshServerPlan);
// A solver snapshot is the only source that can authorize `/routes` reads.
activeRouteIdentity = meta ? routeIdentityFrom(meta) : null;
}
function renderPlan(plan, refresh) {
const nextMapLocationSignature = locationSignature(plan);
const shouldFitMap = nextMapLocationSignature !== mapLocationSignature;
currentPlan = refresh({ ...plan, routingMode: plan.routingMode || layout.routingField.select.value });
mapLocationSignature = nextMapLocationSignature;
layout.routingField.select.value = currentPlan.routingMode || 'road_network';
renderSummaryView();
renderRouteListView();
renderMapView({ fitBounds: shouldFitMap });
renderTimelinesView();
renderDataView();
}
function invalidateRoutes() {
routeRequestToken += 1;
currentRoutes = null;
focusedVehicleId = null;
// Route tables and map geometry must be reloaded for the next snapshot.
activeRouteIdentity = null;
}
function routeIdentityFrom(meta) {
const jobId = meta?.jobId != null ? String(meta.jobId) : solver.getJobId();
const snapshotRevision =
meta?.snapshotRevision != null ? meta.snapshotRevision : solver.getSnapshotRevision();
const routingMode = currentPlan?.routingMode || null;
if (!jobId || snapshotRevision == null || !routingMode) return null;
return { jobId: String(jobId), snapshotRevision: String(snapshotRevision), routingMode };
}
function renderSummaryView() {
renderSummary({ currentDemo, currentPlan, summaryCard: layout.summaryCard, summaryMetrics: layout.summaryMetrics });
}
function renderRouteListView() {
renderRouteList({
currentPlan,
focusedVehicleId,
routeList: layout.routeList,
onFocusVehicle: (vehicleId) => {
focusedVehicleId = focusedVehicleId === vehicleId ? null : vehicleId;
renderMapView();
renderRouteListView();
},
});
}
function renderMapView({ fitBounds = false } = {}) {
renderMap({
currentPlan,
currentRoutes,
focusedVehicleId,
fitBounds,
mapCtrl,
setMapCtrl: (next) => {
mapCtrl = next;
},
});
}
function renderTimelinesView() {
renderTimelines({
currentPlan,
vehicleTimeline: layout.vehicleTimeline,
deliveryTimeline: layout.deliveryTimeline,
});
}
function renderDataView() {
renderData({
currentPlan,
dataBody: layout.dataBody,
onRecommend: async (deliveryId) => {
try {
await openRecommendations(deliveryId);
} catch (error) {
showError(error);
}
},
});
}
function syncLifecycle(meta) {
syncLifecycleDataset(app, solver, meta);
}
function locationSignature(plan) {
const deliveries = (plan.deliveries || []).map((delivery) => `${delivery.id}:${delivery.lat}:${delivery.lng}`);
const vehicles = (plan.vehicles || []).map((vehicle) => `${vehicle.id}:${vehicle.homeLat}:${vehicle.homeLng}`);
return `${deliveries.join('|')}::${vehicles.join('|')}`;
}
}