ETA for a delivery or pickup
There are three types of ETA (Estimated Time of Arrival) times available for nodes:
- Estimated scheduled time
scheduled_tsthat is calculated at an optimization step and is retained throughout operations - Estimated scheduled time that represents realistic estimated time in
estimated_scheduled_tsfield. Initial value is during optimization run of the simulation. - Estimated scheduled time that represents the earliest possible arrival time in
estimated_earliest_scheduled_tsfield
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_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.
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_tsfrom the simulation optimization run - Assigned nodes must have non-zero latitude and longitude
- ETA considers
vehicle.start_timeandvehicle.should_wait_untilfor 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:
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 tonullin 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). Considersservice_timefor previous nodes, but notslackarrived_to_location_ts: Represents the timestamp of the arrival to the node. Updated when an incoming message from driver is received. Can benullif this feature is not used in the Driver Application.departed_at_ts: Deprecated. Should not be used and is set tonullin 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 isfail_to_deliver, otherwise isnullstarted_service_at_ts: Deprecated. Should not be used and is set tonullin 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 tonullin node data.completed_service_at: Represents time when the node has been marked ascompletedslack: 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.
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:
Aas a starting node (pickup)Bwith open time 10AM (drop off)Cwith open time 11AM (drop off)- Service time for both nodes A and B is 5 min
- Travel time between node
BandCis 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:
- By polling
node_status - By subscribing and receiving webhooks or messages through websocket
- By calculating time using current vehicle location and Routing API by the client
Polling Node Data Method
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
{
"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
{
"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:
{
'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.
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.
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
{
"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"
}
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.