Skip to main content

Live insertion into the route

Live Route Insertion: Adapting Logistics On-the-Fly

Live route insertion refers to the dynamic process of adding a new pickup and/or delivery stop to a vehicle's route after the vehicle has already departed and begun executing its pre-planned sequence. This typically happens when the vehicle has completed its first pickup but has not yet delivered those items.

The core challenge is to integrate the new order seamlessly into the remaining stops, respecting crucial constraints like the vehicle's current location, available capacity (considering items already onboard), time windows of the new and existing stops, and ensuring the overall route remains efficient and feasible.

Specific Operational Use Cases:

  • Same-Day / On-Demand Courier Services: A customer places an urgent order (e.g., documents, pharmacy items, retail goods) after the local courier vehicle is already making deliveries. Live insertion allows the dispatcher to evaluate if the new pickup/delivery can be added to the active courier's route based on their proximity, capacity, and existing schedule, maximizing responsiveness without dispatching another vehicle unnecessarily.

  • Emergency Medical or Parts Delivery: A hospital urgently needs specific medical supplies, or a technician requires a critical spare part for an emergency repair after the delivery vehicle handling such items is already en route. Live insertion enables adding this high-priority stop dynamically, potentially diverting the vehicle slightly but ensuring faster delivery than waiting for the next scheduled run, leveraging the vehicle already being in the field.

  • Dynamic Reverse Logistics (Urgent Returns): A business customer needs an urgent pickup for a high-value return item. If a delivery vehicle that has capacity is nearby after completing some deliveries (but before returning or finishing all stops), the return pickup can be inserted into its route, improving asset recovery speed and customer service.

tip

Fleet vehicles can use a live insertion model to select the most suitable vehicle for a given order, considering all constraints. For example, below vehicle labels, vehicle capacity and time windows are used as constraints. To add more vehicles, just the use example below and configure each additional vehicle separately in vehicles array.

Implementation in the SWAT API

Optimization (aka Stateless) API supports live insertion. A few special settings can be used to model this behavior.

Key differences required to implement live insertion (or inserting a new booking into the vehicle):

  • Since vehicles are moving and optimization is done at a moment of the request, vehicle position has to be considered and submitted to the algorithm using vehicle_position node type, which will need to be assigned to a specific vehicle
  • Some of the nodes might have been already completed, which will impact current available capacity of the vehicle, need to be attached to a relevant vehicles and be submitted in the completed_nodes field
  • Some of nodes might have been already be preassigned to a vehicles as routes in prior optimization, those also need to be attached to a relevant vehicle and be submitted in the assigned_nodes field
  • Also a vehicle need to have "fixed" non negotiable constraints set in partial_route by assigning current vehicle_position node and, if required, end position of the vehicle (so the node where the vehicle should finish their job)

Dealing with rejected bookings

When live insertion happens, unless reallocation of existing bookings is allowed, constraints related to already assigned nodes to a vehicle must be fulfilled. The nodes within assigned_nodes can be reordered, their scheduled timestamps may change, but un assignment or reassignment can not happen.

Some common reasons why the orders may be rejected:

  • Given current vehicle position, there is no possible way to meet time constraints for the upcoming drop offs or nodes
  • Cumulative capacity vehicle constraint can be violated since the vehicle has already utilized some of its capacity
  • If supplied orders for insertion can not be satisfied, all orders will be rejected, including those that have been already assigned to a vehicle
warning

Live insertions can be a risky operation since vehicle may be way off their planned routes, be too late or too early which would mean that the constraints set in assigned_nodes might not be met anymore after the initial planning. If this happens, and constraints in already assigned_nodes can not be met anymore, the algorithm will fail the full request and issue no offers for all bookings, even those that are currently assigned. This could also happen if some oif the orders were manually inserted into the routes and therefore constraints can not be met.

There are a few ways to deal with rejected bookings in live insertion scenario:

  • By allowing some nodes to be unassigned from a vehicle that has assigned_nodes with broken constraints. This is done by using allow_jump flag that would allow (where possible) nodes to be reassigned to a different vehicle
  • By allowing some constraints to be broken, for example, using allow_vehicle_late flag, that converts hard time window constraints into soft ones so that assigned_nodes can be rearranged.

