Skip to main content

ETA for a delivery or pickup

There are three types of ETA (Estimated Time of Arrival) times available for nodes:

  • Estimated scheduled time scheduled_ts that is calculated at an optimization step and is retained throughout operations
  • Estimated scheduled time that represents realistic estimated time in estimated_scheduled_ts field. Initial value is during optimization run of the simulation.
  • Estimated scheduled time that represents the earliest possible arrival time in estimated_earliest_scheduled_ts field

The value of estimated_scheduled_ts takes into account service times along the route, current remaining service time for a node in case the vehicle is being serviced at the moment of calculation, and earliness. Therefore, it is recommended for improved accuracy in consuming applications. If there are any delays while the vehicle is en route and the adjusted arrival time needs to be pushed back, estimated_scheduled_ts will be updated with the new value, along with estimated_earliest_scheduled_ts.

Estimated time in estimated_scheduled_ts field is calculated as

Estimated time calculation
  estimated_scheduled_ts = max(estimated_earliest_arrival_ts, node.scheduled_ts)

The initial calculation of ETA occurs as part of the route optimization session when a node is assigned to a vehicle.

tip

Webhook and websocket messages are not dispatched is the node doesn't belong to a valid booking.

ETA (estimated arrival time) is updated every 5 minutes for all vehicles in a simulation. The following rules are applied:

  • Updates happen per simulation
  • Only vehicles that have assigned nodes (in the filed vehicle.assigned_nodes) are selected for an update
  • Selected nodes must already have existing schedule_ts from the simulation optimization run
  • Assigned nodes must have non-zero latitude and longitude
  • ETA considers vehicle.start_time and vehicle.should_wait_until for calculation unless it is en route already

ETA calculation considers route settings of the vehicles or simulation the vehicle belongs to (vehicle settings have higher priority than simulation). Node's matrix timestamp is used for calculating the route is available, current time, or vehicle start time otherwise in the order of priorities, specifically:

Selecting timestamp for routing

start_time = assigned_nodes[0].get_matrix_timestamp()
if <vehicle has valid coordinates>:
start_time = timezone.now()
if vehicle.start_time is not None:
start_time = max(vehicle.start_time, start_time)
if vehicle.should_wait_until is not None:
start_time = max(vehicle.should_wait_until, start_time)

Types of timestamps available for a node

  • scheduled_ts: Represents the initially planned arrival time to a node during route optimization. This can be used for comparing the actual arrival time with the planned one to assess the quality of planning.
  • estimated_arrival_time: Deprecated. Should not be used and is set to null in node data.
  • estimated_earliest_arrival_ts: Represents a less accurate, optimistic estimation of the arrival time to a node. It's recalculated for specific nodes at regular intervals (default: ~5 minutes).
  • estimated_scheduled_ts: Represents the most realistic expected arrival time to a node. It's recalculated for specific nodes at regular intervals (default: ~5 minutes). Considers service_time for previous nodes, but not slack
  • arrived_to_location_ts: Represents the timestamp of the arrival to the node. Updated when an incoming message from driver is received. Can be null if this feature is not used in the Driver Application.
  • departed_at_ts: Deprecated. Should not be used and is set to null in node data.
  • failed_to_deliver_at_ts: If a delivery failed event occurs at the node, this timestamp represents when the event happened. Updated if state of the booking is fail_to_deliver, otherwise is null
  • started_service_at_ts: Deprecated. Should not be used and is set to null in node data. The field is used in Optimization API for a node and must be set for completed nodes.
  • visited_at_ts: Deprecated. Should not be used and is set to null in node data.
  • completed_service_at: Represents time when the node has been marked as completed
  • slack: Represent time in seconds spent at this node (not servicing, but unused time waiting). Please refer slack.
  • service_time: Represents time populated from a corresponding booking. This time will be used as a time required to service a vehicle at a node.
warning

The availability of values depends on the simulation operations mode and the current stage of the node lifecycle. It may not be present at all times. If data is unavailable for some timestamps, they will be set to null. For example, manually assigned nodes like start and end location may not has scheduled_ts assigned to them.

The node.estimated_scheduled_ts is calculated while considering appropriate time windows for the node. That is, node.estimated_scheduled_ts cannot be less than the node's open time window bound, node.open_time_ts. Accordingly, if the vehicle can arrive at the location early, node.estimated_scheduled_ts will be at least node.open_time_ts, so the vehicle should wait. Consequently, slack is implicitly included in ETA.

Examples for ETA calculation

Given nodes:

  • A as a starting node (pickup)
  • B with open time 10AM (drop off)
  • C with open time 11AM (drop off)
  • Service time for both nodes A and B is 5 min
  • Travel time between node B and C is 10 min
