sgerp-frontend-lib 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/timezones.d.ts +7 -2
- package/dist/constants/timezones.d.ts.map +1 -1
- package/dist/constants/timezones.js +17 -2
- package/dist/locales/locale_en.d.ts.map +1 -1
- package/dist/locales/locale_en.js +159 -0
- package/dist/locales/locale_ja.d.ts.map +1 -1
- package/dist/locales/locale_ja.js +112 -0
- package/dist/sgerp/index.d.ts +2 -2
- package/dist/sgerp/index.d.ts.map +1 -1
- package/dist/sgerp/index.js +8 -2
- package/dist/sgerp/simulation-logic/fetchUtils.d.ts +18 -0
- package/dist/sgerp/simulation-logic/fetchUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/fetchUtils.js +194 -0
- package/dist/sgerp/simulation-logic/index.d.ts +4 -1
- package/dist/sgerp/simulation-logic/index.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/index.js +9 -1
- package/dist/sgerp/simulation-logic/vrpStatsCalculator.d.ts +52 -0
- package/dist/sgerp/simulation-logic/vrpStatsCalculator.d.ts.map +1 -0
- package/dist/sgerp/simulation-logic/vrpStatsCalculator.js +247 -0
- package/dist/sgerp/simulation-logic/vrpStatsUtils.d.ts +17 -0
- package/dist/sgerp/simulation-logic/vrpStatsUtils.d.ts.map +1 -0
- package/dist/sgerp/simulation-logic/vrpStatsUtils.js +48 -0
- package/dist/sgerp/utils/timezone.d.ts +15 -0
- package/dist/sgerp/utils/timezone.d.ts.map +1 -0
- package/dist/sgerp/utils/timezone.js +19 -0
- package/dist/sgerp/vrptoolbox-analyzer.d.ts +39 -0
- package/dist/sgerp/vrptoolbox-analyzer.d.ts.map +1 -0
- package/dist/sgerp/vrptoolbox-analyzer.js +210 -0
- package/dist/vrptoolbox/index.d.ts +1 -0
- package/dist/vrptoolbox/index.d.ts.map +1 -1
- package/dist/vrptoolbox/index.js +16 -0
- package/dist/vrptoolbox/vrptoolbox-utils.d.ts +133 -0
- package/dist/vrptoolbox/vrptoolbox-utils.d.ts.map +1 -0
- package/dist/vrptoolbox/vrptoolbox-utils.js +207 -0
- package/package.json +3 -1
|
@@ -9,6 +9,7 @@ exports.fetchOperationsLocationsByProject = fetchOperationsLocationsByProject;
|
|
|
9
9
|
exports.fetchGeofences = fetchGeofences;
|
|
10
10
|
exports.fetchSimulationGeofence = fetchSimulationGeofence;
|
|
11
11
|
exports.fetchSimulationData = fetchSimulationData;
|
|
12
|
+
exports.fetchVRPToolboxData = fetchVRPToolboxData;
|
|
12
13
|
const client_1 = require("../api/client");
|
|
13
14
|
const referenceUtils_1 = require("./referenceUtils");
|
|
14
15
|
/**
|
|
@@ -226,3 +227,196 @@ async function fetchSimulationData(api, simulationId, simulation = null) {
|
|
|
226
227
|
mainGeofence
|
|
227
228
|
};
|
|
228
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Fetch and convert VRPToolbox job data to SGERP simulation format
|
|
232
|
+
* Based on dashviewer's INITIALIZE_VRPTOOLBOX and createStateFromCommuteOffers logic
|
|
233
|
+
*
|
|
234
|
+
* @param vrpServer - VRPToolbox server URL
|
|
235
|
+
* @param taskId - VRPToolbox job/task ID
|
|
236
|
+
* @returns Simulation data in same format as fetchSimulationData
|
|
237
|
+
*/
|
|
238
|
+
async function fetchVRPToolboxData(vrpServer, taskId) {
|
|
239
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
240
|
+
// Fetch VRPToolbox job result with payload
|
|
241
|
+
const url = `${vrpServer}/jobs/${taskId}/result_with_payload`;
|
|
242
|
+
const response = await fetch(url);
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
throw new Error(`Failed to fetch VRPToolbox data: ${response.statusText}`);
|
|
245
|
+
}
|
|
246
|
+
const data = await response.json();
|
|
247
|
+
const { payload, result } = data;
|
|
248
|
+
console.log('[fetchVRPToolboxData] Raw VRPToolbox data:', {
|
|
249
|
+
payloadNodesCount: (_a = payload.nodes) === null || _a === void 0 ? void 0 : _a.length,
|
|
250
|
+
payloadVehiclesCount: (_b = payload.vehicles) === null || _b === void 0 ? void 0 : _b.length,
|
|
251
|
+
resultVehiclesKeys: Object.keys(result.vehicles || {}),
|
|
252
|
+
resultAssignedBookings: ((_c = result.assigned_bookings) === null || _c === void 0 ? void 0 : _c.length) || 0,
|
|
253
|
+
resultRejectedBookings: ((_d = result.rejected_bookings) === null || _d === void 0 ? void 0 : _d.length) || 0,
|
|
254
|
+
samplePayloadNode: (_e = payload.nodes) === null || _e === void 0 ? void 0 : _e[0],
|
|
255
|
+
sampleResultVehicleNodes: result.vehicles ? Object.values(result.vehicles)[0] : null,
|
|
256
|
+
allPayloadNodeTypes: (_f = payload.nodes) === null || _f === void 0 ? void 0 : _f.map((n) => ({ uid: n.uid, type: n.node_type, booking_uid: n.booking_uid })),
|
|
257
|
+
payloadKeys: Object.keys(payload),
|
|
258
|
+
payloadCalculationParameters: payload.calculation_parameters,
|
|
259
|
+
dataKeys: Object.keys(data)
|
|
260
|
+
});
|
|
261
|
+
// Set default routing engine on vehicles if missing
|
|
262
|
+
const defaultRoutingEngine = (_g = payload.engine_settings) === null || _g === void 0 ? void 0 : _g.routing_engine;
|
|
263
|
+
const vrpVehicles = payload.vehicles.map((vehicle) => (Object.assign(Object.assign({}, vehicle), { routing_engine: vehicle.routing_engine || defaultRoutingEngine })));
|
|
264
|
+
// Build assignedNodesCache from result.vehicles
|
|
265
|
+
const assignedNodesCache = {};
|
|
266
|
+
Object.entries(result.vehicles || {}).forEach(([vehicleAgentId, vehicleNodes]) => {
|
|
267
|
+
vehicleNodes.forEach((node) => {
|
|
268
|
+
assignedNodesCache[node.uid] = Object.assign(Object.assign({}, node), { assigned_vehicle_id: vehicleAgentId, status: 'assigned' });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
// Collect partial route nodes from vehicle.assigned_nodes
|
|
272
|
+
const partialRoutesNodes = [];
|
|
273
|
+
vrpVehicles.forEach((vehicle) => {
|
|
274
|
+
const assignedNodes = vehicle.assigned_nodes || [];
|
|
275
|
+
const completedNodes = vehicle.completed_nodes || [];
|
|
276
|
+
assignedNodes.forEach((vehicleNode) => {
|
|
277
|
+
vehicleNode.id = vehicleNode.uid;
|
|
278
|
+
partialRoutesNodes.push(vehicleNode);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
// Process vehicles to SGERP format
|
|
282
|
+
const vehicles = vrpVehicles.map((vehicle) => (Object.assign(Object.assign({}, vehicle), { id: vehicle.agent_id, simulation_id: taskId, routing_engine_settings: vehicle.routing_engine })));
|
|
283
|
+
// Process nodes to SGERP format
|
|
284
|
+
const nodes = [];
|
|
285
|
+
const included = {};
|
|
286
|
+
payload.nodes.forEach((node) => {
|
|
287
|
+
const processedNode = Object.assign(Object.assign({}, node), { simulation_id: taskId, booking_id: node.booking_uid, id: node.uid });
|
|
288
|
+
if (node.uid in assignedNodesCache) {
|
|
289
|
+
processedNode.scheduled_ts = assignedNodesCache[node.uid].scheduled_ts;
|
|
290
|
+
processedNode.assigned_vehicle_id = assignedNodesCache[node.uid].assigned_vehicle_id;
|
|
291
|
+
processedNode.status = assignedNodesCache[node.uid].status;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
processedNode.status = 'rejected_by_system';
|
|
295
|
+
processedNode.scheduled_ts = null;
|
|
296
|
+
processedNode.assigned_vehicle_id = null;
|
|
297
|
+
}
|
|
298
|
+
nodes.push(processedNode);
|
|
299
|
+
included[node.uid] = processedNode;
|
|
300
|
+
});
|
|
301
|
+
// Add partial route nodes that weren't in payload.nodes
|
|
302
|
+
partialRoutesNodes.forEach((partialRouteNode) => {
|
|
303
|
+
if (!(partialRouteNode.id in included)) {
|
|
304
|
+
const processedNode = Object.assign(Object.assign({}, partialRouteNode), { simulation_id: taskId });
|
|
305
|
+
if (partialRouteNode.id in assignedNodesCache) {
|
|
306
|
+
processedNode.scheduled_ts = assignedNodesCache[partialRouteNode.id].scheduled_ts;
|
|
307
|
+
processedNode.assigned_vehicle_id = assignedNodesCache[partialRouteNode.id].assigned_vehicle_id;
|
|
308
|
+
processedNode.status = assignedNodesCache[partialRouteNode.id].status;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
processedNode.scheduled_ts = null;
|
|
312
|
+
processedNode.assigned_vehicle_id = null;
|
|
313
|
+
processedNode.status = 'rejected_by_system';
|
|
314
|
+
}
|
|
315
|
+
nodes.push(processedNode);
|
|
316
|
+
included[partialRouteNode.id] = processedNode;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
// Check if this is CVRP mode (prebook_cvrptw) and find depot/point node
|
|
320
|
+
// CVRP mode detection: check scheduling_mode OR presence of point nodes without pickup nodes
|
|
321
|
+
const schedulingMode = (_h = payload.calculation_parameters) === null || _h === void 0 ? void 0 : _h.scheduling_mode;
|
|
322
|
+
const hasPointNodes = nodes.some((n) => n.node_type === 'point');
|
|
323
|
+
const hasPickupNodes = nodes.some((n) => n.node_type === 'pickup');
|
|
324
|
+
const isCVRP = schedulingMode === 'prebook_cvrptw' || (hasPointNodes && !hasPickupNodes);
|
|
325
|
+
const depotNode = isCVRP ? nodes.find((n) => n.node_type === 'point') : null;
|
|
326
|
+
console.log('[fetchVRPToolboxData] CVRP mode detection:', {
|
|
327
|
+
schedulingMode,
|
|
328
|
+
hasPointNodes,
|
|
329
|
+
hasPickupNodes,
|
|
330
|
+
isCVRP,
|
|
331
|
+
hasDepotNode: !!depotNode,
|
|
332
|
+
depotNodeLocation: depotNode ? { lat: depotNode.lat, lon: depotNode.lon, name: depotNode.location_name } : null
|
|
333
|
+
});
|
|
334
|
+
// Build bookings from nodes (must be done AFTER all nodes are processed)
|
|
335
|
+
const bookingsMap = {};
|
|
336
|
+
const nodeTypeCount = { pickup: 0, dropoff: 0, point: 0, other: 0 };
|
|
337
|
+
nodes.forEach((node) => {
|
|
338
|
+
const nodeType = node.node_type;
|
|
339
|
+
if (nodeType === 'pickup')
|
|
340
|
+
nodeTypeCount.pickup++;
|
|
341
|
+
else if (nodeType === 'dropoff')
|
|
342
|
+
nodeTypeCount.dropoff++;
|
|
343
|
+
else if (nodeType === 'point')
|
|
344
|
+
nodeTypeCount.point++;
|
|
345
|
+
else
|
|
346
|
+
nodeTypeCount.other++;
|
|
347
|
+
if (node.booking_id !== null && node.booking_id !== undefined) {
|
|
348
|
+
if (!(node.booking_id in bookingsMap)) {
|
|
349
|
+
bookingsMap[node.booking_id] = {
|
|
350
|
+
simulation_id: node.simulation_id,
|
|
351
|
+
id: node.booking_id,
|
|
352
|
+
uid: node.booking_id,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const booking = bookingsMap[node.booking_id];
|
|
356
|
+
// Update booking state from node status
|
|
357
|
+
booking.state = node.status;
|
|
358
|
+
// Set location data based on node type
|
|
359
|
+
booking[`${nodeType}_location_name`] = node.location_name;
|
|
360
|
+
booking[`${nodeType}_location_lat`] = node.lat;
|
|
361
|
+
booking[`${nodeType}_location_lon`] = node.lon;
|
|
362
|
+
booking[`${nodeType}_service_time`] = node.service_time;
|
|
363
|
+
// Set time windows and demand based on node type
|
|
364
|
+
if (nodeType === 'pickup') {
|
|
365
|
+
booking.min_pickup_time = node.open_time_ts;
|
|
366
|
+
booking.demand = node.demand;
|
|
367
|
+
}
|
|
368
|
+
else if (nodeType === 'dropoff') {
|
|
369
|
+
booking.max_dropoff_time = node.close_time_ts;
|
|
370
|
+
// In CVRP mode, set demand from dropoff node (take absolute values since dropoff has negative demand)
|
|
371
|
+
if (isCVRP && node.demand) {
|
|
372
|
+
booking.demand = Object.fromEntries(Object.entries(node.demand).map(([key, value]) => [key, Math.abs(value)]));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// For CVRP mode: Set all bookings to use depot as pickup location
|
|
378
|
+
if (isCVRP && depotNode) {
|
|
379
|
+
Object.values(bookingsMap).forEach((booking) => {
|
|
380
|
+
booking.pickup_location_lat = depotNode.lat;
|
|
381
|
+
booking.pickup_location_lon = depotNode.lon;
|
|
382
|
+
booking.pickup_location_name = depotNode.location_name || 'Depot';
|
|
383
|
+
booking.pickup_service_time = depotNode.service_time || 0;
|
|
384
|
+
booking.min_pickup_time = depotNode.open_time_ts;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Now attach booking_object to each node
|
|
388
|
+
nodes.forEach((node) => {
|
|
389
|
+
if (node.booking_id !== null && node.booking_id !== undefined) {
|
|
390
|
+
node.booking_object = bookingsMap[node.booking_id];
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
const bookings = Object.values(bookingsMap);
|
|
394
|
+
console.log('[fetchVRPToolboxData] Result:', {
|
|
395
|
+
vehiclesCount: vehicles.length,
|
|
396
|
+
bookingsCount: bookings.length,
|
|
397
|
+
nodesCount: nodes.length,
|
|
398
|
+
nodeTypeCount,
|
|
399
|
+
sampleBooking: bookings[0],
|
|
400
|
+
sampleBookingKeys: bookings[0] ? Object.keys(bookings[0]) : [],
|
|
401
|
+
sampleBookingId: (_j = bookings[0]) === null || _j === void 0 ? void 0 : _j.id,
|
|
402
|
+
sampleBookingUid: (_k = bookings[0]) === null || _k === void 0 ? void 0 : _k.uid,
|
|
403
|
+
sampleBookingState: (_l = bookings[0]) === null || _l === void 0 ? void 0 : _l.state,
|
|
404
|
+
sampleNode: nodes.find(n => n.booking_id),
|
|
405
|
+
bookingsWithCoordinates: bookings.filter(b => b.pickup_location_lat && b.pickup_location_lon &&
|
|
406
|
+
b.dropoff_location_lat && b.dropoff_location_lon).length,
|
|
407
|
+
bookingsWithPickup: bookings.filter(b => b.pickup_location_lat).length,
|
|
408
|
+
bookingsWithDropoff: bookings.filter(b => b.dropoff_location_lat).length,
|
|
409
|
+
bookingStates: bookings.map(b => b.state).filter((v, i, a) => a.indexOf(v) === i)
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
vehicles,
|
|
413
|
+
bookings,
|
|
414
|
+
nodes,
|
|
415
|
+
drivers: [], // No drivers in VRPToolbox data
|
|
416
|
+
geofences: [], // No geofences in VRPToolbox data
|
|
417
|
+
mainGeofence: null,
|
|
418
|
+
// Also return raw payload and result for analyzer
|
|
419
|
+
rawPayload: payload,
|
|
420
|
+
rawResult: result
|
|
421
|
+
};
|
|
422
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Simulation page logic utilities
|
|
3
3
|
* Inspired by dashviewer's SimulationPage/logic structure
|
|
4
4
|
*/
|
|
5
|
-
export { fetchVehicles, fetchBookings, fetchNodes, fetchDrivers, fetchDriversByProject, fetchOperationsLocationsByProject, fetchSimulationData } from './fetchUtils';
|
|
5
|
+
export { fetchVehicles, fetchBookings, fetchNodes, fetchDrivers, fetchDriversByProject, fetchOperationsLocationsByProject, fetchSimulationData, fetchVRPToolboxData } from './fetchUtils';
|
|
6
6
|
export { createIndexMaps, attachReferences } from './referenceUtils';
|
|
7
7
|
export { calculateBoundingBoxCenter } from './mapUtils';
|
|
8
8
|
export { vehicleComplexRearrange, buildVehicleRouteMap } from './manualEditUtils';
|
|
@@ -13,4 +13,7 @@ export { processUnassignResponse } from './optimisticUpdateUtils';
|
|
|
13
13
|
export type { UnassignResponse, ProcessUnassignResult } from './optimisticUpdateUtils';
|
|
14
14
|
export { LIVE_UPDATE_INTERVALS, PROCESSOR_LIVE_UPDATE_PARAMS, getVehicleLiveUpdateConfig, getBookingLiveUpdateConfig, getNodeLiveUpdateConfig, getProcessorLiveUpdateConfig, triggerAllSimulationUpdates } from './liveUpdates';
|
|
15
15
|
export type { SimulationLiveUpdatesConfig, SimulationLiveUpdateTriggers } from './liveUpdates';
|
|
16
|
+
export { processVRPStatistics, calculateFleetStats } from './vrpStatsCalculator';
|
|
17
|
+
export type { VRPVehicleStats, FleetStats, ProcessedNode } from './vrpStatsCalculator';
|
|
18
|
+
export { formatSeconds, getDistanceFromLatLonInKm, toDisplayDate } from './vrpStatsUtils';
|
|
16
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../sgerplib/sgerp/simulation-logic/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,aAAa,EACb,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,iCAAiC,EACjC,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,8BAA8B,EAC9B,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,0BAA0B,EAC1B,0BAA0B,EAC1B,uBAAuB,EACvB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC7B,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../sgerplib/sgerp/simulation-logic/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,aAAa,EACb,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,iCAAiC,EACjC,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,8BAA8B,EAC9B,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,0BAA0B,EAC1B,0BAA0B,EAC1B,uBAAuB,EACvB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC7B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,eAAe,EACf,UAAU,EACV,aAAa,EACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,aAAa,EACd,MAAM,iBAAiB,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Inspired by dashviewer's SimulationPage/logic structure
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.triggerAllSimulationUpdates = exports.getProcessorLiveUpdateConfig = exports.getNodeLiveUpdateConfig = exports.getBookingLiveUpdateConfig = exports.getVehicleLiveUpdateConfig = exports.PROCESSOR_LIVE_UPDATE_PARAMS = exports.LIVE_UPDATE_INTERVALS = exports.processUnassignResponse = exports.recalculateVehicleRoute = exports.hasRouteChanged = exports.calculateVehicleRoute = exports.shiftWaypointTimestamps = exports.shiftNodeTimestamps = exports.buildVehicleRouteMap = exports.vehicleComplexRearrange = exports.calculateBoundingBoxCenter = exports.attachReferences = exports.createIndexMaps = exports.fetchSimulationData = exports.fetchOperationsLocationsByProject = exports.fetchDriversByProject = exports.fetchDrivers = exports.fetchNodes = exports.fetchBookings = exports.fetchVehicles = void 0;
|
|
7
|
+
exports.toDisplayDate = exports.getDistanceFromLatLonInKm = exports.formatSeconds = exports.calculateFleetStats = exports.processVRPStatistics = exports.triggerAllSimulationUpdates = exports.getProcessorLiveUpdateConfig = exports.getNodeLiveUpdateConfig = exports.getBookingLiveUpdateConfig = exports.getVehicleLiveUpdateConfig = exports.PROCESSOR_LIVE_UPDATE_PARAMS = exports.LIVE_UPDATE_INTERVALS = exports.processUnassignResponse = exports.recalculateVehicleRoute = exports.hasRouteChanged = exports.calculateVehicleRoute = exports.shiftWaypointTimestamps = exports.shiftNodeTimestamps = exports.buildVehicleRouteMap = exports.vehicleComplexRearrange = exports.calculateBoundingBoxCenter = exports.attachReferences = exports.createIndexMaps = exports.fetchVRPToolboxData = exports.fetchSimulationData = exports.fetchOperationsLocationsByProject = exports.fetchDriversByProject = exports.fetchDrivers = exports.fetchNodes = exports.fetchBookings = exports.fetchVehicles = void 0;
|
|
8
8
|
var fetchUtils_1 = require("./fetchUtils");
|
|
9
9
|
Object.defineProperty(exports, "fetchVehicles", { enumerable: true, get: function () { return fetchUtils_1.fetchVehicles; } });
|
|
10
10
|
Object.defineProperty(exports, "fetchBookings", { enumerable: true, get: function () { return fetchUtils_1.fetchBookings; } });
|
|
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "fetchDrivers", { enumerable: true, get: function
|
|
|
13
13
|
Object.defineProperty(exports, "fetchDriversByProject", { enumerable: true, get: function () { return fetchUtils_1.fetchDriversByProject; } });
|
|
14
14
|
Object.defineProperty(exports, "fetchOperationsLocationsByProject", { enumerable: true, get: function () { return fetchUtils_1.fetchOperationsLocationsByProject; } });
|
|
15
15
|
Object.defineProperty(exports, "fetchSimulationData", { enumerable: true, get: function () { return fetchUtils_1.fetchSimulationData; } });
|
|
16
|
+
Object.defineProperty(exports, "fetchVRPToolboxData", { enumerable: true, get: function () { return fetchUtils_1.fetchVRPToolboxData; } });
|
|
16
17
|
var referenceUtils_1 = require("./referenceUtils");
|
|
17
18
|
Object.defineProperty(exports, "createIndexMaps", { enumerable: true, get: function () { return referenceUtils_1.createIndexMaps; } });
|
|
18
19
|
Object.defineProperty(exports, "attachReferences", { enumerable: true, get: function () { return referenceUtils_1.attachReferences; } });
|
|
@@ -38,3 +39,10 @@ Object.defineProperty(exports, "getBookingLiveUpdateConfig", { enumerable: true,
|
|
|
38
39
|
Object.defineProperty(exports, "getNodeLiveUpdateConfig", { enumerable: true, get: function () { return liveUpdates_1.getNodeLiveUpdateConfig; } });
|
|
39
40
|
Object.defineProperty(exports, "getProcessorLiveUpdateConfig", { enumerable: true, get: function () { return liveUpdates_1.getProcessorLiveUpdateConfig; } });
|
|
40
41
|
Object.defineProperty(exports, "triggerAllSimulationUpdates", { enumerable: true, get: function () { return liveUpdates_1.triggerAllSimulationUpdates; } });
|
|
42
|
+
var vrpStatsCalculator_1 = require("./vrpStatsCalculator");
|
|
43
|
+
Object.defineProperty(exports, "processVRPStatistics", { enumerable: true, get: function () { return vrpStatsCalculator_1.processVRPStatistics; } });
|
|
44
|
+
Object.defineProperty(exports, "calculateFleetStats", { enumerable: true, get: function () { return vrpStatsCalculator_1.calculateFleetStats; } });
|
|
45
|
+
var vrpStatsUtils_1 = require("./vrpStatsUtils");
|
|
46
|
+
Object.defineProperty(exports, "formatSeconds", { enumerable: true, get: function () { return vrpStatsUtils_1.formatSeconds; } });
|
|
47
|
+
Object.defineProperty(exports, "getDistanceFromLatLonInKm", { enumerable: true, get: function () { return vrpStatsUtils_1.getDistanceFromLatLonInKm; } });
|
|
48
|
+
Object.defineProperty(exports, "toDisplayDate", { enumerable: true, get: function () { return vrpStatsUtils_1.toDisplayDate; } });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VRPToolbox Statistics Calculator
|
|
3
|
+
* Processes VRP payload and result to calculate fleet statistics, timeline data, and utilization
|
|
4
|
+
*/
|
|
5
|
+
export interface ProcessedNode {
|
|
6
|
+
uid?: string;
|
|
7
|
+
lat: number;
|
|
8
|
+
lon: number;
|
|
9
|
+
scheduled_ts: string;
|
|
10
|
+
location_name?: string;
|
|
11
|
+
node_type: string;
|
|
12
|
+
service_time: number;
|
|
13
|
+
travel_time_from_prev: number;
|
|
14
|
+
nodeBreakDuration: number;
|
|
15
|
+
adjustedSlack: number;
|
|
16
|
+
booking_uid?: string;
|
|
17
|
+
slack?: number;
|
|
18
|
+
demand?: Record<string, number>;
|
|
19
|
+
open_time_ts?: string;
|
|
20
|
+
close_time_ts?: string;
|
|
21
|
+
scheduled_cumulative_dist?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface VRPVehicleStats {
|
|
24
|
+
vehicleId: string;
|
|
25
|
+
vehicleName: string;
|
|
26
|
+
timelineWaypoints: ProcessedNode[];
|
|
27
|
+
utilizationData: Array<{
|
|
28
|
+
time: string;
|
|
29
|
+
utilization: number;
|
|
30
|
+
}>;
|
|
31
|
+
color: string;
|
|
32
|
+
totalDistance: string;
|
|
33
|
+
totalTime: string;
|
|
34
|
+
totalSlack: string;
|
|
35
|
+
totalBreakTime: string;
|
|
36
|
+
totalDrivingTime: string;
|
|
37
|
+
}
|
|
38
|
+
export interface FleetStats {
|
|
39
|
+
totalSlack: string;
|
|
40
|
+
totalDistance: string;
|
|
41
|
+
totalDrivingTime: string;
|
|
42
|
+
totalBreakTime: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Calculate fleet-wide statistics from processed vehicles
|
|
46
|
+
*/
|
|
47
|
+
export declare function calculateFleetStats(vehicles: VRPVehicleStats[]): FleetStats;
|
|
48
|
+
/**
|
|
49
|
+
* Process VRP result and payload to generate vehicle statistics
|
|
50
|
+
*/
|
|
51
|
+
export declare function processVRPStatistics(result: any, payload: any): Promise<VRPVehicleStats[]>;
|
|
52
|
+
//# sourceMappingURL=vrpStatsCalculator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrpStatsCalculator.d.ts","sourceRoot":"","sources":["../../../sgerplib/sgerp/simulation-logic/vrpStatsCalculator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,aAAa,EAAE,CAAC;IACnC,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAaD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,UAAU,CAsB3E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,GAAG,EACX,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,eAAe,EAAE,CAAC,CAsR5B"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VRPToolbox Statistics Calculator
|
|
4
|
+
* Processes VRP payload and result to calculate fleet statistics, timeline data, and utilization
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.calculateFleetStats = calculateFleetStats;
|
|
8
|
+
exports.processVRPStatistics = processVRPStatistics;
|
|
9
|
+
const vrpStatsUtils_1 = require("./vrpStatsUtils");
|
|
10
|
+
const VEHICLE_COLORS = [
|
|
11
|
+
'red',
|
|
12
|
+
'blue',
|
|
13
|
+
'green',
|
|
14
|
+
'orange',
|
|
15
|
+
'purple',
|
|
16
|
+
'brown',
|
|
17
|
+
'magenta',
|
|
18
|
+
'teal',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Calculate fleet-wide statistics from processed vehicles
|
|
22
|
+
*/
|
|
23
|
+
function calculateFleetStats(vehicles) {
|
|
24
|
+
let totalSlack = 0;
|
|
25
|
+
let totalDistance = 0;
|
|
26
|
+
let totalDrivingTime = 0;
|
|
27
|
+
let totalBreakTime = 0;
|
|
28
|
+
vehicles.forEach((vehicle) => {
|
|
29
|
+
totalDistance += parseFloat(vehicle.totalDistance || '0');
|
|
30
|
+
vehicle.timelineWaypoints.forEach((wp) => {
|
|
31
|
+
totalSlack += wp.adjustedSlack || 0;
|
|
32
|
+
totalDrivingTime += wp.travel_time_from_prev || 0;
|
|
33
|
+
totalBreakTime += wp.nodeBreakDuration || 0;
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
totalSlack: (0, vrpStatsUtils_1.formatSeconds)(totalSlack),
|
|
38
|
+
totalDistance: totalDistance.toFixed(2),
|
|
39
|
+
totalDrivingTime: (0, vrpStatsUtils_1.formatSeconds)(totalDrivingTime),
|
|
40
|
+
totalBreakTime: (0, vrpStatsUtils_1.formatSeconds)(totalBreakTime),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Process VRP result and payload to generate vehicle statistics
|
|
45
|
+
*/
|
|
46
|
+
async function processVRPStatistics(result, payload) {
|
|
47
|
+
if (!result || !result.vehicles) {
|
|
48
|
+
console.warn('No vehicles in result', result);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const inputVehicles = (payload === null || payload === void 0 ? void 0 : payload.vehicles) || [];
|
|
52
|
+
const vehicleList = Array.isArray(result.vehicles)
|
|
53
|
+
? result.vehicles
|
|
54
|
+
.filter((v) => v.nodes && v.nodes.length > 0) // Only vehicles with nodes
|
|
55
|
+
.map((v) => ({
|
|
56
|
+
agent_id: v.agent_id,
|
|
57
|
+
nodes: v.nodes,
|
|
58
|
+
}))
|
|
59
|
+
: Object.entries(result.vehicles)
|
|
60
|
+
.filter(([id, nodes]) => nodes && nodes.length > 0) // Only vehicles with nodes
|
|
61
|
+
.map(([id, nodes]) => ({
|
|
62
|
+
agent_id: id,
|
|
63
|
+
nodes: nodes,
|
|
64
|
+
}));
|
|
65
|
+
const vehicles = await Promise.all(vehicleList.map(async (vehicleData, index) => {
|
|
66
|
+
var _a, _b, _c, _d;
|
|
67
|
+
const inputVehicle = inputVehicles.find((v) => v.agent_id === vehicleData.agent_id);
|
|
68
|
+
let vehicleBreakDuration = 0;
|
|
69
|
+
if (inputVehicle &&
|
|
70
|
+
typeof inputVehicle.dynamic_break_duration === 'number') {
|
|
71
|
+
vehicleBreakDuration = inputVehicle.dynamic_break_duration;
|
|
72
|
+
}
|
|
73
|
+
let sortedNodes = JSON.parse(JSON.stringify(vehicleData.nodes));
|
|
74
|
+
sortedNodes.sort((a, b) => new Date(a.scheduled_ts).getTime() - new Date(b.scheduled_ts).getTime());
|
|
75
|
+
// Add start/end nodes if vehicle has start/end times
|
|
76
|
+
if (inputVehicle) {
|
|
77
|
+
if (inputVehicle.start_time) {
|
|
78
|
+
const startNode = ((payload === null || payload === void 0 ? void 0 : payload.nodes) || []).find((n) => { var _a; return n.uid === ((_a = inputVehicle.partial_route) === null || _a === void 0 ? void 0 : _a[0]); }) || vehicleData.nodes[0];
|
|
79
|
+
if (startNode) {
|
|
80
|
+
sortedNodes.unshift({
|
|
81
|
+
lat: startNode.lat,
|
|
82
|
+
lon: startNode.lon,
|
|
83
|
+
scheduled_ts: inputVehicle.start_time,
|
|
84
|
+
location_name: 'Start',
|
|
85
|
+
node_type: 'vehicle_start',
|
|
86
|
+
service_time: 0,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (inputVehicle.end_time) {
|
|
91
|
+
const lastRouteUid = (_a = inputVehicle.partial_route) === null || _a === void 0 ? void 0 : _a[((_b = inputVehicle.partial_route) === null || _b === void 0 ? void 0 : _b.length) - 1];
|
|
92
|
+
const endNode = ((payload === null || payload === void 0 ? void 0 : payload.nodes) || []).find((n) => n.uid === lastRouteUid) ||
|
|
93
|
+
vehicleData.nodes[vehicleData.nodes.length - 1];
|
|
94
|
+
if (endNode) {
|
|
95
|
+
sortedNodes.push({
|
|
96
|
+
lat: endNode.lat,
|
|
97
|
+
lon: endNode.lon,
|
|
98
|
+
scheduled_ts: inputVehicle.end_time,
|
|
99
|
+
location_name: 'End',
|
|
100
|
+
node_type: 'vehicle_end',
|
|
101
|
+
service_time: 0,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Process nodes with travel times, breaks, and slack
|
|
107
|
+
const processedNodes = sortedNodes.map((node, idx, array) => {
|
|
108
|
+
const prevNode = array[idx - 1];
|
|
109
|
+
let prevNodeDepartureTime;
|
|
110
|
+
if (prevNode) {
|
|
111
|
+
let prevNodeActualBreakDuration = 0;
|
|
112
|
+
let prevNodeEffectiveSlack = 0;
|
|
113
|
+
if (typeof prevNode.dynamic_break_time === 'number') {
|
|
114
|
+
prevNodeActualBreakDuration = prevNode.dynamic_break_time;
|
|
115
|
+
prevNodeEffectiveSlack = prevNode.slack || 0;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const prevNodeBreakCount = prevNode.dynamic_break || 0;
|
|
119
|
+
prevNodeActualBreakDuration =
|
|
120
|
+
prevNodeBreakCount * vehicleBreakDuration;
|
|
121
|
+
prevNodeEffectiveSlack = Math.max(0, (prevNode.slack || 0) - prevNodeActualBreakDuration);
|
|
122
|
+
}
|
|
123
|
+
const prevScheduled = new Date(prevNode.scheduled_ts);
|
|
124
|
+
const departureMs = prevScheduled.getTime() +
|
|
125
|
+
((prevNode.service_time || 0) +
|
|
126
|
+
prevNodeEffectiveSlack +
|
|
127
|
+
prevNodeActualBreakDuration) *
|
|
128
|
+
1000;
|
|
129
|
+
prevNodeDepartureTime = new Date(departureMs);
|
|
130
|
+
}
|
|
131
|
+
let travel_time_from_prev = 0;
|
|
132
|
+
if (prevNode && node.node_type !== 'vehicle_end') {
|
|
133
|
+
const nodeScheduled = new Date(node.scheduled_ts);
|
|
134
|
+
if (prevNodeDepartureTime) {
|
|
135
|
+
travel_time_from_prev =
|
|
136
|
+
(nodeScheduled.getTime() - prevNodeDepartureTime.getTime()) /
|
|
137
|
+
1000;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let nodeBreakDuration = 0;
|
|
141
|
+
let adjustedSlack = 0;
|
|
142
|
+
if (typeof node.dynamic_break_time === 'number') {
|
|
143
|
+
nodeBreakDuration = node.dynamic_break_time;
|
|
144
|
+
adjustedSlack = node.slack || 0;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const nodeBreakCount = node.dynamic_break || 0;
|
|
148
|
+
nodeBreakDuration = nodeBreakCount * vehicleBreakDuration;
|
|
149
|
+
adjustedSlack = Math.max(0, (node.slack || 0) - nodeBreakDuration);
|
|
150
|
+
}
|
|
151
|
+
return Object.assign(Object.assign({}, node), { travel_time_from_prev,
|
|
152
|
+
nodeBreakDuration,
|
|
153
|
+
adjustedSlack, service_time: node.service_time || 0 });
|
|
154
|
+
});
|
|
155
|
+
// Calculate total distance (try multiple methods)
|
|
156
|
+
let totalDistance = 0;
|
|
157
|
+
const lastNode = processedNodes[processedNodes.length - 1];
|
|
158
|
+
// Method 1: Use scheduled_cumulative_dist if available
|
|
159
|
+
if (lastNode &&
|
|
160
|
+
typeof lastNode.scheduled_cumulative_dist === 'number') {
|
|
161
|
+
totalDistance = lastNode.scheduled_cumulative_dist / 1000;
|
|
162
|
+
}
|
|
163
|
+
// Method 2: Fallback to Euclidean distance
|
|
164
|
+
else if (processedNodes.length > 1) {
|
|
165
|
+
for (let i = 1; i < processedNodes.length; i++) {
|
|
166
|
+
const prev = processedNodes[i - 1];
|
|
167
|
+
const curr = processedNodes[i];
|
|
168
|
+
totalDistance += (0, vrpStatsUtils_1.getDistanceFromLatLonInKm)(prev.lat, prev.lon, curr.lat, curr.lon);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Calculate total time
|
|
172
|
+
let totalTime = 0;
|
|
173
|
+
if (processedNodes.length > 0 && lastNode) {
|
|
174
|
+
const startTime = new Date(processedNodes[0].scheduled_ts);
|
|
175
|
+
const endTime = new Date(lastNode.scheduled_ts);
|
|
176
|
+
totalTime = (endTime.getTime() - startTime.getTime()) / 1000;
|
|
177
|
+
}
|
|
178
|
+
// Calculate utilization over time
|
|
179
|
+
const utilizationData = [];
|
|
180
|
+
const vehicleCapacity = (inputVehicle === null || inputVehicle === void 0 ? void 0 : inputVehicle.capacity) || (inputVehicle === null || inputVehicle === void 0 ? void 0 : inputVehicle.limits) || {};
|
|
181
|
+
const totalCapacity = Object.values(vehicleCapacity).reduce((sum, val) => sum + val, 0);
|
|
182
|
+
const currentLoad = {};
|
|
183
|
+
// Initialize load for prebook_cvrptw mode
|
|
184
|
+
if (((_d = (_c = payload.engine_settings) === null || _c === void 0 ? void 0 : _c.calculation_parameters) === null || _d === void 0 ? void 0 : _d.scheduling_mode) ===
|
|
185
|
+
'prebook_cvrptw') {
|
|
186
|
+
const inputNodes = payload.nodes || [];
|
|
187
|
+
const allDemands = inputNodes
|
|
188
|
+
.filter((n) => n.node_type === 'dropoff' &&
|
|
189
|
+
n.demand &&
|
|
190
|
+
vehicleData.nodes.some((vn) => vn.booking_uid && vn.booking_uid === n.booking_uid))
|
|
191
|
+
.flatMap((n) => Object.entries(n.demand));
|
|
192
|
+
for (const [key, value] of allDemands) {
|
|
193
|
+
currentLoad[key] = (currentLoad[key] || 0) + Math.abs(value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
Object.keys(vehicleCapacity).forEach((key) => (currentLoad[key] = 0));
|
|
198
|
+
}
|
|
199
|
+
// Track utilization at each node
|
|
200
|
+
processedNodes.forEach((node) => {
|
|
201
|
+
const originalNode = (payload.nodes || []).find((n) => n.uid === node.uid || n.id === node.uid);
|
|
202
|
+
if (originalNode === null || originalNode === void 0 ? void 0 : originalNode.demand) {
|
|
203
|
+
if (node.node_type === 'dropoff') {
|
|
204
|
+
for (const [key, value] of Object.entries(originalNode.demand)) {
|
|
205
|
+
currentLoad[key] =
|
|
206
|
+
(currentLoad[key] || 0) - Math.abs(value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (node.node_type === 'pickup') {
|
|
210
|
+
for (const [key, value] of Object.entries(originalNode.demand)) {
|
|
211
|
+
currentLoad[key] =
|
|
212
|
+
(currentLoad[key] || 0) + Math.abs(value);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
let utilizationAfter = 0;
|
|
217
|
+
if (totalCapacity > 0) {
|
|
218
|
+
const totalLoad = Object.values(currentLoad).reduce((sum, val) => sum + val, 0);
|
|
219
|
+
utilizationAfter = (totalLoad / totalCapacity) * 100;
|
|
220
|
+
}
|
|
221
|
+
utilizationData.push({
|
|
222
|
+
time: node.scheduled_ts,
|
|
223
|
+
utilization: Math.max(0, Math.min(100, utilizationAfter)),
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
// Calculate vehicle-specific stats
|
|
227
|
+
const vehicleStats = processedNodes.reduce((acc, node) => {
|
|
228
|
+
acc.slack += node.adjustedSlack || 0;
|
|
229
|
+
acc.breakTime += node.nodeBreakDuration || 0;
|
|
230
|
+
acc.drivingTime += node.travel_time_from_prev || 0;
|
|
231
|
+
return acc;
|
|
232
|
+
}, { slack: 0, breakTime: 0, drivingTime: 0 });
|
|
233
|
+
return {
|
|
234
|
+
vehicleId: vehicleData.agent_id,
|
|
235
|
+
vehicleName: (inputVehicle === null || inputVehicle === void 0 ? void 0 : inputVehicle.service_number) || vehicleData.agent_id,
|
|
236
|
+
timelineWaypoints: processedNodes,
|
|
237
|
+
utilizationData: utilizationData,
|
|
238
|
+
color: VEHICLE_COLORS[index % VEHICLE_COLORS.length],
|
|
239
|
+
totalDistance: totalDistance.toFixed(2),
|
|
240
|
+
totalTime: (0, vrpStatsUtils_1.formatSeconds)(totalTime),
|
|
241
|
+
totalSlack: (0, vrpStatsUtils_1.formatSeconds)(vehicleStats.slack),
|
|
242
|
+
totalBreakTime: (0, vrpStatsUtils_1.formatSeconds)(vehicleStats.breakTime),
|
|
243
|
+
totalDrivingTime: (0, vrpStatsUtils_1.formatSeconds)(vehicleStats.drivingTime),
|
|
244
|
+
};
|
|
245
|
+
}));
|
|
246
|
+
return vehicles;
|
|
247
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VRPToolbox Statistics Utilities
|
|
3
|
+
* Helper functions for calculating and formatting VRP statistics
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Format seconds into HH:MM:SS format
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatSeconds(seconds: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Calculate distance between two lat/lon coordinates in kilometers (Haversine formula)
|
|
11
|
+
*/
|
|
12
|
+
export declare function getDistanceFromLatLonInKm(lat1: number, lon1: number, lat2: number, lon2: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Convert timestamp to display Date (handles timezone conversions)
|
|
15
|
+
*/
|
|
16
|
+
export declare function toDisplayDate(ts: string): Date;
|
|
17
|
+
//# sourceMappingURL=vrpStatsUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrpStatsUtils.d.ts","sourceRoot":"","sources":["../../../sgerplib/sgerp/simulation-logic/vrpStatsUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,CAaR;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAc9C"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VRPToolbox Statistics Utilities
|
|
4
|
+
* Helper functions for calculating and formatting VRP statistics
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.formatSeconds = formatSeconds;
|
|
8
|
+
exports.getDistanceFromLatLonInKm = getDistanceFromLatLonInKm;
|
|
9
|
+
exports.toDisplayDate = toDisplayDate;
|
|
10
|
+
/**
|
|
11
|
+
* Format seconds into HH:MM:SS format
|
|
12
|
+
*/
|
|
13
|
+
function formatSeconds(seconds) {
|
|
14
|
+
const hours = Math.floor(seconds / 3600);
|
|
15
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
16
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
17
|
+
const formattedHours = String(hours).padStart(2, '0');
|
|
18
|
+
const formattedMinutes = String(minutes).padStart(2, '0');
|
|
19
|
+
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
|
|
20
|
+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Calculate distance between two lat/lon coordinates in kilometers (Haversine formula)
|
|
24
|
+
*/
|
|
25
|
+
function getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
|
|
26
|
+
const R = 6371; // Earth's radius in km
|
|
27
|
+
const dLat = deg2rad(lat2 - lat1);
|
|
28
|
+
const dLon = deg2rad(lon2 - lon1);
|
|
29
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
30
|
+
Math.cos(deg2rad(lat1)) *
|
|
31
|
+
Math.cos(deg2rad(lat2)) *
|
|
32
|
+
Math.sin(dLon / 2) *
|
|
33
|
+
Math.sin(dLon / 2);
|
|
34
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
35
|
+
const d = R * c;
|
|
36
|
+
return d;
|
|
37
|
+
}
|
|
38
|
+
function deg2rad(deg) {
|
|
39
|
+
return deg * (Math.PI / 180);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert timestamp to display Date (handles timezone conversions)
|
|
43
|
+
*/
|
|
44
|
+
function toDisplayDate(ts) {
|
|
45
|
+
// Parse as UTC and return as local Date object
|
|
46
|
+
const date = new Date(ts);
|
|
47
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()));
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone utility functions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get the effective timezone based on the current mode
|
|
6
|
+
* In VRPToolbox mode, uses the VRP timezone selector
|
|
7
|
+
* Otherwise, uses the project timezone (or falls back to UTC)
|
|
8
|
+
*
|
|
9
|
+
* @param mode - The current effective mode ('vrptoolbox' or other)
|
|
10
|
+
* @param vrpTimezone - The timezone selected in VRPToolbox mode
|
|
11
|
+
* @param projectTimezone - The project's configured timezone
|
|
12
|
+
* @returns The effective timezone string (e.g., 'UTC', 'Asia/Singapore')
|
|
13
|
+
*/
|
|
14
|
+
export declare function getEffectiveTimezone(mode: string, vrpTimezone: string, projectTimezone?: string): string;
|
|
15
|
+
//# sourceMappingURL=timezone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezone.d.ts","sourceRoot":"","sources":["../../../sgerplib/sgerp/utils/timezone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CAER"}
|