API structure

First of all, current vehicle (or vehicles) position(s) should be added to the list of nodes in the request. Each additional node will mapped to an actual vehicle later on:

"nodes": [
...
{
"node_type": "vehicle_position",
"lat": 1.3341,
"lon": 103.8498,
"open_time_ts": "2025-04-03T00:54:16.601615+00:00", // Vehicle location timestamp
"uid": "2c4c394e-6b8c-4d37-adbb-0da611cb1f1f",
"close_time_ts": "2025-04-03T12:00:00+00:00",
"close_time_ts_dynamic": "2025-04-03T12:00:00+00:00",
"service_time": 0,
"demand": { // Current vehicle load in demand units
"g": 41300,
"cbcm": 340000
}
}
...
]

Also, all new order should be included into the "nodes" (in this example, a single order):

"nodes": [
...
"nodes": [
{
"uid": "5411df54-f06e-4505-b2ab-ae8f4892dda2",
"booking_uid": "e52c6195-e81c-47e3-bed2-b4e68f9a2e7b",
"stop_id": "5411df54-f06e-4505-b2ab-ae8f4892dda2",
"lat": 1.417,
"lon": 103.7541,
"demand": {
"g": 70500,
"cbcm": 950000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 300,
"node_type": "pickup",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "5 Mandai Link Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
},
{
"uid": "8273d84b-80f3-420f-8239-a8a99d11760e",
"booking_uid": "e52c6195-e81c-47e3-bed2-b4e68f9a2e7b",
"stop_id": "8273d84b-80f3-420f-8239-a8a99d11760e",
"lat": 1.2999,
"lon": 103.7876,
"demand": {
"g": -70500,
"cbcm": -950000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 360,
"node_type": "dropoff",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "1 Fusionopolis Way Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
}
...
]

Key changes need to occur to a vehicle definition to describe:

  • Nodes that have been assigned to the vehicle already should be represented in vehicle definition
  • Completed nodes by the vehicle (pickup in this example) should be also added to a vehicle
  • Sequence of assigned nodes

The algorithm prioritizes using already-assigned vehicle routes. It attempts to insert new orders into existing routes if feasible. If this is impossible, it tries assigning the orders to a different vehicle. If no vehicle can accommodate the orders, the algorithm reports failure. Completed nodes assigned to a vehicle are used to calculate current available vehicle capacity so the capacity constraint is not broken.

    "vehicles": [
{
"agent_id": "957d8937-d22d-42bc-8a51-1c68d91c48aa",
"capacity": {
"g": 3000000,
"cbcm": 14000000
},
"lat": 1.292013,
"lon": 103.840632,
"start_time_node_index": null,
"assigned_nodes": [
{
"uid": "849f2a6c-0bfe-4ad3-99a7-b463a16c069a",
"lat": 1.3341,
"lon": 103.8498,
"location_name": "500 Lorong 6 Toa Payoh Singapore",
"display_name": "500 Lorong 6 Toa Payoh Singapore",
"booking_uid": "70891fba-31b4-40e1-b14f-91071af80482",
"open_time_ts": "2025-04-02T22:00:00+00:00",
"close_time_ts": "2025-04-03T23:00:00+00:00",
"close_time_ts_dynamic": "2025-04-02T23:00:00+00:00",
"service_time": 360,
"demand": {
"g": -41300,
"cbcm": -340000
},
"status": "assigned",
"node_type": "dropoff",
"assignment_order": 3,
"scheduled_ts": "2025-04-02T22:31:38+00:00",
"max_trip_duration": 7200,
"penalty": 1000000,
"lifo_order_check": false,
"groups": [],
"geofence_ids": [],
"slack": 58.6,
"data": {},
"finalization_type": "min",
"allow_jump": false,
"estimated_earliest_arrival_ts": "2025-04-03T01:54:49.102781+00:00",
"estimated_scheduled_ts": "2025-04-03T01:54:49.102781+00:00",
"vehicle_characteristics": {}
}
],
"completed_nodes": [
{
"uid": "ed29fc88-55cf-4bc1-9cbe-b36b5f5ab113",
"lat": 1.417,
"lon": 103.7541,
"location_name": "5 Mandai Link Singapore",
"location_code": "",
"display_name": "5 Mandai Link Singapore",
"booking_uid": "70891fba-31b4-40e1-b14f-91071af80482",
"open_time_ts": "2025-04-02T21:00:00+00:00",
"close_time_ts": "2025-04-02T21:45:00+00:00",
"close_time_ts_dynamic": "2025-04-02T21:45:00+00:00",
"service_time": 300,
"demand": {
"g": 41300,
"cbcm": 340000
},
"status": "completed",
"node_type": "pickup",
"scheduled_ts": "2025-04-02T21:00:00+00:00",
"completed_service_at": "2025-04-03T00:53:02.867000+00:00",
"started_service_at_ts": "2025-04-03T00:53:02.867000+00:00", // This is required field for completed_nodes for a vehicle to calculate time window constraints for the upcoming nodes
"max_trip_duration": 7200,
"fixed_route_is_unreachable": false,
"penalty": 1000000,
"lifo_order_check": false,
"groups": [],
"geofence_ids": [],
"trip_cost": 0,
"slack": 3211.9,
"finalization_type": "min",
"allow_jump": false,
"estimated_earliest_arrival_ts": "2025-04-03T01:21:23.541136+00:00",
"estimated_scheduled_ts": "2025-04-03T01:21:23.541136+00:00",
"vehicle_characteristics": {}
}
],
"geofence_ids": [
-1
],
"routing_engine": {
"routing_engine_name": "osrme",
"url": "http://mapbox-osrm-proxy",
"road_network": "van",
"speed": null,
"time_factor": 1,
"make_depot_zero": true,
"use_speed_in_routing": false,
"key": "<your_key>",
"batch_matrix_size": 250,
"curb": false
},
"service_number": "3T003",
"start_time": "2025-04-02T18:00:00+00:00",
"end_time": "2025-04-03T12:00:00+00:00",
"vehicle_cost": 1000,
"characteristics": {},
"lifo_order_check": false,
"labels": [
"ambient",
"nonhalal"
],
"partial_route": [
"2c4c394e-6b8c-4d37-adbb-0da611cb1f1f" // UID of the current vehicle location representing that the vehicle is in this location through partial route
],
}
]

Additionally, the algorithm needs to be aware that current vehicle location has to be respected by setting a flag in calculation_parameters

        "calculation_parameters": {
"scheduling_mode": "logistics_real_time",
"calculations_mode": "sync",
"use_vehicles_nodes": false,
"allow_vehicle_late": false,
"vehicle_late_penalty_coefficient": 10,
"max_possible_lateness": null,
"use_node_weights_cost": true,
"use_path_equalizer": false,
"path_equalizer_weight": 100,
"use_mixed_time_matrix": false
}

Putting it all together:

Full Optimization API request
{
"current_time": "2025-04-02T18:00:00+00:00",
"nodes": [
{
"uid": "5411df54-f06e-4505-b2ab-ae8f4892dda2",
"booking_uid": "e52c6195-e81c-47e3-bed2-b4e68f9a2e7b",
"stop_id": "5411df54-f06e-4505-b2ab-ae8f4892dda2",
"lat": 1.417,
"lon": 103.7541,
"demand": {
"g": 70500,
"cbcm": 950000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 300,
"node_type": "pickup",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "5 Mandai Link Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
},
{
"uid": "8273d84b-80f3-420f-8239-a8a99d11760e",
"booking_uid": "e52c6195-e81c-47e3-bed2-b4e68f9a2e7b",
"stop_id": "8273d84b-80f3-420f-8239-a8a99d11760e",
"lat": 1.2999,
"lon": 103.7876,
"demand": {
"g": -70500,
"cbcm": -950000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 360,
"node_type": "dropoff",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "1 Fusionopolis Way Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
},
{
"uid": "dd05e2bf-c586-498c-ae1a-5123e623f508",
"booking_uid": "d5d559d0-e19e-4dad-bf34-b7f4d40eb8db",
"stop_id": "dd05e2bf-c586-498c-ae1a-5123e623f508",
"lat": 1.2763,
"lon": 103.8545,
"demand": {
"g": -13400,
"cbcm": -810000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 360,
"node_type": "dropoff",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "8 Marina View Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
},
{
"uid": "e718ad20-5b55-429b-ac0d-41d55ac0bff8",
"booking_uid": "d5d559d0-e19e-4dad-bf34-b7f4d40eb8db",
"stop_id": "e718ad20-5b55-429b-ac0d-41d55ac0bff8",
"lat": 1.417,
"lon": 103.7541,
"demand": {
"g": 13400,
"cbcm": 810000
},
"open_time_ts": "2025-04-03T00:00:00+00:00",
"close_time_ts": "2025-04-03T10:00:00+00:00",
"service_time": 300,
"node_type": "pickup",
"close_time_ts_dynamic": "2025-04-03T10:00:00+00:00",
"geofence_ids": [
-1
],
"trip_cost": 0,
"max_slack": null,
"groups": [],
"matrix_timestamp": null,
"vehicle_characteristics": {},
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_name": "5 Mandai Link Singapore",
"penalty": 1000000,
"end_of_trip": null,
"time_windows": null,
"vehicle_labels": {
"and": [
"ambient",
"nonhalal"
]
}
},
{
"node_type": "vehicle_position",
"uid": "2c4c394e-6b8c-4d37-adbb-0da611cb1f1f",
"lat": 1.3341,
"lon": 103.8498,
"open_time_ts": "2025-04-03T00:54:16.601615+00:00",
"close_time_ts": "2025-04-03T12:00:00+00:00",
"close_time_ts_dynamic": "2025-04-03T12:00:00+00:00",
"service_time": 0,
"demand": {
"g": 41300,
"cbcm": 340000
}
}
],
"vehicles": [
{
"agent_id": "957d8937-d22d-42bc-8a51-1c68d91c48aa",
"capacity": {
"g": 3000000,
"cbcm": 14000000
},
"lat": 1.292013,
"lon": 103.840632,
"start_time_node_index": null,
"assigned_nodes": [
{
"uid": "849f2a6c-0bfe-4ad3-99a7-b463a16c069a",
"lat": 1.3341,
"lon": 103.8498,
"location_name": "500 Lorong 6 Toa Payoh Singapore",
"display_name": "500 Lorong 6 Toa Payoh Singapore",
"booking_uid": "70891fba-31b4-40e1-b14f-91071af80482",
"open_time_ts": "2025-04-02T22:00:00+00:00",
"close_time_ts": "2025-04-03T23:00:00+00:00",
"close_time_ts_dynamic": "2025-04-02T23:00:00+00:00",
"service_time": 360,
"demand": {
"g": -41300,
"cbcm": -340000
},
"status": "assigned",
"node_type": "dropoff",
"assignment_order": 3,
"scheduled_ts": "2025-04-02T22:31:38+00:00",
"max_trip_duration": 7200,
"penalty": 1000000,
"lifo_order_check": false,
"groups": [],
"geofence_ids": [],
"slack": 58.6,
"data": {},
"finalization_type": "min",
"allow_jump": false,
"estimated_earliest_arrival_ts": "2025-04-03T01:54:49.102781+00:00",
"estimated_scheduled_ts": "2025-04-03T01:54:49.102781+00:00",
"vehicle_characteristics": {}
}
],
"completed_nodes": [
{
"uid": "ed29fc88-55cf-4bc1-9cbe-b36b5f5ab113",
"lat": 1.417,
"lon": 103.7541,
"location_name": "5 Mandai Link Singapore",
"location_code": "",
"display_name": "5 Mandai Link Singapore",
"booking_uid": "70891fba-31b4-40e1-b14f-91071af80482",
"open_time_ts": "2025-04-02T21:00:00+00:00",
"close_time_ts": "2025-04-02T21:45:00+00:00",
"close_time_ts_dynamic": "2025-04-02T21:45:00+00:00",
"service_time": 300,
"demand": {
"g": 41300,
"cbcm": 340000
},
"status": "completed",
"node_type": "pickup",
"scheduled_ts": "2025-04-02T21:00:00+00:00",
"completed_service_at": "2025-04-03T00:53:02.867000+00:00",
"started_service_at_ts": "2025-04-03T00:53:02.867000+00:00",
"max_trip_duration": 7200,
"fixed_route_is_unreachable": false,
"penalty": 1000000,
"lifo_order_check": false,
"groups": [],
"geofence_ids": [],
"trip_cost": 0,
"slack": 3211.9,
"finalization_type": "min",
"allow_jump": false,
"estimated_earliest_arrival_ts": "2025-04-03T01:21:23.541136+00:00",
"estimated_scheduled_ts": "2025-04-03T01:21:23.541136+00:00",
"vehicle_characteristics": {}
}
],
"geofence_ids": [
-1
],
"routing_engine": {
"routing_engine_name": "osrme",
"url": "http://mapbox-osrm-proxy",
"road_network": "van",
"speed": null,
"time_factor": 1,
"make_depot_zero": true,
"use_speed_in_routing": false,
"key": "dmVyeSBzZWNyZXQga2V5",
"batch_matrix_size": 250,
"curb": false
},
"service_number": "3T003",
"start_time": "2025-04-02T18:00:00+00:00",
"end_time": "2025-04-03T12:00:00+00:00",
"max_physical_stops": null,
"vehicle_cost": 1000,
"characteristics": {},
"lifo_order_check": false,
"labels": [
"ambient",
"nonhalal"
],
"partial_route": [
"2c4c394e-6b8c-4d37-adbb-0da611cb1f1f"
]
}
],
"engine_settings": {
"routing_engine": {
"key": "<your _api_key>",
"url": "http://mapbox-osrm-proxy",
"curb": false,
"speed": null,
"time_factor": 1,
"road_network": "van",
"vehicle_model": "Van",
"make_depot_zero": true,
"batch_matrix_size": 250,
"continue_straight": true,
"routing_engine_name": "osrme",
"osrme_timestamp_mode": "start_time",
"use_speed_in_routing": false
},
"model_parameters": {
"vehicle_costs": 1000,
"vehicle_amortized_linear_cost_factor": null,
"vehicle_amortized_quadratic_cost_factor": null,
"booking_penalty": 1000000,
"mixed_fleet": true,
"use_walking_time_to_reduce_time_windows": false,
"mutually_exclusive_groups": [],
"strictly_exclusive_groups": [],
"group_crossing_penalty": 10000,
"use_lifo_order_check": false,
"lifo_order_check_on_all_vehicles": true,
"optimize_quantity": "total_time"
},
"solver_parameters": {
"algorithm": "static",
"first_solution_strategy": 17,
"solution_limit": 100000000,
"use_local_search_metaheuristic": true,
"guided_local_search_lambda_coefficient": 0.1,
"use_tsp_opt": false,
"time_limit_ms": 10000,
"log_search": false,
"lns_time_limit_ms": 1000,
"savings_neighbors_ratio": 1,
"use_waypoints_optimization": false,
"waypoints_solution_limit": 1000,
"waypoints_optimization_second_phase": false,
"use_depth_first_search": false,
"optimization_step": 1,
"use_all_local_search_operators": false,
"use_local_search_operators": [],
"use_cvb_local_search_operator": false,
"cvb_local_search_iterations_limit": 100000,
"cvb_fleetmin_iterations_limit": 3000000,
"cvb_fleetmin_solutions_limit": 30000,
"cvb_fleetmin_time_limit": 600
},
"calculation_parameters": {
"scheduling_mode": "logistics_real_time",
"calculations_mode": "sync",
"use_vehicles_nodes": false,
"allow_vehicle_late": false,
"vehicle_late_penalty_coefficient": 10,
"max_possible_lateness": null,
"use_node_weights_cost": true,
"use_path_equalizer": false,
"path_equalizer_weight": 100,
"debug_info_keys": [],
"use_mixed_time_matrix": false
}
}
}