// Node B ETA
pickup_B.open_time_ts = 10AM
pickup_B.estimated_scheduled_ts = 10AM
pickup_B.estimated_earliest_arrival_ts = 9:10AM
slack_B = 50 minutes

//Node C ETA
pickup_C.open_time_ts = 11AM
pickup_C.estimated_scheduled_ts = 11AM
pickup_C.estimated_earliest_arrival_ts = 9:25AM [9:10AM + 5min (service time B) + 10 minutes travel B->C]
slack_C = 45 minutes (10AM + 5min service time B + 10 minutes travel time = 10:15 - earliest arrival after first scheduled ts)

Retrieving estimated arrival time from SWAT APIs

The SWAT backend provides the ability to retrieve scheduled pickup or drop-off times for a given node. There are three options for retrieving ETA data from SWAT APIs:

Polling Node Data Method

tip

Each booking

contains at least two nodes representing pickup and drop off location. This is the reason why a node should be used to trigger a webhook (either for drop off or pickup within the same booking). To find out which nodes belong to the booking, GET <base_url>/api/v2/node?booking=<booking_id> can be used.

To request data about the node, the following request can be used: GET /api/v2/node/{node_id}

which will result for a pickup node in a response similar to:

See JSON payload
Pickup node data

{
"allow_jump": false,
"arrived_to_location_ts": null,
"assigned_vehicle": "/api/v2/vehicle/17789910",
"assignment_order": 0,
"boarding_pass": "784",
"booking": "/api/v2/booking/23286258",
"booking_group_uid": "0274e962-2024-1008-0659-48d787b6ab5f",
"booking_uid": "0274e962-2024-1008-0659-48d787b6ab5f",
"cancelled_at_ts": null,
"close_time_ts": "2024-10-08T04:30:00+00:00",
"close_time_ts_dynamic": "2024-10-08T04:30:00+00:00",
"completed_service_at": "2024-10-08T05:33:47.409000+00:00",
"created_at": "2024-10-07T23:25:00.551915+00:00",
"customer_id": null,
"data": {
"dropoff_address": "P3749",
"dropoff_city": "Thailand",
"dropoff_country": "TH",
"dropoff_region": "Thailand",
"external_id": "123",
"is_dropoff_end_of_trip": true,
"is_pickup_end_of_trip": false,
"order_identity": {
"external_id": "123",
"simulation_id": 177821
},
"pickup_address": "Warehouse",
"pickup_city": "Thailand",
"pickup_country": "TH",
"pickup_customer_name": " Warehouse",
"pickup_location_lat": "11.69046292",
"pickup_location_lon": "101.516127",
"pickup_location_name": "Warehouse",
"pickup_region": "Thailand"
},
"demand": {
"cbcm": 1709600,
"undefined": 0,
"weight": 564
},
"departed_at_ts": null,
"display_name": "Warehouse",
"dynamic_break": null,
"end_of_trip": false,
"estimated_arrival_time": null,
"estimated_earliest_arrival_ts": "2024-10-08T05:35:46.221656+00:00",
"estimated_scheduled_ts": "2024-10-08T05:35:46.221656+00:00",
"external_id": null,
"failed_to_deliver_at_ts": null,
"finalization_type": "min",
"fixed_route_is_unreachable": false,
"fixed_route_journey_time": null,
"fixed_route_ride_time": null,
"fixed_route_wait_time": null,
"fixed_route_walk_time": null,
"geofence_id": 1613,
"geofence_ids": [
1613
],
"groups": [],
"h3": "0040062244546130126251",
"id": 50801404,
"initial_close_time_ts": null,
"initial_close_time_ts_dynamic": null,
"initial_max_trip_duration": null,
"initial_open_time_ts": null,
"lat": 11.69046292,
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_code": "",
"location_name": "Warehouse",
"lon": 101.516127,
"matrix_timestamp": null,
"max_slack": 3600,
"max_trip_duration": 27000,
"min_trip_duration": null,
"modified_at": "2024-10-08T05:33:47.794259+00:00",
"node_type": "pickup",
"offer_should_be_auto_accepted": null,
"open_time_ts": "2024-10-08T03:30:00+00:00",
"open_time_ts_dynamic": null,
"partial_route_index": null,
"passenger_count": 0,
"penalty": 1000000,
"processing_order_timestamp": null,
"ready_to_board_at": null,
"resource_uri": "/api/v2/node/50801404",
"scheduled_ts": "2024-10-08T03:30:00.000734+00:00",
"service_time": 0,
"simulation": "/api/v2/simulation/177821",
"slack": 238.8,
"started_service_at_ts": null,
"status": "completed",
"time_windows": null,
"transfer_type": "depart_at",
"transit_stop": null,
"trip_cost": 0,
"uid": "cad4f25a-73c3-41b1-887c-682b93f3dd3f",
"user_accepted_offer_at": null,
"vehicle_characteristics": {
"ALL": 1,
"SVB1": 0,
"SVB2": 0,
"weight": {
"max": 4,
"min": 0
}
},
"vehicle_labels": {
"or": [
"10"
]
},
"visited_at_ts": null,
"walking_distance_to_node": 0,
"walking_time_to_node": 0,
"weight": null
}

or for a drop off node

See JSON payload
Drop off node data
{
"allow_jump": false,
"arrived_to_location_ts": null,
"assigned_vehicle": "/api/v2/vehicle/17789910",
"assignment_order": 6,
"boarding_pass": "047",
"booking": "/api/v2/booking/23286180",
"booking_group_uid": "47b2609f-2024-1008-0659-4d53afe4a5f3",
"booking_uid": "47b2609f-2024-1008-0659-4d53afe4a5f3",
"cancelled_at_ts": null,
"close_time_ts": "2024-10-08T09:00:00+00:00",
"close_time_ts_dynamic": "2024-10-08T09:00:00+00:00",
"completed_service_at": "2024-10-08T06:30:07.218000+00:00",
"created_at": "2024-10-07T23:25:00.557228+00:00",
"customer_id": null,
"data": {
"dropoff_address": "P3522",
"dropoff_city": "Thailand",
"dropoff_country": "TH",
"dropoff_region": "Thailand",
"external_id": "45",
"is_dropoff_end_of_trip": true,
"is_pickup_end_of_trip": false,
"order_identity": {
"external_id": "45",
"simulation_id": 177821
},
"pickup_address": "Warehouse",
"pickup_city": "Thailand",
"pickup_country": "TH",
"pickup_customer_name": "Warehouse",
"pickup_location_lat": "12.69046292",
"pickup_location_lon": "101.516127",
"pickup_location_name": "Warehouse",
"pickup_region": "Thailand"
},
"demand": {
"cbcm": -2828700,
"undefined": 0,
"weight": -962
},
"departed_at_ts": null,
"display_name": "P3522",
"dynamic_break": null,
"end_of_trip": true,
"estimated_arrival_time": null,
"estimated_earliest_arrival_ts": "2024-10-08T06:32:00.578048+00:00",
"estimated_scheduled_ts": "2024-10-08T06:36:39.900734+00:00",
"external_id": null,
"failed_to_deliver_at_ts": null,
"finalization_type": "min",
"fixed_route_is_unreachable": false,
"fixed_route_journey_time": null,
"fixed_route_ride_time": null,
"fixed_route_wait_time": null,
"fixed_route_walk_time": null,
"geofence_id": 1613,
"geofence_ids": [
1613
],
"groups": [],
"h3": "0040062244542150160631",
"id": 50801567,
"initial_close_time_ts": null,
"initial_close_time_ts_dynamic": null,
"initial_max_trip_duration": null,
"initial_open_time_ts": null,
"lat": 12.73208162,
"lifo_order_check": false,
"lifo_order_penalty": null,
"location_code": "",
"location_name": "P3522",
"lon": 101.4874158,
"matrix_timestamp": null,
"max_slack": 3600,
"max_trip_duration": 27000,
"min_trip_duration": null,
"modified_at": "2024-10-08T06:30:07.366347+00:00",
"node_type": "dropoff",
"offer_should_be_auto_accepted": null,
"open_time_ts": "2024-10-08T05:00:00+00:00",
"open_time_ts_dynamic": null,
"partial_route_index": null,
"passenger_count": 0,
"penalty": 1000000,
"processing_order_timestamp": null,
"ready_to_board_at": null,
"resource_uri": "/api/v2/node/50801567",
"scheduled_ts": "2024-10-08T06:36:39.900734+00:00",
"service_time": 1800,
"simulation": "/api/v2/simulation/177821",
"slack": 0,
"started_service_at_ts": null,
"status": "completed",
"time_windows": null,
"transfer_type": "depart_at",
"transit_stop": null,
"trip_cost": 0,
"uid": "f54331ec-9fa2-4167-aba0-29fe73d97ee0",
"user_accepted_offer_at": null,
"vehicle_characteristics": {
"ALL": 1,
"SVB1": 0,
"SVB2": 0,
"weight": {
"max": 4,
"min": 0
}
},
"vehicle_labels": {
"or": [
"10"
]
},
"visited_at_ts": null,
"walking_distance_to_node": 0,
"walking_time_to_node": 0,
"weight": null
}

Webhook or websockets method

To configure a webhook refer configuring webhook section, for setting up websockets, refer configuring websockets section.

For ETA purposes, message_type=vehicle_arrival under vehicle_group agent type.

Once a client application is subscribed to the even stream, it will start receiving events following the schema:

Vehicle arrival schema
{
'simulation_id': id,
'agent_type': 'vehicle_group',
'message_type': 'vehicle_arrival',
'agent_id': String agent_id,
'data': {
'vehicle_id': integer vehicle id
'vehicle_agent_id': UUID Vehicle.agent_id
'booking_id': integer booking ID
'booking_uid': UUID booking UID
'node_id': integer node ID
'node_uid': node UID
'estimated_earliest_arrival_ts': timestamp of possible earliest arrival (not taking into account service times along the route and earliness)
'estimated_scheduled_ts': estimated time of starting servicing the node (taking into account service times along the route and earliness)
'scheduled_ts': scheduled timestamp, to compare scheduled and ETA
},
'current_sim_ts': simulation timestamp,
'server_ts': server timestamp,
}

If the consuming applications requires other additional time stamps, it can poll the data as described from node data using value of node_id in the message.

tip

If the client application fails to respond with an HTTP 20X status code, the SWAT server will attempt to retry sending the message five times with exponential backoff before abandoning it.

Routing API method

To obtain an estimated time of arrival (ETA) at any given moment, an alternative method is to recalculate the route from the vehicle's current location to the next upcoming node. This method can also be used if the SWAT Driver App is not in use, and the SWAT backend system is unaware of the vehicle's current location.

To achieve this, the client application should retrieve the next node scheduled for the vehicle and capture the node's latitude and longitude. The GET /api/v2/vehicle/<vehicle_id>/assigned_nodes endpoint can be used to retrieve a list of the vehicle's upcoming assigned nodes, which can then be filtered by scheduled_ts to retrieve the next scheduled node.

The response will include the node's coordinates, which can then be used to obtain a route to that node.

tip

The route will depend on the vehicle's actual current location and may differ from the originally planned route. However, the same routing profile that was applied during optimization will be used to ensure a high level of route consistency.

To retrieve route for a vehicle, GET /vehicle_route_proxy/<vehicle_id>/<wkt_waypoints> endpoint then can be used, where WKT Waypoints will represent at a minimum the current location of the vehicle and the coordinates of the next upcoming node. The request will return a result similar to:

See JSON payload
Example route
{
"code": "Ok",
"waypoints": [
{
"name": "",
"hint": "b9V9gHHVfYAaAAAAFwAAADMAAAC2AAAAvJ0hQfeMC0EjbpxBCBqNQhoAAAAXAAAAMwAAALYAAAD5AgAAwsD9BZDm0AAfwf0FX-bQAAQADw1YmAJi",
"distance": 11.428674,
"location": [
100.516034,
13.690512
]
},
{
"name": "เจริญราษฎร์ 7 แยก 29-1-2",
"hint": "T9J9gFHSfYA5AAAAOwAAAAAAAAAAAAAAkRH_QaeEAEIAAAAAAAAAADkAAAA7AAAAAAAAAAAAAAD5AgAAIcj9BZ3u0ADvyP0FL-7QAAAADw1YmAJi",
"distance": 25.392264,
"location": [
100.517921,
13.692573
]
},
{
"name": "",
"hint": "TtJ9gKvSfYAzAAAAGAAAAAAAAACmAAAAL8LlQfXSTkEAAAAACoq3QjMAAAAYAAAAAAAAAKYAAAD5AgAAnsn9BSbv0ABTyf0Fk-7QAAAATwdYmAJi",
"distance": 18.17438,
"location": [
100.518302,
13.69271
]
}
],
"routes": [
{
"geometry": "_hrbYck_v~D|e@rUxa@kmAgr@c]bL{[krBaw@e`@z|@{N_HwN}GdF{M",
"weight": 149.6,
"distance": 832.1,
"duration": 149.6,
"weight_name": "routability",
"legs": [
{
"summary": "",
"distance": 771.4,
"duration": 133.7,
"weight": 133.7,
"steps": []
},
{
"summary": "",
"distance": 60.7,
"duration": 15.9,
"weight": 15.9,
"steps": []
}
]
}
],
"uuid": "c2a247ad-b556-4abe-924d-bc56835b201f"
}

warning

Whenever this request occurs, the corresponding event is transmitted via websockets or webhooks. A separate microservice processes this event, requesting and generating a vehicle routing path. The path is computed based on the stored vehicle's current position and the final assigned node. This path is stored in the vehicle's path attribute.

In routes object, duration field includes total duration of the route in seconds that can be used by a client to calculate ETA by adding it to current time.