NAV
shell python javascript

Introduction

Optimisation of assets including vehicles, goods, people and energy. Our powerful algorithms cut costs and emissions while improving productivity. Trakm8 Vortex is a suite of algorithms that solve Vehicle Routing Problems.

Trakm8 Vortex API supports both REST and WebSocket communication protocols. The request and response payload format uses JSON.

Vehicle routing problems can take a long time to solve as they are computationally intensive.

A key challenge that faces the logistics sector is the underlying complexity of managing mobile assets efficiently while meeting business objectives. Fleets are become larger, interacting with and adopting new technologies such as electric vehicles and eventually self-driving vehicles. The need for optimisation algorithms which produce realistic solutions, given ever more complex constraints, is growing exponentially.

The challenge is creating effective algorithms which scale. This is where Trakm8 Vortex excels. With our cloud based, highly scalable algorithms we are a recognised world leader in optimisation solutions.

The constrained vehicle routing problem (CVRP) is a combinatorial challenge that requires solving the following two problems together: 1. decide which vehicles will do which tasks 2. for each vehicle, decide the best sequence for the tasks assigned to it. The tasks can be deliveries, collections, transfers, engineering or maintenance visits, or any of a variety of other jobs to be done at some specific location; there maybe many constraints such as time-windows, tools, load and waiting times, capacity & weight, operator & driver skills, vehicle characteristics (e.g. lift or crane), battery charging time, battery capacity, vehicle range and many more constraints.

The routing uses a connected graph to mathematically model the segments of a road network and this has its own constraints such as speed limits, turn restrictions, truck restrictions (e.g. width, height, weight), legal restrictions, traffic patterns, weather and so on. Given all of these constraints the challenge is to quickly calculate good solutions for CVRPs that may have, typically, from 5 to 500 vehicles and from 10s to 10000s of jobs per day. Computationally this is a big challenge; however the use of cloud computing, parallel processing and centralised web-services has allowed significant computational capacity to be deployed. Even small vehicle fleets can access huge computational power which would otherwise be unaffordable.

Licence

The use of this application is subject to the Trakm8 End User License Agreement (EULA) and by accessing the application, you agree that any use you make of the application shall be in strict compliance with the EULA. The EULA is available on request. Contact information for Trakm8 Limited is available at www.trakm8.com. Software distributed under the EULA is distributed on an "AS IS" basis WITHOUT WARRANTY OF ANY KIND, either express or implied. Copyright © 1999-2019 Trakm8 Limited. All Rights Reserved. Use of this application is also subject to certain third party terms which are available at: https://legal.here.com/terms/b2bserviceterms/en

Background

The algorithms available are mainly ‘metaheuristic’ algorithms. This class of algorithm includes: genetic algorithms,evolutionary algorithms, tabu search and simulated annealing. Vortex also includes algorithms that are hybrids, combining aspects of more than one different algorithm.

Some examples in the Vortex algorithms suite include:

RESTful API

The API is based on the principles of a RESTFul web services. The payload is a JSON encoded format. The calculation of an optimised schedule can take several minutes. Large and complex requests with constraints and hundreds of vehicles will take longer to execute. For this reason the initial HTTP Post request returns immediately with a unique ID for this request. The client must use this ID to poll the server until a solution is available. Polling of the server with the ID will provide progress updates.

POST https://DOMAIN:PORT/RESOURCE?APIKEY=xxxxxx

The content payload is JSON

In summary the client process is:

  1. Client sends HTTPS POST Request with the JSON payload; the server returns with a request ID; the algorithm calculation starts.
  2. Client repeatedly sends HTTPS GET Request with the request ID appended in the URL; the sever replies with progress update.
  3. Finally, the client send a HTTPS GET Requests with the request ID; the server replies with the complete schedule.

The recommended poll rate is that the client should not send requests more frequently than 10 seconds. The server responds with HTTP status code 200. The final results is stored for a short time (~ 1 hour), so the client may make subsequent poll requests.

The payload format is defined in V1 or V2.

Validate a request

Sometimes, it might be required to check the Vortex request for syntax/type errors quickly without executing the algorithm. This can be done using the validate end-point.

Validate returns basic validation errors immediately, after posting the request JSON to Vortex.

Validate example for V2

import json
import requests

data = '{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}'


r = requests.post(url = "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/validate?apikey=APIKEY", data = data)

print(r.status_code, r.text)
var data = {"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]};

var xmlhttp = new XMLHttpRequest();   // new HttpRequest instance 
xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if (this.status == 200)
        console.log(JSON.stringify(this))
    }
  };

xmlhttp.open("POST", "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/validate?apikey=APIKEY");
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(data));

 curl -XPOST "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/validate?apikey=APIKEY" -d '{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}'

 curl -XPOST "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/validate?apikey=APIKEY" -d '{}'

Convert JSON V1 to V2

For JSON v1 to v2 migration purposes, it is sometimes helpful to convert a Vortex JSON V1 request to V2. This can be done by the v1tov2 end-point. However, this way is not recommended to be used in production as this is only designed to help Vortex users to migrate to V2 JSON with less hassle.

Convert V1 to V2 example

import json
import requests

data = '{"ContinueWithoutImprovementTime":9,"PlanDate":"02/01/2019","TotalRunTime":1200,"Depots":[{"Latitude":51.5319517,"Longitude":0.1078958,"Tag":"2|DEPOT"}],"Drivers":[{"Tag":"250|DRIVER","MaximumDrivingDuration":"09:07:00"}],"Requests":[{"Latitude":51.6003284,"Longitude":0.3166651,"Tag":"268887|ORDER"}],"Vehicles":[{"Depot":"2|DEPOT","Driver":"250|DRIVER","Tag":"250|VEHICLE"}]}'


r = requests.post(url = "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v1tov2?apikey=APIKEY", data = data)

print(r.status_code, r.text)
var data = {"ContinueWithoutImprovementTime":9,"PlanDate":"02/01/2019","TotalRunTime":1200
,"Depots":[{"Latitude":51.5319517,"Longitude":0.1078958,"Tag":"2|DEPOT"}],"Drivers":[{"Tag":"250|DRIVER","MaximumDrivingDuration":"09:07:00"}],"Requests":[{"Latitude":51.6003284,"Longitude":0.3166651,"Tag":"2688
87|ORDER"}],"Vehicles":[{"Depot":"2|DEPOT","Driver":"250|DRIVER","Tag":"250|VEHICLE"}]};

var xmlhttp = new XMLHttpRequest();   // new HttpRequest instance 
xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if (this.status == 200)
        console.log(JSON.stringify(this))
    }
  };

xmlhttp.open("POST", "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v1tov2?apikey=APIKEY");
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(data));
curl -XPOST "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v1tov2?apikey=APIKEY" -d '{"ContinueWithoutImprovementTime":9,"PlanDate":"02/01/2019","TotalRunTime":1200
,"Depots":[{"Latitude":51.5319517,"Longitude":0.1078958,"Tag":"2|DEPOT"}],"Drivers":[{"Tag":"250|DRIVER","MaximumDrivingDuration":"09:07:00"}],"Requests":[{"Latitude":51.6003284,"Longitude":0.3166651,"Tag":"2688
87|ORDER"}],"Vehicles":[{"Depot":"2|DEPOT","Driver":"250|DRIVER","Tag":"250|VEHICLE"}]}'

Terminate a schedule

You can stop the V2 scheduling request at any time of the execution as long as you know the RequestId. This can be done by sending a DELETE RESTFul request to the service.

DELETE https://DOMAIN:PORT/v2/REQUEST_ID?APIKEY=xxxxxx

Please note that when there is no active process dealing with the targeted scheduling request, the service would return an error message with HTTP status code 404 Not Found.

Terminate Example

You can terminate a running V2 schedule request with RequestId = f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1.

Terminate a schedule as Follows:

import requests

r = requests.delete(url = "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1?apikey=APIKEY")

print(r.status_code)
print(r.text)
var xmlhttp = new XMLHttpRequest();   // new HttpRequest instance 
xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      console.log(JSON.stringify(this))
    }
  };

xmlhttp.open("DELETE", "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1?apikey=APIKEY");
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(null);
 curl -XDELETE "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2/f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1?apikey=APIKEY"

RESTFul V2 Example

  1. POST the Request which returns with a request Id.

POST request example:

import json
import requests

data = 
{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}


r = requests.post(url = "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2?apikey=APIKEY", data = json.dumps(data))

if r.status_code == 200:
  print(r.text)
var data = {"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]};

var xmlhttp = new XMLHttpRequest();   // new HttpRequest instance 
xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if (this.status == 200)
        console.log(JSON.stringify(this))
    }
  };

xmlhttp.open("POST", "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2?apikey=APIKEY");
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(data));

 curl -XPOST "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2?apikey=APIKEY" -d '{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}'

Request Id:

{
  "RequestId":"d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a"
}
  1. GET the response using previous request Id, which would return the result plan with status: HTTP/1.1 200 OK. If the results are not ready yet, the service response with status: HTTP/1.1 202 Accepted and may return any intermediate results if there are any.

GET response using request Id:

import requests
r = requests.get("https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a?apikey=APIKEY")

if r.status_code == 200:
  print(r.text)

var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if (this.status == 200)
        console.log(JSON.stringify(this));
    }
  };
  xhttp.open("GET", "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a?apikey=APIKEY", true);
  xhttp.send();
curl -v -XGET "https://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a?apikey=APIKEY"

JSON v2 response:

{
  "Result": "SCHEDULES",
  "Data": {
    "SolutionSet": [
      {
        "Objectives": {
          "BalanceSecond": 0,
          "CarbonKg": 3.17571,
          "Cost": 1181.12,
          "DistanceKm": 317.571,
          "OvertimeSecond": 0,
          "PlannedJobsNumber": 1,
          "ShiftsNumber": 1,
          "TotalTimeSecond": 18030,
          "UnplannedJobsNumber": 0,
          "Penalties": 0
        },
        "Schedule": [
          {
            "Actions": [
              {
                "ActionType": "SHIFTSTART",
                "Day": 1,
                "StartLocationGeohash": "u10q4r59",
                "StartLatLon": [51.7184,0.455418],
                "StartTime": "06:00:00",
                "Drivers": ["Driver_1"]
              },
              {
                "ActionType": "BRIEF",
                "Day": 1,
                "StartLocationGeohash": "u10q4r59",
                "StartLatLon": [51.7184,0.455418],
                "StartTime": "06:00:00",
                "EndTime": "06:15:00",
                "Drivers": [
                  "Driver_1"
                ]
              },
              {
                "ActionType": "LOAD_UNLOAD",
                "Depot": "Depot_1",
                "Day": 1,
                "StartLocationGeohash": "u10q4r59",
                "StartLatLon": [51.7184,0.455418],
                "StartTime": "06:15:00",
                "EndTime": "06:21:00",
                "Drivers": [
                  "Driver_1"
                ],
                "Unload": [],
                "Load": ["Job_1"],
                "OnBoard": [["Compartment1","Job_1","100",[20,21,22],[200,400,400]],["Overload",[0,0,0]]]
              },
              {
                "ActionType": "TRAVEL",
                "Day": 1,
                "StartLocationGeohash": "u10q4r59",
                "EndLocationGeohash": "gcp7b4v4",
                "StartLatLon": [51.7184,0.455418],
                "EndLatLon": [51.2997,-1.04765],
                "StartTime": "06:21:00",
                "EndTime": "08:31:00",
                "DistanceKm": 156.85,
                "Drivers": ["Driver_1"]
              },
              {
                "ActionType": "DELIVER",
                "Day": 1,
                "IdString": "Job_1",
                "StartLocationGeohash": "gcp7b4v4",
                "StartLatLon": [51.2997,-1.04765],
                "StartTime": "08:31:00",
                "EndTime": "08:47:00",
                "OnBoard": [["Compartment1",[0,0,0],[200,400,400]],["Overload",[0,0,0]]],
                "Drivers": ["Driver_1"]
              },
              {
                "ActionType": "TRAVEL",
                "Day": 1,
                "StartLocationGeohash": "gcp7b4v4",
                "EndLocationGeohash": "u10q4r59",
                "StartLatLon": [51.2997,-1.04765],
                "EndLatLon": [51.7184,0.455418],
                "StartTime": "08:47:00",
                "EndTime": "11:00:00",
                "DistanceKm": 160.721,
                "Drivers": ["Driver_1"]
              },
              {
                "ActionType": "SHIFTEND",
                "Day": 1,
                "StartLocationGeohash": "u10q4r59",
                "StartLatLon": [51.7184,0.455418],
                "StartTime": "11:00:00",
                "Drivers": ["Driver_1"]
              }
            ],
            "VehicleId": "Vehicle_1"
          }
        ],
        "UnplannedJobs": []
      }
    ],
    "ExecutionTimeSecond": 99
  },
  "TimestampUnix": 1553243894,
  "ScheduleId": "d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a",
  "Finished": true
}

JSON V2 advantages over V1

V2 JSON provides many advantages over JSON V1, the only downside at the moment is that it is still under development and might be changed to support more features. On the other hand, JSON V1 is stable and unlikely to change but it is limited. JSON V2 provides the following advantages:

  1. More expressive JSON format: Compared with JSON V1, the new version is more readable.
  2. Jobs and Depots can have any number of availability time windows, and these can be different for different days of the plan.
  3. If required, drivers can be allocated to vehicles automatically.
  4. Vehicles can be assigned to multiple depots: In JSON V1 each vehicle have to be configured with a valid Depot setting which specifies only one Depot which can be used with the vehicle. This changed in JSON V2, where a vehicle can be configured with more than one depot to use.
  5. If provided, v2 can work with figures for the quantities of goods at each depot, and ensure that jobs are only loaded at depots with enough supply, and are only unloaded at depots with enough capacity.
  6. Multiple drivers can be assigned to a vehicle: There is no way to configure a vehicle with multiple drivers in JSON V1, this is fixed in JSON V2 where it is easy to configure a vehicle with multiple drivers.
  7. v2 includes more detailed calculations in relation to driver break, working time, and rest time regulations, ensuring compliance as well as efficiency.
  8. More advanced speed profiles: JSON V2 provides a possibility to configure vehicles with more rich speed profiles which can take the time of the day into account which gives more control to simulate for example the rush hour traffic. On the other hand, a generic speed profiles can be used with JSON V1.
  9. Support for road restrictions: V1 doesn't support configuring vehicle features such as width, height, and weight. This is important to support road restrictions. Where as, JSON V2 provides a room for those settings.

Websocket API

To open a websocket use the code

from websocket import create_connection

ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY")
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
>

Vortex is a framework for optimization and solving complex problems which may take time to execute. Thus, to Vortex, you need to open a websocket connection with the server. This provides a better way to update the user about current status while the execution is taking place.

Once a websocket is opened, users can interact with vortex using a list of supported commands. These commands allow users to choose a specific algorithm to use, provide the problem input, and also interact with the algorithm while it is running.

All commands has a generic JSON format

{
  "Command": "COMMAND_NAME",
  "Data": //JSON PAYLOAD
}

The value COMMAND_NAME need to be replaced with the actual Command. Whereas, PAYLOAD should be a valid JSON which has different format depending on the command itself.

Tm8Vortex supports list of commands which allow users to interact with the server and define their preferences.

Command Key/Value pairs

Key Value
Command String value represents the required command, command names are case insensitive. Currently the only supported value is: RUN. More commands are to be added in the future.
Data A valid JSON payload which provide the required input data for the command. The payload format is different from command to another.

Run Command

Run command allow clients to set a problem and run it using a specified algorithm. The client can specify an algorithm to tackle the problem using the key Algorithm in the payload; however when the Algorithm key is missing (or has the value auto) Vortex will decide the best algorithm to use.

To execute a RUN command use the following code:

from websocket import create_connection
ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY")

ws.send('''{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[],"Locations":[{"LocationGeohash":"u10q4r59","Latitude":51.71837,"Longitude":-1.0475999},{"LocationGeohash":"gcp7b4v4","Latitude":51.29964,"Longitude":0.455261394}]}}''')
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[],"Locations":[{"LocationGeohash":"u10q4r59","Latitude":51.71837,"Longitude":-1.0475999},{"LocationGeohash":"gcp7b4v4","Latitude":51.29964,"Longitude":0.455261394}]}}')
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
> {"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}

Run has the following JSON format

{
  "Command": "Run",
  "Data": {
    "NumDays": 1,
    "PlanDate": "17/12/2018",
    "ContinueWithoutImprovementTime": "90s",
    "TotalRunTime": "5m",
    "Depots": [{
        "IdString": "Depot_1",
        "TimeWindows": [{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],
        "PossibleDays": [1],
        "LoadingTime": "00:00:30",
        "LocationGeohash": "u10q4r59",
        "IncludeDriverCharacteristics": [],
        "Attributes": ["GoodsSupply","GoodsReceipt"],
        "Characteristics": []
      }],
    "Drivers": [{
        "IdString": "Driver_1",
        "BriefingTime": "00:15:00",
        "DebriefingTime": "00:00:00",
        "Characteristics": [],
        "CostPerDay": 22,
        "CostPerHour": 3.5,
        "Availability": [{"Day":1,"Number":9}],
        "PossibleDays": [1],
        "MaximumOvertime": "00:30:00",
        "OvernightRadiusKm": 180,
        "OvernightCost": 75,
        "OvertimeCostPerHour": 7,
        "ShiftEarlyStartTime": "06:00:00",
        "ShiftLateStartTime": "08:00:00"
      }],
    "VehicleDriverMap": [],
    "VehicleDepotMap": [{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],
    "Vehicles": [{
        "IdString": "Vehicle_1",
        "DeliveriesFirst": false,
        "PossibleDays": [1],
        "CarbonPerKmKg": 0.01,
        "CarryingWeightKg": 1200,
        "CostPerDay": 30,
        "CostPerDayofNonUse": 20,
        "CostPerKm": 3.5,
        "MaxLoadWeightKg": 3000,
        "DepotLoadingTime": "5m",
        "Capacities": [200,400,400],
        "Characteristics": [],
        "StartLocationGeohash": "u10q4r59",
        "EndLocationGeohash": "u10q4r59",
        "OpenStart": false,
        "OpenEnd": false,
        "MaximumLoadUnloadTime": "12:00:00",
        "MaximumWorkingTime": "15:00:00",
        "OperatingRadiusKm": 1000000,
        "PreLoaded": false,
        "UnloadAfterShift": true,
        "SuppressDepotReload": false,
        "Type": "HGV",
        "StartTime": "04:00:00"
      }],
    "VehicleProfiles": [{
        "Type": "HGV",
        "SpeedProfiles": [{
            "SpeedsMetersPerSecond": [25,25,20,20,20,20,20],
            "TimeFrom": "00:00:00",
            "TimeTo": "24:00:00"
          }],
        "ChargingParameters": {}
      }, {
        "Type": "TallVan",
        "SpeedProfiles": [{
            "SpeedsMetersPerSecond": [20,20,20,20,20,10,10],
            "TimeFrom": "00:00:00",
            "TimeTo": "24:00:00"
          }],
        "ChargingParameters": {}
      }, {
        "Type": "MiniVan",
        "SpeedProfiles": [{
            "SpeedsMetersPerSecond": [25,25,25,22,22,20,20],
            "TimeFrom": "00:00:00",
            "TimeTo": "24:00:00"
          }],
        "ChargingParameters": {}
      }],
    "Jobs": [{
        "IdString": "Job_1",
        "Service": "delivery",
        "Vehicle": null,
        "RunOrder": null,
        "Day": null,
        "CanSplitOverCompartment": false,
        "PossibleDays": [1],
        "Quantities": [20,21,22],
        "Type": "stocktype_2",
        "TimeWindows": [{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],
        "FixedVisitDuration": "15:00",
        "ServiceVisitDuration": "01:00",
        "LoadTime": "1m",
        "RevenueIncome": 100,
        "Priority": 1,
        "LocationGeohash": "gcp7b4v4"
      }],
    "JobConstraints": []
  }
}

Attach Command

Attach command can be used to get updates on an already running scheduling request. Thus, a requestID is required as an input for this command. It is possible to connect more than one websocket with an Attach command to the same scheduling request. In this case, Vortex would broadcast plan updates to multiple websockets.

Example: in order to get updates on scheduling request with Id RequestId = 279da947bbf867dfc149823171724c65f684ac842a86d7b9b69691fa35fd867e.

To execute an ATTACH command use the following code:

from websocket import create_connection
ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2?apikey=APIKEY")

ws.send('''{"Command":"Attach","Data":{"RequestId":"279da947bbf867dfc149823171724c65f684ac842a86d7b9b69691fa35fd867e"}}''')
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Attach","Data":{"RequestId":"279da947bbf867dfc149823171724c65f684ac842a86d7b9b69691fa35fd867e"}}')
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
>{"Command":"Attach","Data":{"RequestId":"279da947bbf867dfc149823171724c65f684ac842a86d7b9b69691fa35fd867e"}}

Attach has the following JSON format

{
  "Command": "Attach",
  "Data": {
    "RequestId": "279da947bbf867dfc149823171724c65f684ac842a86d7b9b69691fa35fd867e"
  }
}

Terminate Command

Terminate command enables you to stop a running scheduling request. You should already have the scheduling request id in order to be able to call this command. For example, in order to terminate the scheduling request with Id RequestId = f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1:

To execute a TERMINATE command use the following code:

from websocket import create_connection
ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest/v2?apikey=APIKEY")

ws.send('''{"Command":"Terminate","Data":{"RequestId":"f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1"}}''')
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Terminate","Data":{"RequestId":"f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1"}}')
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
>{"Command":"Terminate","Data":{"RequestId":"f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1"}}

Terminate has the following JSON format

{
  "Command": "Terminate",
  "Data": {
    "RequestId": "f06fa1bd248f5759ee86dc49fbe14c35b3cda3e41ce3fac0e04cb0b461bdd7c1"
  }
}

Result Key/Value pairs

Websocket server response to each command with a result JSON payload, which is defined as follows:

Key Value
Result A string representing the type of the result, its value could be OK, ERROR, NOTE, STATS, and SCHEDULES.
Data A JSON payload provide more details about the result, it is different for each result type.

Complete simple test

This is a complete small demo example:

#!/usr/bin/python

import time
import websocket
import os

PORT = 8443
DOMAIN = "api-core01-dev.trakm8.net"
SERVICE = "/bdservices/vortex/api/latest"
APIKEY = os.environ.get('APIKEY_CORE01_DEV')

URL = "wss://"+DOMAIN+":"+str(PORT)+SERVICE+"?apikey="+APIKEY
print(URL)

from websocket import create_connection
ws = create_connection(URL)

ws.send('''{
  "Command": "Run",
  "Data": {
  "NumDays":  1,
  "PlanDate": "17/12/2018",
  "ContinueWithoutImprovementTime": "90s",
  "TotalRunTime": "5m",
  "Depots": [{
      "IdString": "Depot_1",
      "TimeWindows":  [{
          "Day":  1,
          "Windows":  [["06:00:00", "20:00:00"]]
        }],
      "PossibleDays": [1],
      "LoadingTime":  "00:00:30",
      "LocationGeohash":  "u10q4r59",
      "IncludeDriverCharacteristics": [],
      "Attributes": ["GoodsSupply", "GoodsReceipt"],
      "Characteristics":  ["depotskill_1"]
    }],
  "Drivers":  [{
      "IdString": "Driver_1",
      "BriefingTime": "00:15:00",
      "DebriefingTime": "00:00:00",
      "Characteristics":  [],
      "CostPerDay": 22,
      "CostPerHour":  3.5,
      "Availability": [{
          "Day":  1,
          "Number": 9
        }],
      "PossibleDays": [1],
      "MaximumOvertime":  "00:30:00",
      "OvernightRadiusKm":  180,
      "OvernightCost":  75,
      "OvertimeCostPerHour":  7,
      "ShiftEarlyStartTime":  "06:00:00",
      "ShiftLateStartTime": "08:00:00"
    }],
  "VehicleDriverMap": [],
  "VehicleDepotMap":  [{
      "VehicleId":  "Vehicle_1",
      "DepotId":  "Depot_1"
    }],
  "Vehicles": [{
      "IdString": "Vehicle_1",
      "DeliveriesFirst":  false,
      "PossibleDays": [1],
      "CarbonPerKmKg":  0.01,
      "CarryingWeightKg": 1200,
      "CostPerDay": 30,
      "CostPerDayofNonUse": 20,
      "CostPerKm":  3.5,
      "MaxLoadWeightKg":  3000,
      "DepotLoadingTime": "5m",
      "Capacities": [200, 400, 400],
      "Characteristics":  [],
      "StartLocationGeohash": "u10q4r59",
      "EndLocationGeohash": "u10q4r59",
      "OpenStart":  false,
      "OpenEnd":  false,
      "MaximumLoadUnloadTime":  "12:00:00",
      "MaximumWorkingTime": "15:00:00",
      "OperatingRadiusKm":  1000000,
      "PreLoaded":  false,
      "UnloadAfterShift": true,
      "SuppressDepotReload":  false,
      "Type": "HGV",
      "StartTime":  "04:00:00"
    }],
  "VehicleProfiles":  [{
      "Type": "HGV",
      "SpeedProfiles":  [{
          "SpeedsMetersPerSecond":  [25, 25, 20, 20, 20, 20, 20],
          "TimeFrom": "00:00:00",
          "TimeTo": "24:00:00"
        }],
      "ChargingParameters": {
      }
    }, {
      "Type": "TallVan",
      "SpeedProfiles":  [{
          "SpeedsMetersPerSecond":  [20, 20, 20, 20, 20, 10, 10],
          "TimeFrom": "00:00:00",
          "TimeTo": "24:00:00"
        }],
      "ChargingParameters": {
      }
    }, {
      "Type": "MiniVan",
      "SpeedProfiles":  [{
          "SpeedsMetersPerSecond":  [25, 25, 25, 22, 22, 20, 20],
          "TimeFrom": "00:00:00",
          "TimeTo": "24:00:00"
        }],
      "ChargingParameters": {
      }
    }],
  "Jobs": [{
      "IdString": "Job_1",
      "Service":  "delivery",
      "Vehicle":  null,
      "RunOrder": null,
      "Day":  null,
      "CanSplitOverCompartment":  false,
      "PossibleDays": [1],
      "Quantities": [20, 21, 22],
      "Type": "stocktype_2",
      "TimeWindows":  [{
          "Day":  1,
          "Windows":  [["07:00:00", "20:00:00"]]
        }],
      "FixedVisitDuration": "15:00",
      "ServiceVisitDuration": "01:00",
      "LoadTime": "1m",
      "RevenueIncome":  100,
      "Priority": 1,
      "LocationGeohash":  "gcp7b4v4"
    }],
  "JobConstraints": []
 } 
}''')

while True:
    time.sleep(5)
    response = ws.recv()
    print(response)

ws.close()
wscat -c "wss://api-core01-dev.trakm8.net:8443/bdservices/vortex/api/latest?apikey=$APIKEY_CORE01_DEV"
connected (press CTRL+C to quit)
> {"Command":"Run","Data":{"NumDays":1,"PlanDate":"18/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":["depotskill_1"]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}

var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[],"Locations":[{"LocationGeohash":"u10q4r59","Latitude":51.71837,"Longitude":-1.0475999},{"LocationGeohash":"gcp7b4v4","Latitude":51.29964,"Longitude":0.455261394}]}}');

ws.onmessage = function (event) {
  var message = event.data;
  console.log(message);
}

V2 JSON Request

Supports both WebSocket and RESTful.

The following details the Key/Value pairs in the Data Object associated with "Run" command.

Key Description
Algorithm Default:"auto". This specifies the algorithm to be used to solve the problem in hand, if it is not passed (or configured as auto), Vortex will decide the best algorithm to use.
CurrentDateTime Default:"null". This is only required in specialised use cases involving same-day planning -- see Additional Job Keys. In most use-cases this should be ignored or left null. If used, the format is: "DD/MM/YY HH:MM:SS".
PlanDate Default: "DD/MM/YYYY" tomorrow's date in DD/MM/YYYY format. This is the date of the first day of the plan; in other words, if the first operation of the plan occurs at 08:00 when a driver leaves the depot on the first day of the plan, then that operation will happen at 08:00 on this date. various PossibleDays arrays (in Depot, Driver, Vehicle and Job objects) relate to this date as follows. E.g., if PossibleDays is [2,3,6] and PlanDate is 03/12/2018, this indicates availability on only 04/12/2018, 05/12/2018 and 08/12/2018.
PriorRequestID Default: null. This can be optionally populated using the requestID of a previous (very similar), or slightly modified request. Consequently, when populated, Vortex would be able to reuse previous schedules and possibly distance and time matrices to find optimal schedules much faster.
CacheMatrices Default: false. This is a boolean value to determine whether or not to cache distance and time matrices. It is useful to turn this flag on when current request is likely to be modified in the future and run again; in this scenario, caching matrices on current request would help Vortex to calculate modified future requests' matrices faster.
NumDays Default:1. The number of days that the plan is expected to cover. Usually this will be 1, but sometimes it could be several days. It is the number of days from the first day to the last day of the planning horizon, inclusive. Please note the following illustrative example: if the plandate is a Friday, and the horizon is 4 days (Friday, Saturday, Sunday, Monday), then Numdays is 4, even if days 2 and 3 never appear in any of the PossibleDays arrays.
LocationPrecisionMetre Default:1. This is a positive integer represents the precision vortex would use for input problem's locations. Less precision values mean that more adjacent locations might be considered as same location. Based on the LocationPrecisionMetre value, Vortex is clustering problem's locations into clusters each with maximum size of LocationPrecisionMetre in meters. When Vortex identify a group of geographic locations which can be located in a circle with diameter less than LocationPrecisionMetre value, all these geographic locations are represented by one of the group's locations; usually, the location in the centre is used to represent other group's locations.
ContinueWithout ImprovementTime Default:"5m". ContinueWithoutImprovementTime is a time (see Time Strings Format) indicating a stopping condition, the algorithm execution will stop if there is no improvements on the current results for longer than ContinueWithoutImprovementTime.
TotalRunTime Default:"1h". A time (see Time Strings Format) specifying the maximum time the algorithm can take to finish. Please note that execution might stop earlier if ContinueWithoutImprovementTime condition is met.
Depots Default:null. Specify a list of depots, for more info please check Depot Key/Value pairs. Note: there should always be at least one depot, except in the following case: when all jobs are Visits, and each vehicle has both StartLocationGeohash and EndLocationGeohash set.
Drivers Mandatory. Specify a list of drivers; at least one must be provided; for more info please check Driver Key/Value pairs. Note: a 'driver' in this context will not normally refer to an individual person. Instead, it is a collection of driver attributes that will be inherited by some of the vehicles (via the VehicleDriverMap - see below). For example, there might be two drivers, one named "EarlyShift" and one named "LateShift", holding the respective operating constraints.
Jobs Mandatory. Specify a list of job requests, each item has the format specified in Job Key/Value pairs. At least one job must be provided.
JobConstraints Default:null. Specify any constraints involving groups of jobs, see Job Constraints.
PlanProfile Default:null. For multi-day problems, this indicates the users preference for how the jobs are distributed over days. If the value is "Frontload", then (all else being the same) jobs will tend to be scheduled on earlier days. If the value is "Backload", then jobs will tend to be scheduled on later days. In both cases, the Objectives will include a PlanProfile key showing the job distribution as an array (see Objectives). If the value is null,"None" or "Display", then there will be no particular preference implemented, but in the latter case the PlanProfile will be included in the Objectives Object.
PriorityMode Default:"Strict". This is a string that specifies the approach that will be used in trying to favour the planning of higher-priority jobs. The default value,"Strict", means that vortex will put all its effort into trying to plan all of the highest priority jobs first, then all of the next-highest priority, and then the next-highest priority, and so on. However,"Strict" can have unwelcome consequences in some scenarios; for example, if a particular priority 1 job turns out to be too difficult to plan, then none of the prority 2, 3 (and so on) jobs will be planned (despite resources and time being available), because vortex expends all its effort on the difficult priority 1 job. To avoid such scenarios, the alternative value "Permissive" can be used; With "PriorityMode":"Permissive", vortex will still work harder to plan higher-priority jobs, but not exclusively, and will not be prevented from planning lower priority jobs when one or more higher-priority ones are proving problematic. Note: if both PriorityMode and RoutingMode (see below) are "Strict", then inaccessible jobs will have their priority downgraded to ensure that they don't block the planning of lower priority jobs.
RoutingMode Default:"Strict". This string specifies the approach used for dealing with locations that are not reachable. For example, the only way to access a particular job location might be to drive along a road that is too narrow for the available vehicles. In the latter scenario, if the RoutingMode is "Strict", then that job would remain unplanned. However, if the RoutingMode is "Permissive", then vortex will make estimations of the drive-time and drive-distances involved in getting to that location, and would allow it to be reached despite the restrictions (or other reasons) that would otherwise prevent access. In practice, this could correspond to the vehicle getting as close as it can to the location in question, while the collection or delivery is accomplished by, for example, liaison with a smaller vehicle provided by the customer. Note: if both PriorityMode (see above) and RoutingMode are "Strict", then inaccessible jobs will have their priority downgraded to ensure that they don't block the planning of lower priority jobs.
StockMixingRules Default:null. Optional: if one or more combinations of items cannot be carried in the same vehicle, the details can be expressed here. See Stock Mixing Rules.
StockSequenceRules Default:null. Optional: if there are rules about sequencing of stock within a compartment and/or at vehicle level, the details can be expressed here. See Stock Sequence Rules.
Vehicles Mandatory. Specify a list of vehicles. A vehicle is almost always a specific vehicle. For example, if the client has 25 vehicles available today, then there will be 25 vehicles in this array. At least one vehicle must be provided. For more info please refer to Vehicle Key/Value pairs.
VehicleDepotMap Default:null. This must be present, unless it is a rare case with no Depots provided (see above). This array defines the relationship between Vehicles and Depots, this is a list of mapping items defined in VehicleDepotMap Array. Normally there will be precisely one Vehicle/Depot pair for each vehicle. However it is possible for a vehicle to be mapped to multiple depots; this would mean that this vehicle is free to load, unload, or finish its route at any of these depots, irrespective of where it started its route.
VehicleDriverMap Default:null. Partially or fully defines the relationship between Vehicles and Drivers, this is a list of mapping items defined in VehicleDriverMap Array. Every vehicle will usually be mapped to precisely one driver. Meanwhile, because there may be several instances of each driver available (see Driver Key/Value pairs), one driver may be mapped to many vehicles. If this is is not present, drivers will be allocated to vehicles automatically; if it is present but only contains a partial map, then drivers will be allocated to the remaining vehicles. Automatic allocation of drivers will respect the Drivers' Availability constraints, and, if any, the Vehicles' DriverCharacteristics.
VehicleProfiles Mandatory. A list of vehicle types, giving a type name (e.g. "HGV", "minivan",...), a speed profile and some other data for each type VehicleProfiles Array. The list of types should cover each of the Vehicles in the list below.
AdjustSpeeds Default:1. All journeys (across all vehicle types and speed profiles) will have their speed changed by this factor. E.g. if AdjustSpeeds is 1.1 then all journeys will be 10% faster; if AdjustSpeeds is 0.5, all journeys will be 50% slower and journey times will be doubled.

The following additional key/value pairs modify the output in ways that might be useful to some customers.

Key Description
AdditionalDriverOutput Default:[]. This is an array of driver object json keys. If this is populated, then each output shift will contain a Drivers array at the same level as the Actions array (see Vehicle plan output). The contents will be duplications of the selected input attributes for the driver(s) allocated to the shift.
AdditionalServiceActionOutput Default:[]. This is an array of job object json keys. If this is populated, then all output Actions which relate to the processing of a job (the DELIVERY, COLLECTION or VISIT actions) will contain duplications of the selected input attributes for the job.
AdditionalVehicleOutput Default:[]. This is an array of vehicle object json keys. If this is populated, then each output shift will contain a Vehicle object at the same level as the Actions array (see Vehicle plan output). The contents will be duplications of the selected input attributes for the vehicle associated with the shift.
ApplyMinimumTravelTimeBetweenSameLocations Default:false. See MinimumTravelTime below.
EarlyOutputTime Default:null. Normally, the first full output will arrive after OutputInterval has elapsed. In specific circumstances, normally not faced by the end user, there may be a requirement to receive the first output more quickly. EarlyOutputTime can be set to achieve this -- e.g. "EarlyOutputTime":"3s".
FinalOutputChoice Default:"All". The final output is, by default, the complete set of solutions, which will vary in terms of the tradeoffs between cost, mileage, overtime, and so on. With this key, however, you can apply one of the three values: "BestCost", "BestDistance", or "BestBalance", limiting the final output to the corresponding single solution that provides the best value, respectively, for "Cost", "DistanceKm", or "BalanceMinutes".
FSSMTour Default:1. This is a positive integer algorithm parameter that influences performance. Any further explanation would require top-level security clearance.
InterimOutputChoice Default:"All". The interim output (at intervals of OutputInterval time), by default shows the complete set of solutions. With this key, however, you can limit the interim output to a single solution. The same values can be used as for FinalOutputChoice above.
MinimumTravelTime Default:"0s". This should be a valid time string (see Time Strings Format) which indicates the minimum amount of time that vortex will take for travel between any two different locations. In other words, even if the journey might take less time, according to the chosen routing service (and any speed modifiers, etc.), vortex will apply the minimum time. If the two locations are precisely the same, however (with the same LocationGeohash), the default is that this will not be applied. To change this, and apply the minimum when the locations are the same, the user can set ApplyMinimumTravelTimeBetweenSameLocations to true at top level.
OutputEmptyShifts Default:false. Normally, vortex outputs the plan for each shift that has jobs to do (a 'shift' is simply identified by a specific vehicle on a specific day). This plan consists of the list of Actions and the Vehicle's ID (see Vehicle plan output). If a shift is doing no jobs, then this shift is completely omitted from the output. However, if you set OutputEmptyShifts:true, then every shift with a driver allocated will appear in the output. If all else is default, then the empty shift will contain an empty array of actions (i.e. Actions:[]), the VehicleId, and the Day indicator. The 'Day' indicator is not provided in this way for populated shifts, since it is contained in the actions. Also, if other additional outputs have been specified (e.g. via AdditionalDriverOutput), they will also appear for the empty shifts.
OutputEndDay Default:false EndDay appears in the output usually only used for the OVERNIGHT action type. If OutputEndDay is set to true, then `EndDay" will also appear in any action which starts and finishes on different days -- e.g. a travel action that starts at 23:55:00 and finishes the next day at 00:15:32.
OutputInterval Default:"60s". The time interval between interim outputs showing the latest collection of solutions.
OutputMultipleServiceDetails Default:false. By default, when a vehicle does a series of collections/deliveries at a single customer site, vortex only provides an overall time frame for the site visit. If set to true, this will add further information to any tasks that are part of a multiple site visit. See About Multiple Actions at the same customer site.
OutputOnboard Default:"true". This is a Boolean value specifying whether or not the "OnBoard" information structure will be included in the output (see Action Key/Value pairs) -- "OutputOnBoard" is also an accepted spelling. This does not operate in FastMode, where we suggest to use OutputPayloadQuantities instead.
OutputPayloadQuantities Default:false. If this Boolean is set to true, extra information will be included in output actions about the load on the vehicle at each stage in the plan. This is a simplified and stripped-down alternative to OutputOnboard, providing information relating only to the first compartment on the vehicle, and the first Stocktype. see Action Key/Value pairs for details.
OutputSeconds Default: false. By default, the times in the vortex output are snapped to the minute, e.g. "16:12:37" (12 minutes and 37 seconds past 4pm) will be shown as "16:12:00"; this is to simplify the output, in recognition of the fact that 'to the second' timings are usually unnecessarily precise. But, if OutputSeconds is set to "true", then the precise time "16:12:37" will be shown instead.
OutputTotalTimeMinute Default: false. By default, an output plan will show "TotalTimeSecond", which is the total amount of time in seconds. This is the default for historical reasons. For convenience, by setting OutputTotalTimeMinute to true at top level, "TotalTimeMinute" will additionally be shown.
ReformatBrokenTravel Default: false. When this is false, any Travel time that inccludes breaks within the travel will be output as follows: the TRAVEL action will be output first, and then the BREAK action(s) will be listed immediately afterwards -- see About Break Actions .... If set to true, then the interleaved partial travel actions and breaks will be output strictly chronologically, however the TRAVEL actions will in this case be TRAVEL_PART actions - see Action Key/Value pairs.
StatisticsInterval Default:"5s". The time interval between interim outputs showing summary statistics of the run so far.

Some additional Key/Value pairs are noted below, which we generally recommend should not be used (i.e. the default values should normally be left in place). Using non-default values for these Key/Value pairs could have a range of unexpected side-effects, so it is best to discuss their use first with the vortex team.

Key Description
ActivateBalanceObjective Default:false. If this is set to true, then vortex will try to balance the work across the available drivers; essentially this means it will try to equalise driver's shift times, to avoid, for example, cases where one driver has a 2 hour shift and another has a 12 hour shift. Vortex will, as usual, simultaneously work to optimise cost, mileage, etc ..., but it should be noted that behaviour may be quite different when Balance is active. In particular, vortex will assume that your intention is to use all available drivers.
AutoRestartEvaluations Default:true. This is an algorithm feature that usually leads to better performance, hence is set by default.
BalanceGrainTime Default:"15m". This is only relevant when ActivateBalanceObjective is true, and should be a valid time string (see Time Strings Format). It defines the granularity of shift lengths relevant for reasoning about different solutions in terms of Balance. E.g. if one solution has BalanceMinutes 7 and another has BalanceMinutes 26, then, if BalanceGrainTime is "30m" they will be considered equally good in terms of Balance, but if BalanceGrainTime is "15m" then the smaller one will be considered better.
BoostCloseJobs Default:false. Setting this to true directs the vortex algorithm to include a strategy that focusses more than usual on consdiering the relative placement of jobs that are "close" to each other. Whether or not this is effective depends on many other factors.
BreaksMode Default:"Default". This specifies the system in place for implementation of Drivers' breaks. The Default system in place works according to EU/GB rules, deals with breaks required according to driving time, and other breaks required according to working time; this default system can be freely and significantly modified by varying several associated parameters (see Driver Key/Value pairs to match your requirements for breaks. However an alternative available is "Segmented"; in this mode, a driver's shift is broken into two segments, each of which has a maximum working time, and an earliest and latest time in which a break (of a given length) should be taken.
CloseJobsSecondsThreshold Default:300. This is a parameter that affects vortex performance when BoostCloseJobsis in play.
DistanceStratifiedSpeedVector Default:"null". This is meant to be used in situations where it has been necessary to calibrate between one of the "RoutingService" options offered by our API, and a different (perhaps internal) approach that you have normally used in the past. In such circumstances, experience has proven that the appropriate way to calibrate is to adjust speeds differently according to journey distance. The details are worked out offline by you, us, or both, and the outcomes are conveyed to EOS/vortex via a 56-element vector. The n*th element in the vector represents the speed adjustment required for distances between *n-1 and n km, until we get to the 56th element, which covers speed adjustment for all journeys longer than 55km. Example: "DistanceStratifiedSpeedVector": [0.891779, 0.910081, 0.924933,0.920734, 0.910217, 0.888631, 0.883943, 0.879616, 0.868773, 0.860822, 0.861527, 0.865147, 0.859153, 0.862944, 0.865738, 0.861909, 0.861817, 0.875176, 0.885938, 0.882264, 0.883927, 0.885873, 0.886773, 0.885924, 0.903905, 0.909144, 0.902468, 0.889361, 0.882545, 0.889222, 0.909144, 0.902468, 0.889361, 0.882545, 0.889222, 0.899207, 0.908964, 0.912408, 0.920449, 0.921763, 0.924532, 0.927267, 0.930185, 0.928964, 0.927594, 0.921386, 0.929614, 0.929797, 0.925988, 0.915805, 0.906487, 0.906526, 0.905976, 0.90251, 0.902821, 0.959647].
EnableExcessQ1 Default:false. Normally, a vehicle cannot carry a load beyond its stated capacities. But if EnableExcessQ1 is true, then a level of excess in the first quantity that is present in the seed solution (see Seeding the input will be allowed, at least temporarily, as long as it does not exceed MaximumExcessPercentQ1, which in turn is set in the vehicle and/or depot objects. If EnableExcessQ1 is left as false, then MaximumExcessPercentQ1 is considered to be zero for every vehicle.
FinalOutputWeights Default:"null". The final output is, by default, the complete set of solutions, which will vary in terms of the tradeoffs between cost, mileage, overtime, and so on. With this key, however, you can specify a simple formula to identify which of the solutions is most desirable. See Selecting a Single Output for examples and further explanation.
InterimOutputWeights Default:"null". This is the equivalent of FinalOutputChoice, but applies to the interim outputs while vortex is running.
LateJobsFactor Default:100. Only in FastMode, this affects the way that vortex trades off the number of late jobs against other potential issues. Its behaviour depends on the relative settings of LateMinutesFactor, OvertimeMinutesFactor, and OverweightFactor.
LateMinutesFactor Default:10. Only in FastMode, this affects the way that vortex trades off the total late minutes oflate jobs against other potential issues. Its behaviour depends on the relative settings of LateJobsFactor, OvertimeMinutesFactor, and OverweightFactor.
MaximumPlannableDistance Default: 1500000. This is the maximum single-journey distance, in metres, that vortex will plan, in the following sense: a job is regarded as plannable if the travel time from the start location of at least one vehicle to that job is less than this distance. If not, then this job will always be left unplanned, even if "PlanAllJobs" is set to true. The default value represents a rounded version of the distance from Land's End to John 'o Groats.
OvertimeMinutesFactor Default:10. Only in FastMode, this affects the way that vortex trades off the total overtime minutes of drivers with overtime against other potential issues. Its behaviour depends on the relative settings of LateJobsFactor, LateMinutesFactor, and OverweightFactor.
OverweightFactor Default:1. Only in FastMode, this affects the way that vortex trades off the excess weight in a vehicle against other potential issues. Its behaviour depends on the relative settings of LateJobsFactor, LateMinutesFactor, and OvertimeMinutesFactor.
PlanAllJobs Default: false. If set to true, then every job that is less than MaximumPlannableDistance from at least one vehicle's start location will be planned, irrespective of problems such as overtime, overloading, late deliveries, etc. This is only to be used if a plan with such violations can be usefully interpreted. This really should not be used in circumstances where you would normally not expect to be able to do all of the available jobs. For example, if you typically have 200 jobs available, and your vehicle resources can normally manage 50, then leave well alone. If you set "PlanAllJobs" to true in such circumstances, then all will be planned and the overtime, lateness, etc... will be minimised, but it will still be too high, and the solution provided will not be a useful guide towards an appropriate feasible solution. If in doubt, discuss with us!
PriorityMode Default:"Strict". An additional value is available, denoted "Staged". If PriorityMode is set to "Staged", vortex will start by operating in "Strict" mode, and after some time will change to (and stay in) "Permissive" mode. The change in mode comes either when a target percentage of the priority jobs have been planned, or when a certain time has been reached, whichever is earliest. This is a developing feature, and at the moment the default parameters may be unsuitable for many customers, so we don't recommend using this without discussing it with us first.
RangeExcessPenaltyPerKm Default:50. Only in FastMode, this affects the way that vortex penalises shifts where the vehicle's distance travelled exceeds its range.
ShowBalance Default:false. If this is set to true, then the Objectives object will include BalanceMinutes and BalanceSecond, even if the Balance objective is not activated (see Objectives). This is automatically set to true if ActiviateBalanceObjective is true.
UseSuitability Default:false. Setting this to true directs the vortex algorithm to include a specific strategy in choosing which vehicles should be preferred for which jobs.
WaitsCountAsBreaks Default:"true". In a standard use case for vortex, if a plan includes, for example, a 25-minute wait at a site before a customer's delivery window opens, then (since the plan is known in advance), we consider that this wait can be counted as a break, provided it meets the WorkQualifyingBreakThreshold. This means that the wait can be counted for the purposes of, for example, accounting for appropriate amounts of driving and/or working breaks, and therefore improve overall efficiency. However, users may sometimes wish to disallow this, in which case WaitsCountAsBreaks can be set to false.
WorkingTimeIncludesBreaks Default: false Normally, working time refers to time driving, waiting, collecting and delivering. So, for example, if a driver has a maximum working time of 8 hours, and has spent 6 hours driving and 2 hours delivering, with 60 minutes of breaks, then this was a valid shift with 8 hours' work. However if WorkingTimeIncludesBreaks is set to true, this would be an invalid shift, with 9 hours of work. In rare cases, a user may wish to set this to true; this happens when shifts are generally considered in terms of wall-clock time, e.g. from 09:00 to 17:00 -- in such a case a start time may be set at 09:00, and then a maximum working time of 8 hours, where that time working time is implicitly covering any and all activity after 09:00. Note: if BreaksMode is segmented (which is the case in FastMode), then WorkingTimeIncludesBreaks is automatically set to true.

FastMode

In retail home-delivery use-cases, vortex is sometimes used in combination with EOS, our Elastic Optimization Service, which incorporates slot-booking functionality. Vortex needs to be particularly fast and responsive in this context, and used a mode of operation called FastMode. In 'FastMode', performance in a given time frame is boosted, but at the cost of several features -- ones that would rarely or never be needed in retail home-delivery -- being unavailable.

Fastmode is activated automatically if the input satisfies the following: - BreaksMode is set to "Segmented"; - WorkingTimeIncludesBreaks is set to true; - every job is a DELIVERY (there are no collections or VISITS); - there are at most two quantities.

As indicated, however, in fastmode number of features become unavailable, and there are also changes to some defaults. These restrictions are associated with the following assumptions:

In the remainder of this documentation, the phrase Unavailable In FastMode to indicate the relevant features; these will often be assocaited with the above (e.g. since there is only one stocktype, then StockMixingRules are not relevant), but sometimes not. Meanwhile there are some features that are Only available in FastMode, which will uselly be temporary, waiting for the full implementation to be released. Currently Locking is in this category.

Selecting a Single Output

The quality of a solution is measured by several different objectives. When all are 'active', these are: Cost, DistanceKm, OvertimeSecond, CarbonKg (relevant when vehicles differ in terms of CarbonPerKmKg), ShiftsNumber, TotalTimeSecond, and BalanceMinutes. By default, Vortex optimises these different objectives at the same time, and at any point (including the end of the run) there will typically a collection of different solutions. These different solutions are each 'as good as each other', and represent different tradeoffs between the objectives.

For example, imagine that there were only two objectives: Cost and Distance. At the end of the run, the result might be a set of five solutions, whose cost and distance values are summarised below:

  1. Cost £1,000, Distance 10,000 km
  2. Cost £1,010, Distance 9,000 km
  3. Cost £1,100, Distance 8,800 km
  4. Cost £1,400, Distance 8,700 km
  5. Cost £1,500, Distance 8,500 km

Without further information about your business preferences, there is no clear way to choose between these solutions; each is as good as the other. Take any two, and one will be better on cost, while the other will be better on mileage. Note, especially, that a simple preference for 'Best Cost' solution may not be appropriate. You can use "FinalOutputChoice": "BestCost" to specify that, at the end of the run, only a single solution -- the best cost one -- will be output. However in this case that will mean you never see solution 2, which is only trivially more expensive than solution 1, but saves 1,000 km in mileage, and which may indeed be preferable.

So, if you want to confine the output to a single solution which is the best on a single objective, then FinalOutputChoice and InterimOutputChoice are available to use for this purpose. E.g. "FinalOutputChoice": "BestMileage" would deliver solution number 5 above. However, to make more subtle distinctions, and avoid missing solutions potentially more suitable (which may include any of solutions 2, 3, or 4, depending on the circumstances), you can use FinalOutputWeights and/or InterimOutputWeights. These work as follows.

Suppose you decide that Cost is important, but Mileage is also important, and you wouldn't mind an extra cost of £100 if it saved 1,000 km -- in other words, you would trade off £1 against 10 kilometres. With FinalOutputWeights you can indicate that by setting weights for Cost and Mileage of 10 and 1 respectively. (Note that the numbers are switched -- because we are saying that 10 times 1 pounds roughly equates to 1 x 10 km). We can express that like this:

          "FinalOutputWeights":   [{
                              "Objective":    "Cost",
                              "Weight":       10
                             }, {
                              "Objective":    "DistanceKm",
                              "Weight":       1
                            }]

This means that the final output will show a single solution, which will be the one with best (lowest) value for: 10×Cost + 1×DistanceKm. In the above example, this would lead to solution 2 being output, since it scores the lowest (19,100) using this formula.

In the array provided as the value to "FinalOutputWeights" or "InterimOutputWeights", you can use any and all of seven objectives, and use any numbers as weights. Any objective not included would have a default weight of 0. In this example you can see each of the seven objectives used:


        "InterimOutputWeights": [{
                            "Objective":    "BalanceMinutes",
                            "Weight":       1
                    }, {
                            "Objective":    "CarbonKg",
                            "Weight":       1
                    }, {
                            "Objective":    "Cost",
                            "Weight":       10
                    }, {
                            "Objective":    "DistanceKm",
                            "Weight":       2.5
                    }, {
                            "Objective":    "OvertimeSecond",
                            "Weight":       0.1
                    }, {
                            "Objective":    "ShiftsNumber",
                            "Weight":       5.0
                    }, {
                            "Objective":    "TotalTimeSecond",
                            "Weight":       1
                    }],

This would yield a single output solution, the one that minimized the formula:

BalanceMinutes + 10×Cost + 2.5×DistanceKm + 1×CarbonKg + 0.1×OvertimeSecond + 5×ShiftsNumber + 1×TotalTimeSecond

If there is one key objective of interest, but some concern to tradeoff appropriately against one or two others, then using FinalOutputWeights is recommended.

However, if you need to carefully consider the many tradeoffs between several different objectives, you may want to do modelling with spreadsheets under various scenarios, do various experiments with vortex, etc, before deciding on a set of weights. In such circumstances it may be wiser simply not to use FinalOutputWeights at all, and simply obtain the full set of solutions each time and inspect them before making a choice.

Time Strings Format

Many of the values in the json input objects are time values, Often these are specific times (e.g. when a driver can start in the morning), and often these are durations of time (e.g. the maximum working time allowed in a shift). Sometimes time values are standalone, and sometimes they are elements of arrays. In all cases the format rules are the same, and is as follows:

For example, to express a default MaximumWorkingTime for a driver of 11 hours, you can say:

...
"MaximumWorkingTime": "11h",
...

However if readability is less important to you than, for example, consistency, then any of the following are fine instead:

...
"MaximumWorkingTime": "660m"
"MaximumWorkingTime": "39600s"
"MaximumWorkingTime": "11:00:00"
"MaximumWorkingTime": "00 11:00:00"
...

A time string must be in one of the following formats:

  • "SS"; e.g. "30" (meaning 30 seconds, or 30 seconds past midnight), "00" (meaning zero duration), "01" (meaning 1 second duration, or 1 second past midnight).
  • "MM:SS"; e.g. "00:30" (meaning 30 seconds, or 30 seconds past midnight), "01:50" (meaning 110 seconds), "59:00" (meaning 59 minutes, or 1 minute before 01:00 AM).
  • "HH:MM:SS"; e.g. "00:30:00" (30 minutes, or half past midnight), "01:50:00" (110 minutes, or ten to two in the morning), "23:10:00" (23 hours and 10 minutes, or 10 past 11 pm) , or "00:23:10" (23 minutes and 10 seconds, or 23 minutes and 10 seconds past midnight).
  • "DD MM:HH:SS"; e,g "00 00:00:30" (30 seconds), "00 00:30:00" (30 minutes), "01 01:00:21" (25 hours and 21 seconds).
  • "NNN...s"; e.g. "1s" means 1 second, "2001s" means 2001 seconds;
  • "NNN...m"; e.g. "1m" means 1 minute, "100m" means 100 minutes;
  • "NNN...h"; e.g. "1h" means 1 hour, "60h" means 60 hours;
  • "NNN...d"; e.g. "1d" means 1 day, "365d" means 365 days.

Sometimes there is a need to express times that start the day before. For example, drivers may have shifts that begin just before midnight, but where most of the work is concentrated in the early hours of the next day. In such a case, you would naturally set the "PlanDate"to be the date where most of the work happens, but the start times for the drivers and vehicles, and the opening times of the depots, would actually be the previous day. To cover scenarios like this, you can use "-" in front of a time, as follows.

If "<time>" is a valid time string in any of the above formats, then "-<time>" is also a valid time string. The minus sign indicates counting back from midnight at the very start of the plan date. For example: json "ShiftEarlyStartTime":"-01:00:00" "ShiftEarlyStartTime":"-60m" "ShiftEarlyStartTime":"-1h" each of the above indicates that this driver's shift can start one hour before midnight, i.e. at 11pm, on the previous day.

Finally, the following are also examples of allowed formats:

  • "+\<time>\>", where "<time>\" is any valid time that does not already start with a "+" or "-" ; e.g. "+1h" means 1 hour, "+12:05:00" means "12:05:00", etc.

  • `more than 24 hours; e.g. "26:05:00" means 5 past 2am tomorrow; "60:00:00" means noon the day after tomorrow.

You can freely use any mixture of these formats, choosing whichever is most readable and convenient in context.

Note: Multiple Stocktypes is Unavailable In FastMode

Capacities, Quantities, and Stock

Objects available

Most of the time, the algorithm will be used to schedule the movements of vehicles that do the following:

  • have goods loaded onto them at a depot, and then transport those goods to deliver them to customers;
  • or, travel to various customer sites to collect goods, and then unload those goods at a depot.

So, the vehicles are generally carrying goods around, and we might need to know information about the sizes of those items, to make sure the vehicle has enough capacity to carry them. To provide this information, Job, Vehicle and Depot objects have associated attributes as follows:

  • In a Job object, there is a Quantities array, which can be populated with information about the size of that job. For example, "Quantities": [22,3] might indicate that this job comprises 3 packages, weighing in total 22Kg.

  • In a Vehicle object, a Capacities array is available, to indicate its maximum capacity for jobs. So, for example, "Capacities": [500,50] might indicate that the vehicle can carry at most 500 kg, and a maximum of 50 crates.

  • In more complex cases, the Capacities object in a Vehicle may instead be an array of objects, indicating capacities for different types of stock.

  • In even more complex cases, the vehicle will not have a Capacities object; instead it will have a Compartments array, providing information about the capacities of different compartments on the vehicle.

  • meanwhile, Depot objects have an Attributes list available, which can contain "GoodsSupply", meaning it has enough stock to supply all the jobs, and/or "GoodsReceipt", indicating that it has enough capacity to receive all the collections.

  • finally, for more complex situations, a Depot object may, instead of an Attributes list, have a Stock object, which provides information about the depot's levels of, and storage capacities for, one or more different types of stock.

In the rest of this section, we explain when the above objects are needed, and how they are used.

Goods Scenarios

Sometimes, the algorithm doesn't need to know anything else about the goods, apart from where they are, and where they have to be. For example, a vehicle may have more than enough capacity to carry all of the jobs that it could possibly Collect and/or Deliver in a typical shift.

Often, however, the weight or volume (or other quantities) of the goods are an important aspect for the algorithm to consider. For example, we may be delivering around 50 washing machines per day, but each of our vehicles can only carry a maximum of 5 washing machines at a time.

Moreover, there may be different types of goods, and the algorithm may need to have information about the vehicle's capacity for each type individually. E.g. a vehicle may be able to carry either 5 washing machines, or 100 microwaves. Sometimes we could handle this by considering the weights and/or volumes of the items. But sometimes there are other factors that come into play; for example, if we only consider volume, we might calculate that we could fit 10 washing machines on the vehicle - however the limitation of 5 comes from the requirements for limitations on stacking and securing them in transit.

So, when it comes to capacities and quantities, there are three broad kinds of cases. In the following we consider each of these in turn, and explain how they can be dealt with in the Job, Vehicle and Depot objects.

When Quantities Don't Matter

Below indicates an example where there are three depots; the first can handle both Collection and Delivery jobs, the second can only handle Delivery jobs, and third can only handle Collection jobs.

"Depots": [
    { "IdString": "MainDepot",
       ...
      "Attributes": ["GoodsSupply","GoodsReceipt"],
       ...
    },
    { "IdString": "SupplyCentre1",
       ...
      "Attributes": ["GoodsSupply"],
       ...
    }, 
    { "IdString": "Warehouse22",
       ...
      "Attributes": ["GoodsReceipt"],
       ...
    }, 
    ...
   ],
   ...

This is the case when a vehicle may have more than enough capacity to carry all of the jobs that it could possibly Collect and/or Deliver in a typical shift. We don't need to know any details about the size of the goods being carried, because we know that the vehicle will always have enough spare capacity to carry it. This is also the case when all of the jobs are Visits (e.g. for maintenance tasks), which don't involve carrying goods at all.

In this scenario, no information needs to be provided; the Quantities array can be omitted from the Job objects, and the Capacities array can be omitted from the Vehicle objects. The Depot objects do need to have the Attributes list appropriately populated, but no other information is required. Behind the scenes, the algorithm will assume that there is a single quantity value which is zero in all cases.

So, to sum up, in this scenario there is only one place in the input where anything concerning goods needs to be mentioned: if any of the jobs are Deliveries, then at least one depot needs to have the "GoodsSupply" attribute set, indicating that jobs can be loaded onto vehicles at that depot. If there are any Collection jobs, then at least one depot needs to have the "GoodsReceipt" attribute set.

A single type of stock

The quantities of jobs and the capacities of vehicles could then be expressed like this:

"Jobs": [
    { "IdString": "Smith's Nail Shop",
       ...
      "Quantities": [2],
       ...
    },
    { "IdString": "Jones' Cement Emporium",
       ...
      "Quantities": [8],
       ...
    }, ...
    ...
   ],
"Vehicles": [
   { "IdString": "Truck ABC123",
       ...
      "Capacities": [150],
       ...
   },
   { "IdString": "SmallVan22",
       ...
      "Capacities": [40],
       ...
   },

When quantities matter -- usually this is because the vehicle capacities limit the jobs that can be carried at one time -- it is very often the case that all of the goods can be considered to be the same type. For example, we may be delivering building materials. Even though there might be many specific types of items, we can safely characterize each job in terms of one or more quantities (e.g. weight and volume, perhaps roughly estimated), and these quantities can be set against the capacities of the vehicles, which would of course be expressed in the same units. For example, in our building materials example, we might roughly characterize each job as a number of boxes of a fixed size, while estimating that our main vehicles can carry 150 such boxes, and the smaller ones can carry 40.

Alternatively, we might need to use two or more quantities, such as weight, volume, and number of units, and some of these may only be relevant to some jobs. For example, a van might have a weight capacity of 500 kg, a volume capacity of 10 cubic metres, and, specifically for cross-trainers, a units capacity of 3. In terms of weight or volume, it would in theory be possible to carry more than 3 cross-trainers, however for other reasons only 3 can be carried at once. To handle this (or any) case, the first step is to decide on the structure of our quantities/capacities arrays; this must then be consistent across all objects where quantities or capacities appear. In this case, we would decide that there are three quantities: weight, volume, and cross-trainer-units, and (arbitrarily but consistently), we will put them in that order in the capacities and quantities arrays.





The following example, where only the third job involves cross-trainers:

"Jobs": [
    { "IdString": "Smith's White Goods",
       ...
      "Quantities": [100, 6, 0],
       ...
    },
    { "IdString": "Jones' Fitness equipment",
       ...
      "Quantities": [40, 2, 0],
       ...
    }, 
    { "IdString": "Brown's Exercise Kits",
       ...
      "Quantities": [120, 6, 2],
       ...
    }, ...
    ...
   ],
"Vehicles": [
   { "IdString": "Truck01",
       ...
      "Capacities": [500, 10, 3],
       ...
   },
   { "IdString": "SmallVan22",
       ...
      "Capacities": [200, 4, 2],
       ...
   },

Notice that the "Brown's Exercise Kits" job, with quantities [120,6,2], is presumably to Deliver 2 cross-trainers. Importantly, a weight and volume are also supplied for this job. Otherwise, when a vehicle is carrying this job, the algorithm would overestimate the remaining capacity for other types of goods. When expressing the quantities of jobs, the golden rule is to consider how this affects the estimation of the remaining capacity in a vehicle. This means that there needs to be at least one element of the quantity/capacity arrays that is used by all types of job. For example, the "Smith's White Goods" job, with "Quantities": [100, 6, 0], involves zero cross-trainers, but this doesn't mean, if this job is loaded onto "Truck01", that there will still be capacity for 3 cross-trainers on the Truck. The algorithm will consider the shared quantities, and note that in this case, based on volume, the "Smith's" job takes 60% of the van's capacity, and on that basis only 1 cross-trainer could now fit.

When it comes to Depots, if we can safely assume that the depots have enough stock to supply all the jobs (and/or enough storage space to receive all the collections), then the requirements are exactly the same as in the 'When Quantities don't Matter' section above. That is, if we indicate that a Depot has the "GoodsSupply" attribute, then by default it has enough goods to supply all of the jobs. However, if the levels or capacities of depots are something we need to deal with, and/or if we need to consider multiple types of stock independently, then we will probably need to introduce and populate a Stock array in each Depot object. This is discussed below, and also in the section devoted to the Stock array.



Multiple types of stock

In the above scenario, we had different types of goods being moved around, but we could handle this with a single quantities/capacities array. Some types of goods -- cross-trainers in our example -- needed their own special place in those arrays, but nevertheless we could deal with all goods as if they were of a single type, with every payload able to be characterised by our single quantities array, and where all goods could be mixed together at will in our vehicles, and freely supplied or received at depots.

But, sometimes we will need to deal with more complex situations, which require us to keep track of different types of goods. Ww will refer to these as different types of 'stock', and the algorithm will deal with multiple 'stocktypes'. Happily, in all cases, we will always use a single, consistent Quantities/Capacities array (in terms of the number of entries, their ordering, and their meaning). However, as we will see, each Vehicle might need a specific Capacities array for each stocktype, while our Depots will need a Stock array, with an entry for each stocktype, indicating the levels, capacities and other information about that particular stocktype. Finally, each individual Job will now need a Type attribute, naming the stocktype of that Job.

There are three independent reasons that would lead to us needing to add these things:

  • levels and capacities at Depots may be limited in relation to the amount of goods involved in the plan. E.g. the plan may involve the delivery of 100 washing machines and 100 microwaves. However, each Depot may only hold 20 of each, so we need to be careful that, over the course of the plan, we don't load more from any individual depot than it can supply.
  • There may be restrictions on carrying different types of stock in the same Vehicle at the same time. For example, raw-meat and cooked-meat can't be carried together. In some cases no combinations are allowed at all (e.g. a tanker carrying fuel cannot carry a mixture of diesel and petrol, unless it has multiple compartments, which we will look at later). In this sort of case we additionally need to consider StockMixingRules.
  • Given the needs of the types of goods involved in the problem, and the different ways that they need to be packed in transit, it may simply not be possible (or too difficult) to characterise the capacity of a Vehicle with a single Capacities array; basically, there needs to be a different Capacity array for each stocktype (although they will retain the same number of elements).

So, in all such cases we need to have the stocktype identified in the Job object, and we need to know each individual Vehicle's capacity for each different stocktype. In the example listed, we have three stocktypes, treadmills, dumbbells, and cross-trainers, and we have decided that only two quantities are needed, respectively representing weight and number-of-cross-trainers.

We have three stocktypes, treadmills, dumbbells, and cross-trainers, and we have decided that only two quantities are needed, respectively representing weight and number-of-cross-trainers:

"Jobs": [
    { "IdString": "Smith's Lifestyle Goods",
       ...
      "Quantities": [100,0],
      "Type": "treadmills",
       ...
    },
    { "IdString": "Jones' Fitness equipment",
       ...
      "Quantities": [40, 0],
      "Type": "dumbbells",
       ...
    }, 
    { "IdString": "Brown's Exercise Kits",
       ...
      "Quantities": [60, 2],
      "Type": "cross-trainers",
       ...
    }, ...
    ...
   ],
"Vehicles": [
   { "IdString": "Truck ABC123",
       ...
      "Capacities": [{"Type":"treadmills", "Capacity": [400, 0]},
                     {"Type":"dumbbells", "Capacity":  [500, 0]} ],
       ...
   },
   { "IdString": "SmallVan22",
       ...
      "Capacities": [{"Type":"treadmills", "Capacity": [400, 0]},
                     {"Type":"cross-trainers", "Capacity":  [240, 8]} ],
       ...
   },

In this case, the Truck is not allowed to carry cross-trainers at all, and the small van is not allowed to carry dumbbells. The truck, if only carrying treadmills, could carry 400kgs' worth; if only carrying dumbbells, it could carry 500 kgs' worth. If carrying a mixture of these two types, its load and spare capacity will be calculated proportionately. E.g. if it is already carrying 400 kg of dumbbells, then it is considered 80% full, and hence can only handle a 20% load of other items, i.e. a further 80Kg of treadmills, or a further 100kg of dumbbells.

In this scenario, it may still be true that we can consider Depots as having enough supply or capacity for the tasks involved, in which case we just need the Depot's Attributes appropriately set. However, if we need to consider the levels of stock at a Depot, we will use the Stock object. See The Stock Array for details and examples.

Another thing we might need in the multiple types of stock scenario is the StockMixingRules object. This is a top-level object, which indicates what pairs of stocktypes can or cannot be carried together in the same vehicle. See Stock Mixing Rules for details and examples.

Finally, in some scenarios, especially where the vehicles are fuel tankers, the vehicles have compartments. Each job will be loaded into a specific compartment, which will have its own capacity, potentially different from that of other compartments on the same vehicle. If vehicles have compartments, then the Vehicle object should not have a Capacities array at all; instead it will have a Compartments array, and the capacities of the individual compartments will be expressed within that. See Vehicle Compartments for details and examples.







Note: Unavailable In FastMode

The Stock Array

If a Depot object has the "GoodsSupply" attribute set, then this means that Delivery jobs can be loaded at that depot. By default, the depot is considered to have enough stock to supply the needs of all of the Delivery jobs. Similarly, if a Depot has the "GoodsReceipt" attribute, then customer Collection jobs can be unloaded there, and by default it is deemed to have enough capacity to store all of the Collection jobs.

However, in some cases, the levels of stock (and/or the holding capacity for stock) at a depot might be limited, and the algorithm will be required to keep track. Further, there may be more than one type of stock (e.g. "Bricks", "Cement", "Gravel", ...) and the algorithm may need to keep track of the depot levels of each type.

Here is an example, which illustrates all of the main features:


"Stock": [{
            "Type": "Bricks",
            "Cost": {"PerQuantityUnit":[0, 1, 0], "SupplyCost":2.5, "StorageCost":1.1},
            "Level":        [{"Day":  1, "Quantity": [10000, 100, 0],"Type": "Absolute"},
                              {"Day":  2, "Quantity": [2000, 20, 0],"Type": "Relative"},
                              {"Day":  3, "Quantity": [0, 0, 0],"Type": "Relative"}
                            ],
            "Capacity":     [{"Day":  1, "Quantity": [20000, 200, 0],"Type": "Absolute"},
                              {"Day":  2, "Quantity": [20000, 200, 0],"Type": "Absolute"},
                              {"Day":  3, "Quantity": [20000, 200, 0],"Type": "Absolute"}]
            },
            {
            "Type": "Cement",
            "Cost": {"PerQuantityUnit":[1, 0, 0], "StorageCost":3.4},
             "Capacity":     [{"Day":  1, "Quantity": ["inf","inf","inf"],"Type": "Absolute"},
                              {"Day":  2, "Quantity": ["inf","inf","inf"],"Type": "Absolute"},
                              {"Day":  3, "Quantity": ["inf","inf","inf"],"Type": "Absolute"}
                              ]
            },
          ],
            ...

A Depot's Stock array contains an entry for each stocktype that is available at, or can be stored at that depot. Each entry identifies the stocktype, and contains a "Level" and/or a "Capacity" array for that stocktype. These latter arrays contain an entry for each day of the plan. The level of that stocktype available on a given day is expressed, in the usual way as an array of quantities, in this case called "Quantity". On day 1 this will always indicate the absolute quantity of that stocktype available at the depot. On other days, however, it could either again be interpreted as absolute values, or it could be interpreted relative to the previous day.

For each stocktype, there is also a Cost object, which indicates (if relevant) the cost charged by the depot for supplying that stocktype, and/or (if relevant), the cost charged by the depot for storing that stocktype. The Cost object has a PerQuantityUnit array, which indicates which quantity serves as the unit basis for calculating costs.

For example, in the above case, the depot starts on day 1 with [10000, 100, 0] bricks available to supply Delivery jobs. On day 2, a further [2000, 20, 0] will be available (so, if our plan delivers [3000, 30, 0] bricks from that depot on day 1, we know that there will be [9000, 90, 0] available at the start of day 2). The depot is not replenished at all on day 3, so if we again deliver [3000, 30, 0] on day 2, there will be [6000, 60, 0] available at the start of day 3. Meanwhile, for each unit of the second element of the quantity vector, the cost of Bricks supplied by this depot (for a Delivery job) will be £2.50. E.g. for an amount [100,10,0] of Bricks, the charge will be £25.00; the cost to store the same number of Bricks at this depot (i.e. the cost to unload bricks obtained from a Collection job) would be £11.00. Any cost not provided is assumed to be zero.

Note that, if this is a multiday problem, the costs provided as "SupplyCost" and "StorageCost" will be the default for every day of the plan. However if you want to supply different costs on different days, then, for an N-day problem, you can instead provide "SupplyCostArray" and/or "StorageCostArray", as follows:


            "Cost": {"PerQuantityUnit":[0, 1, 0], "SupplyCostArray":[2.5, 2.6, 2.6, 2.1, 2.2], "StorageCost":1.1},

In the above, the problem must be a 5-day one, and different supply costs have been supplied for days 1--5 respectively; since no array is given for storage cost, the value for each day will be 1.1.

This depot also has a Capacity array for bricks. So, if there are any brick Collections from customers, we know that they could be unloaded at this depot. Capacity will normally always be interpreted as an absolute. Finally, this depot has capacity for cement, so collections of cement from customer sites can be unloaded there; however it has no level for cement, indicating that cement deliveries must be loaded at another depot. Since the quantity entries are all "inf", it's capacity for cement can, be treated as unlimited.

Finally, the defaults for the Cost object (i.e. the assumptions that will be made if this object or parts of it are missing) are as follows. First, if SupplyCost is absent for a given stocktype, we assume that the cost of supply for that stocktype from this depot is zero. Similarly, a missing StorageCost means that the cost of storage is zero. If one of these is supplied, but the PerQuantityUnit array is missing, we assume that the quantity to use for calculating costs is the first one. For example, if the quantities and capacities in this input are four-element arrays, then the default PerQuantityUnit is [1, 0, 0, 0].

Vehicle Compartments

Note: Multiple vehicle compartments is Unavailable In FastMode The Compartments array contains an entry for each compartment; each entry contains an AllowedLoads array, which indicates the capacity for a specific stocktype, and a PriorJobType. The latter can simply be omitted, but it is useful in situations where only one stocktype can be present in a compartment (mixing any two stocktypes within compartments is disallowed), and there are rules in place that place restrictions on the sequencing of job types.


"Compartments": [{
                  "AllowedLoads": [{"Capacity":     [300, 20, 5], "Type": "cooked_meat"},
                                    {"Capacity":     [400, 10, 10], "Type": "raw_meat"}
                                  ],
                    "PriorJobType": null
                }, 
                {
                  "AllowedLoads": [{"Capacity":     [300, 20, 5], "Type": "cooked_meat"},
                                    {"Capacity":     [400, 10, 10], "Type": "raw_meat"}
                                  ],
                    "PriorJobType": "raw_meat"
                }
              ]

In some scenarios, especially where the vehicles are fuel tankers, each vehicle has a number of isolated Compartments. Jobs will be loaded into a specific compartment, which will have its own capacity, potentially different from that of other compartments on the same vehicle. This type of vehicle is common in the fuel distribution sector. Compartments make it possible to carry two jobs together on the same vehicle, which would otherwise not be possible with a single compartment; e.g. diesel and petrol cannot be carried together in a single-compartment tanker, but they can in a multi-compartment tanker.

If vehicles have compartments, then the Vehicle object should not have a Capacities array at all; instead it will have a Compartments array, and the capacities of the individual compartments will be expressed within that. The listed example shows how it's done.

If any stocktype is omitted from the AllowedLoads array for an individual compartment, this is taken to mean that this compartment is prohibited from carrying that stocktype.



Stock Mixing Rules

Note: Unavailable In FastMode

Suppose the situation is that we carry four types of food around on single-compartment vehicles: "raw_meat", "cooked_meat", "biscuits", and "bread". Mixing between any stocktypes is allowed, with the exception that "raw_meat" must not be mixed with anything else. In this case the most natural way to express the rules is to start with "AllMixingAllowed", and then indicate three specific exceptions, as follows:

  "StockMixingRules": ["AllMixingAllowed",
                      {"BannedMixGroup":["VehicleLevel","raw_meat","cooked_meat"]},
                      {"BannedMixGroup":["VehicleLevel","raw_meat","biscuits"]},
                      {"BannedMixGroup":["VehicleLevel","raw_meat","bread"]}
                     ]

Note that we cannot put all four in the same "BannedMixGroup", because this would disallow, for example, the mixing of bread and biscuits.

An alternative, equally valid way to express this scenario is to first ban all mixing, and then express the exceptions. In this case it turns out to be more parsimonious, e.g.:

  "StockMixingRules": ["NoMixingAllowed",
                      {"AllowedMixGroup":["VehicleLevel","cooked_meat","biscuits","bread"]}
                     ]

If one or more combinations of items cannot be carried in the same vehicle, the details can be expressed in the StockMixingRules array. The default position is that any pairing of stocktypes (i.e. any pair of jobs) can be carried together on the same vehicle. If the vehicle has compartments, then the default position extends to the compartments -- i.e. any stocktypes can be mixed together within any compartment of any vehicle. If the default is true, then there is no need to supply a StockMixingRules object.

However, if the situation is different, it can be described by populating the StockMixingRules array. The array is a list of statements and/or mixing constraints. For example:

  • "NoMixingAllowed" : if this is the first item in the array, it indicates that, by default, no mixing is allowed between any pairs of stocktypes on the vehicle. The remainder of the StockMixingRules array may then contain some exceptions to this rule.

  • "AllMixingAllowed" : if this is the first item in the array, it indicates that, by default, any pair of stocktypes can be mixed on the vehicle, and in any compartment on the vehicle. The remainder of the StockMixingRules array may then contain some exceptions to this rule.

  • {"AllowedMixGroup": ["VehicleLevel","Bread","Biscuits","Oatcakes"]} : if the first element of the StockMixingRules array was "NoMixingAllowed", then all of the remaining elements need to be objects containing an "AllowedMixGroup", indicating exceptions to that rule. In this example, the three stocktypes "Bread", "Biscuits" and "Oatcakes" can freely be mixed together on the same vehicle, however, if relevant, this doesn't mean that they can be mixed in the same compartment of that vehicle. However, {"AllowedMixGroup": ["CompartmentLevel","Bread","Biscuits", "Oatcakes"]} means that they can be freely mixed within a compartment.

  • {"BannedMixGroup": ["VehicleLevel","cooked_meat","raw_meat"]} : if the first element of the StockMixingRules array was "AllMixingAllowed", then all of the remaining elements need to be objects containing a "BannedMixGroup" object, indicating exceptions to that rule. In this example, the two stocktypes "cooked_meat" and "raw_meat" cannot be mixed together on the same vehicle. Logically this also means the ban is in place for any compartment on the vehicle. However, if it is OK to have them together on the vehicle, but not in the same compartment, then the following should be used instead: `{"BannedMixGroup": ["CompartmentLevel","cooked_meat","raw_meat"]}.

Note: even if NoMixingAllowed is set, vortex will still assume that mixing of a stocktype with itself is still allowed, at both vehicle and compartment levels. If you wish to ban mixing a stocktype with itself, you will always need an explict BannedMixGroup to express that.

Stock Sequencing Rules

Note: Unavailable In FastMode In some circumstances, it is not permitted to place, for example, stocktype A on the vehicle if stocktype B has just been unloaded. That is, B is no longer on board, however stocktype A is still temporarily prohibited. To express these types of pairwise rules, we have StockSequenceRules, which are very much like the StockMixingRules.

The example below, for instance, means exactly what you would imagine it to mean if you have read the StockMixingRules section. But it is worth emphasising the meaning of each pair of stock items in a rule: if the first of the pair was the most recent thing unloaded from the compartment/vehicle, then the second item in the pair is the one that is banned (or allowed). Also it is worth noting that we can only have pairs of items in these rules.

  "StockSequenceRules": ["AllSequenceAllowed",
                      {"BannedSequenceGroup":["VehicleLevel","raw_meat","cooked_meat"]},
                      {"BannedSequenceGroup":["VehicleLevel","raw_meat","biscuits"]},
                      {"BannedSequenceGroup":["VehicleLevel","raw_meat","bread"]}
                     ]

As before, we can make the default situation NoSequenceAllowed, in case this helps express the situation more parsimoniously.E.g.:

  "StockSequenceRules": ["NoSequenceAllowed",
                      {"AllowedSequenceGroup":["VehicleLevel","cooked_meat","biscuits"]}
                     ]

If one or more combinations of items cannot be carried in the same vehicle, the details can be expressed in the StockMixingRules array. The default position is that any pairing of stocktypes (i.e. any pair of jobs) can be carried together on the same vehicle. If the vehicle has compartments, then the default position extends to the compartments -- i.e. any stocktypes can be mixed together within any compartment of any vehicle. If the default is true, then there is no need to supply a StockMixingRules object.

However, if the situation is different, it can be described by populating the StockMixingRules array. The array is a list of statements and/or mixing constraints. For example:

  • "NoMixingAllowed" : if this is the first item in the array, it indicates that, by default, no mixing is allowed between any pairs of stocktypes on the vehicle. The remainder of the StockMixingRules array may then contain some exceptions to this rule.

  • "AllMixingAllowed" : if this is the first item in the array, it indicates that, by default, any pair of stocktypes can be mixed on the vehicle, and in any compartment on the vehicle. The remainder of the StockMixingRules array may then contain some exceptions to this rule.

  • {"AllowedMixGroup": ["VehicleLevel","Bread","Biscuits","Oatcakes"]} : if the first element of the StockMixingRules array was "NoMixingAllowed", then all of the remaining elements need to be objects containing an "AllowedMixGroup", indicating exceptions to that rule. In this example, the three stocktypes "Bread", "Biscuits" and "Oatcakes" can freely be mixed together on the same vehicle, however, if relevant, this doesn't mean that they can be mixed in the same compartment of that vehicle. However, {"AllowedMixGroup": ["CompartmentLevel","Bread","Biscuits", "Oatcakes"]} means that they can be freely mixed within a compartment.

  • {"BannedMixGroup": ["VehicleLevel","cooked_meat","raw_meat"]} : if the first element of the StockMixingRules array was "AllMixingAllowed", then all of the remaining elements need to be objects containing a "BannedMixGroup" object, indicating exceptions to that rule. In this example, the two stocktypes "cooked_meat" and "raw_meat" cannot be mixed together on the same vehicle. Logically this also means the ban is in place for any compartment on the vehicle. However, if it is OK to have them together on the vehicle, but not in the same compartment, then the following should be used instead: `{"BannedMixGroup": ["CompartmentLevel","cooked_meat","raw_meat"]}.

Depot Key/Value pairs

Key Description
IdString Mandatory. A unique ID for the depot.
TimeWindows See Default TimeWindows. An array of time-window objects, each of which provides a list of one or more time windows for each day of the schedule. See Example TimeWindows array.
Attributes Default:["GoodsSupply", "GoodsReceipt"]. Indicating whether or not this depot can be used for supplying goods for Delivery jobs, and whether it can be used for receiving goods afer a Collection job. If Attributes is empty, or missing, this depot is only used as a vehicle base.
LoadingTime Default:"00:00:00". Loading time for this depot. This time will be added to other loading times for a vehicle and a job request. This is a single fixed cost to add when the algorithm loads or unloads a vehicle (NOT per item loaded/unloaded).
PossibleDays Default:[1,2,3,..,Numdays]. Array represents a days numbers that this depot is available on. e.g. [1, 5] means that this depot is available on the first day of the schedule, and on the fifth day of the schedule.
LocationGeohash Default:null. The geographical location geohash of the depot. The depot should have a geographic location populated using either LocationGeohash or Latitude/Longitude
LocationLatLong Default:[]. The depot's latitude and longitude pairs. Please note that if LocationGeohash is already populated, the geographic location would be calculated using only the geohash value. In other words LocationGeohash has priority over LocationLatLong.
IncludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for drivers to be able to use this depot. See Characteristics, Include and Exclude examples
ExcludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a driver from using this depot. See Characteristics, Include and Exclude examples
IncludeVehicle Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for vehicles to be able to use this depot. See Characteristics, Include and Exclude examples
ExcludeVehicle Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a vehicle from using this depot. See Characteristics, Include and Exclude examples
MaximumExcessPercentQ1 Default: 0. In the fastmode/EOS context (see FastMode), a vehicle is allowed to be temporarily overloaded, and the maximum overload is controlled by this parameter. Please refer to additional Vehicle Key/Value pairs for more detail. When set in the depot object, this applies to vehicles based at that depot, but can still be overridden at the vehicle level.
Stock Default:null. This is an optional object that specifies, if needed, what types of stock can be supplied by (and/or delivered to) this depot. If the levels of stock in a depot need to be considered by the algorithm (whether for one type of stock or more), this is done by populating the Stock object. Details are given in the The Stock Array. Note that if the Stock object is missing or null, then the levels of (and capacity for) stock are guided entirely by the "Attributes" object. If "GoodsSupply" is set, this will mean that the algorithm can always assume that this depot has enough stock to supply any delivery job. If "GoodsSupply" is not set, then this depot cannot be used to supply any delivery job. Similarly, if "GoodsReceipt" is set, this will mean that the algorithm can always assume that this depot has enough stock to receive any collection job. If "GoodsReceipt" is not set, then this depot cannot be used to receive any collection job. See The Stock Array for other information about this object and its defaults.
Characteristics Default:[].

For more advanced users:

Key Description
LateMinutesFactor Default: 10. This can be set to over-ride the global value of LateMinutesFactor (see additional key value pairs at top level ... for this depot alone.
LateJobsFactor Default: 100. This can be set to over-ride the global value of LateJobsFactor(see additional key value pairs at top level ... for this depot alone
OvertimeMinutesFactor Default: 10. This can be set to over-ride the global value of OvertimeMinutesFactor (see additional key value pairs at top level ... for this depot alone
OverweightFactor Default: 0.001. This can be set to over-ride the global value of OverweightFactor (see additional key value pairs at top level ... for this depot alone
RangeExcessPenaltyPerKm Default: 50. This can be set to over-ride the global value of RangeExcessPenaltyPerKm (see additional key value pairs at top level ... for this depot alone

Example TimeWindows array

TimeWindws example:

"TimeWindows": [{
                          "Day":  1,
                          "Windows":      [["06:00:00", "15:00:00"], ["15:30:00", "23:00:00"]]
                  }, {
                          "Day":  2,
                          "Windows":      [["06:00:00", "10:00:00"], ["11:00:00", "15:00:00"], ["15:30:00", "23:00:00"]]
                  }, {
                          "Day":  3,
                          "Windows":      [["06:00:00", "15:00:00"], ["15:30:00", "23:00:00"]]
                  }
                ]

The example 'TimeWindows' array is for a three-day problem. In this case, the facility is generally open from 06:00 to 23:00 with a break at tea-time (perhaps for cleaning, or even for tea.). On day 2 they have an additional break from 10:00 to 11:00, perhaps for staff training or for fire drill practice.

On each day, there can be any number of windows, including zero (which would mean closed on that day).








Splitting the window into two as follows:

"TimeWindows": [{
                        "Day":  1,
                        "Windows":      [["08:00:00", "24:00:00"]],
                }, {
                        "Day":  2,
                        "Windows":      [["00:00:00", "02:00:00"], ["08:00:00", "24:00:00"]]
                }, {
                        "Day":  3,
                        "Windows":      [["00:00:00", "02:00:00"], ["08:00:00", "24:00:00"]]
                }, {
                        "Day":  4,
                        "Windows":      [["00:00:00", "02:00:00"], ["08:00:00", "26:00:00"]]
                }
              ]

Each object in the array specifies the windows that start on that day. If a window continues into the next day, please express this by splitting the window into two, as the following example, where there is a four-day schedule, and the facility is generally open from 08:00 until 02:00 the next morning.

Notice that the last window of the last day covers the full range of the window, extending into the morning of the next day.










Finally, here is how to represent a 24-hour facility over a 3-day schedule:

"TimeWindows": [               {
                                        "Day":  1,
                                        "Windows":      [["00:00:00", "24:00:00"]],
                                }, {
                                        "Day":  2,
                                        "Windows":      [["00:00:00", "24:00:00"]],
                                }, {
                                        "Day":  3,
                                        "Windows":      [["00:00:00", "48:00:00"]],
                                }

On the last day, it is recommended to extend the end of the window into the next day, allowing for the possibility of completing jobs and/or shifts on day 3 very early in the morning of the fourth day, which may well be possible within the rules.

Default TimeWindows

Every Depot object and every Job object has a TimeWindows attribute. If it is not specified in the input, then the default is to have a single time window available for the entire day, on each day of the plan. For example, in a two day problem, the default TimeWindows will be:

"TimeWindows": [               {
                                        "Day":  1,
                                        "Windows":      [["00:00:00", "24:00:00"]],
                                }, {
                                        "Day":  2,
                                        "Windows":      [["00:00:00", "48:00:00"]],
                                } 
               ]

Driver Key/Value pairs

Key Description
AdditionalDoorstepTime Default:"0m". This is an amount of time which will be added to every job done by this driver (i.e. every collection, delivery and visit). The typical use case is that the driver might be a new employee being broken in gently. If, say, it is set to "00:01:30", then a delivery that would otherwise take 6 minutes will take 7 and a half minutes if done by this driver. Go to Action Key/Value pairs to see how this is reflected in the output. This additional time is independent of, and hence additional to, any such additional time included based on the vehicle (see Vehicle Key/Value Pairs).
Availability Default: 1 driver of this type available for each day of the plan (see example below). If supplied, this is an array indicating the number of drivers of this type available on each day of the plan. E.g. for a two-day plan this could be: "Availability": [{"Day":1,"Number":5}, {"Day":2,"Number":7}]. An example of the default for a three-day plan is; "Availability": [{"Day":1,"Number":1}, {"Day":2,"Number":1}, {"Day":3,"Number":1}].
DrivingBreak Threshold Default:"270m". This is the maximum amount of driving allowed before taking a break; When EU rules apply, this is the legal limit (4 and a half hours), and the GUI could enable the user to reduce it if they wish. There is no equivalent for GB rules, however for both GB rules and no-rules cases, the GUI should provide the user with the ability to configure a driving break threshold, with the EU value as default.
DrivingBreak Duration Default:"45m". This is the amount of break time required, under EU rules, to recover from driving without a break for DrivingBreakThreshold. When EU rules apply, GUI users can be allowed to increase this value. When GB-rules or no-rules, GUI cases can be allowed to change this either up or down.
DrivingBreakPartOne Default:"15m". See below.
DrivingBreakPartTwo Default:"30m". Under the EU rules, the DrivingBreakThreshold amount of driving can be 'covered' by a single break of DrivingBreakDuration, or alternatively it can be covered by two breaks -- interspersed in the driving period -- where the first break is of duration at least DrivingBreakPartOne and the second is at least DrivingBreakPartTwo. Under EU rules, the GUI should allow the user to increase each of these if they wish. Under GB-rules or no-rules, Vortex will only consider the simple case of a single driving break DrivingBreakDuration to cover DrivingBreakThreshold of driving; so, (i) these two should not be exposed to the user, and (ii) they should be set at whatever value the user has set for DrivingBreakDuration.
BriefingTime Default:"0m" The briefing duration at the beginning of driver's shift.
DebriefingTime Default:"0m". The debriefing time needed by the driver at the end of their shift.
Characteristics Default:[]. List of strings represents the skills that this driver has.
CostPerDay Default:0. Daily cost of the driver, this is a fixed cost amount added in the days when the driver is hired.
CostPerHour Default:0. Hourly cost of the driver.
IdString Mandatory. A unique ID for the driver.
MaximumDriving Duration Default:"10h". The maximum time a driver can drive during their shift. The default value reflects the maximum allowed by both EU rules (see rule EU2) and GB rules (see rule GB1). When either EU or GB rules apply, the GUI should allow users to reduce the default values. When no rules (or 'custom rules') apply, the GUI can allow users to vary this in either direction.
MaximumDriving DurationAlternate Default:"9h". When EU rules apply, if the number of allowed days of MaximumDrivingDuration have been used up, then this is the time allowed in the remaining days of the current week. For the GB-rules and no-rules cases, this is not used and should be hidden from the user.
MaximumDriving DurationDays PerWeek Default:2. This is the number of days in a 6-day period that driving forMaximumDrivingDuration is allowed. This is necessary for the EU rules case and should be set at 2, but can be reduced by the user. For the GB rules and no-rules cases there is no equivalent required, so this should be set to 7 and not exposed to the user.
MaximumDriving DurationDays PerWeekState Default:0. This is the number of days in the current week (the week, starting on Monday and ending on Sunday, that includes PlanDate) that this driver has driven for longer than MaximumDrivingDurationAlternate.
MaximumDriving TimeWeek Default:"56h". This is the maximum amount of driving time allowed in a week. By EU rules this is 56 hours, with a week defined as the period between Monday 00:01 and Sunday 23:59. The GUI should preset this to 56 hours, and, in the EU rules case, allow the user to reduce it if they wish. For the GB-rules and no-rules cases, there is no equivalent rule. However we want to provide the user with the ability to configure this, and hence the GUI should use the EU default value, but allow the user to change it up pr down within reason.
MaximumDriving TimeFortnight **Default:"90h". Exactly as above, the EU has a rule for maximum driving in two weeks (90 hours). In the EU rules case the GUI can set this and allow it be reduced, in the GB and non-rules cases it can allow it to be changed at will.
MaximumOvertime Default:"0m". Maximum overtime duration (beyond 'MaximumWorkingTime') which the driver can do in one shift.
OvertimeCostPerHour Default:0. Hourly cost for overtime.
MaximumOvernights Number Default:0. Maximum number of continuous nights that a driver can do when doing overnights. The value zero indicates that overnight settings is turned off for this driver.
OvernightRadiusKm Default:0. If a driver is going to stay overnight at a location, this location must be at least OvernightRadiusKm from the driver's end location. The end location comes from the vehicle that this driver is assigned to; normally it will be the vehicle's depot, or it could refer to the nearest of the allowed depots.
OvernightCost Default:0. Fixed cost for an overnight.
PossibleDays Default:[1,2,3,...,Numdays]. List of integers representing the possible days numbers that the driver is available on. e.g. [1, 5] means that this driver is available on the first day of the schedule and the fifth day of the schedule.
ShiftEarlyStartTime Default:"06:00:00". The earliest time that the driver can start their day's work.
ShiftEarlyStart TimeArray Default:null. A day/times array with "Time" set atShiftEarlyStartTime for each day. An optional array of objects providing over-rides for the ShiftEarlyStartTime on specific days. See Days and Times array example
ShiftLateStartTime Default:"08:00:00". The latest time that the driver can start their day's work.
ShiftLateStart TimeArray Default:null. A day/times array with "Time" set atShiftLateStartTime for each day. An optional array of objects providing over-rides for the ShiftLateStartTime on specific days. See Days and Times array example
MinimumRestPeriod Default:"9h". Under EU rules, this (9 hours) is the absolute minimum amount of continuous rest that a driver should have between two shifts.
MinimumRestPeriod Alternatives **Default:[ ["11h"], ["3h", "9h"]]"". Under EU rules, if a driver has already had rest periods as short as 9 hrs for 3 days in the week, the remaining working days must have longer rests. The longer rest within one of these remaining days can either be: (i) a single continuous period of at least 11 hours, or (ii) it can comprise two periods, one of at least 3 hrs and the other of at least 9 hrs. The indicated default json array expresses these two possibilities. There is no equivalent GB rule. When GB rules or no-rules apply, we will simplify the situation by providing only the MinimumRestPeriod for the user to configure, keeping 9 hours as a convenient default, and allow the user to change it up or down; also, MinimumRestPeriodDaysPerWeek should be fixed at 7, MinimumRestPeriodAlternatives should be null, and neither should be available to the user.
MinimumRestPeriod DaysPerWeek Default:3. Under EU rules, the above MinimumRestPeriodSecond can be the amount of rest allowed for at most this number of days in any given week. For the remaining days of the week, the longer period of MinimumRestPeriod is required.
MinimumRestPeriod DaysPerWeekState Default:0. For the week (Monday to Sunday) that includes the PlanDate, this is the number of days so far that the driver's rest period has been less than MinimumRestPeriod.
MinimumWeekly RestPeriod Default:"45h". After around a week of driving with standard daily rest periods, the EU rules dictate that a driver must have a continuous rest period of at least this long (45 hrs). The GUI user should be allowed to increase it. For GB-rules and no-rules cases, there is no equivalent directive; however we will give users the ability to configure this weekly rest period, with the EU default value, allowing them to increase or decrease it.
MaximumWeekly RestInterval Default:"144h". In the EU rules, this is the maximum amount of time (144 hours) allowed between two weekly rest periods; in EU-rules cases, the user can be allowed to reduce this. In GB-rules and no-rules cases they can be allowed to configure it at will.
SinceLastWeeklyRest Default:"0m". This provides state information for a driver, indicating how much time there has been since the end of their most recent weekly rest period.
MaximumWorking Time Default:"11h". This is the maximum amount of time that the driver is allowed to WORK during a shift. This value is taken to be the default for each day of the plan. EU rules do not provide a daily max for working time (presumably deemed covered by rest rules). So we provide a reasonable default (arising from the GB rules) and the ability to configure it up or down within reason. The same is done for no-rules case. However, if GB rules apply, this must be 11 hours, and the user should only be able to reduce it.
MaximumWorking TimeArray Default:null. A day/times array with "Time" set atMaximumWorkingTime for each day
NightWindow Default:["00:00:00", "04:00:00"]. If a shift overlaps with this window, it is considered as night work, and a different maximum working time might apply (see next). Vortex will check the early part of the shift for overlap with this window, and the later part of the shift for overlap with the next night window (e.g. in this case that would be ["1 00:00:00", "1 04:00:00"]).
MaximumNight WorkingTime Default:"10h". In the EU rules, the maximum working time allowed is lower if the driver's shift includes working during the nighttime period, where nighttime is defined as 00:00--04:00 (the default value for the NightWindow above). When EU rules apply, this window can be widened if the user wishes, e.g. [“23:00” “04:30”], but must contain [“00:00 “04:00”]. When GB rules or no-rules apply, these can be changed to any interval.
WorkQualifying BreakDuration Default:"15m". This is the smallest amount of time that qualifies to contribute to a break (see below).
WorkBreakDuration1 Default:"30m". This is the amount of break required if the amount of work is in a certain range (see below).
WorkBreakDuration2 Default:"45m". This is the amount of break required if the amount of work is in a certain range (see below).
WorkBreakThreshold1 Default:"6h". Defines the lower part of the range (see below).
WorkBreakThreshold2 Default:"9h". This and the previous four keys implement the EU rules for breaks from work. In simple terms: if the amount of work time in a shift is between WorkBreakThreshold1 and WorkBreakThreshold2, then the break should be WorkBreakDuration1; if the work time is longer than WorkBreakThreshold2, then the break time should be WorkBreakDuration2. Breaks can be made of smaller parts, but the minimum length of a part to count is WorkQualifyingBreakDuration. When EU rules apply, the defaults will usually apply unchanged, however a user can be allowed to increase any of the these five values. When GB rules or no-rules apply, we provide the same model to the user with the same defaults, but the defaults can now be changed up or down within reason. Note that WorkBreakDuration1 and WorkBreakDuration2 must both be >= WorkQualifyingBreakDuration; also, WorkBreakThreshold2 must be larger than WorkBreakThreshold1
MaximumWeekly WorkingTime Default:"60h". In EU rules, this (currently 60 hours) is the maximum amount of work allowed within a week (defined as the period from Monday 00:01 to Sunday 23:59). When EU rules apply, this can be increased by the user. For GB-rules and no-rules cases, this can be configurable up or down, within reasonable limits.
MaximumAverage WeeklyWorkingTime Default:"48h". when calculated over a reference number of weeks (see below), this is the maximum allowed for the average amount of work in a week (48 hours). This only applies in the EU rules case. In the GB-rules and no-rules cases, we will use only the MaximumWeeklyWorkingTime, so MaximumAverageWeeklyWorkingTime` should be set to the same value, and hidden from the user.
WeeklyAverage ReferenceWeek Default:17. This is the standard number of weeks in the EU reference period (see above); the user should be allowed to increase it. For GB rules and no-rules cases, this should be hidden from the user and fixed at 1.
WeeklyAverage ReferenceStateWeek Default:1. The current week of the reference period when the new schedule starts. This provides necessary state information, combined with below. This should only be visible when EU-rules apply, and able to be varied between 1 and WeeklyAverageReferenceWeek inclusive. For GB and no-rules it should be hidden and fixed at 1.
WeeklyAverageState Default:"0m". This is the amount of working time there has been for this driver in the current reference period, up to the start of the new schedule. This should only be visible and configurable in the EU rules case if WeeklyAverageReferenceStateWeek is > 1. For GB-rules and no-rules cases, this should always be hidden and set to 0.
ThisWeekWorkSoFar Default:"0m". In all cases, we need this state information about the current week (for the EU rules case, this is still necessary even though we have the WeeklyAverageState). However, the GUI can omit this if the first day of the new schedule is a Monday.
OneWeekDriving TimeState Default:"0m". For the purpose of EU rules on driving time, A week begins on Monday and ends on Sunday. This indicates the amount of driving time that this driver has accumulated in the week that includes the first day of the plan. This is necessary so that the algorithm can correctly respond to MaximumDrivingTimeWeek for the first week of the plan.
TwoWeekDriving TimeState Default:"0m". This is the two-week version of the OneWeekDrivingTimeState above. This should be equal to teh value of OneWeekDrivingState plus the driving time incurred in the week immediately before that. This is necessary so that the algorithm can correctly respond to MaximumDrivingFortnight for the first week of the plan.
LastShiftEnd DateTime Default:"DD/MM/YYYY 18:00:00" where DD/MM/YY is 3 days before Plandate. This is a date and time, in format "DD/MM/YY HH:MM:SS", indicating the time when this driver's last shift ended. The default mimics the situation where the shift ended on Friday 6pm and the new plan starts on the Monday. It is needed for accurate handling of rules for driver rest periods during the first few days of the plan.

The following are additional key/value pairs that should only be used in the circumstance that Breaksmode is set to "Segmented". In this situation, there are up to two breaks in a shift. The first break is of length "WorkBreakDuration1", and must occur between two given times -- e.g. perhaps between 2 hours and 4 hours after the start of the shift. The second break is of length "WorkBreakDuration2" and must occur between two later given times, e.g. between 6 hours and 8 hours after the start of the shift. When the "Segmented" BreaksMode is in play, the two "WorkBreakDuration" keys are therefore used in a similar way to the default BreaksMode. However the the two parameters for each of the two breaks are expressed by using additional keys, "EarlyWorkBreakThreshold1" and "EarlyWorkBreakThrshold2", in conjunction with using the existing keys, "WorkBreakThrshold1" and "WorkBreakThrshold2" with slightly different semantics. The new keys and alternate usage are as follows:

Key Description
EarlyWorkBreakThreshold1 by default, same as WorkBreakThreshold1. Used only when BreaksMode is "Segmented", this is the earliest time (counted from the beginning of the shift) when the driver can take their first break.
EarlyWorkBreakThreshold2 by default, same as WorkBreakThreshold2. Used only when BreaksMode is "Segmented", this is the earliest time (counted from the beginning of the shift) when the driver can take their second break.
WorkBreakThreshold1 Default:"6h". When BreaksMode is "Segmented", this is the latest time (counted from the beginning of the shift) when the driver can take their first break.
WorkBreakThreshold2 Default:"9h". When BreaksMode is "Segmented", this is the latest time (counted from the beginning of the shift) when the driver can take their second break.
ContinuousWorkLimit Default:"24h". In normal circumstances, the default settings (in the main list above) for driver working breaks and associated thresholds are sufficient to lead to outcomes consistent with the standard UK/EU driving regulations. In rare combinations of conditions, the default settings will nevertheless allow a continuous period of working (driving + delivering/collecting) which is longer than WorkBreakThreshold1, which in turn is not permitted under the standard rules. For many users, this may still be acceptable since it can be reconciled with double-manning or similar arrangements. However, to absolutely ensure that the plan for a given driver involves no periods of continuous work longer than a certain amount, this key can used. A typical setting would be "360m", and the break imposed will be the same as WorkBreakDuration1.
UnpenalizedOvertime Default:"0s". In normal circumstances, any overtime beyond the maximum allowed overtime (even one second beyond) will incur penalties, and hence it generally won't appear in plans. However, this attribute enables the maximum overtime to be relaxed to some degree, at the driver level. A representative setting might be "00:00:59", for example. This means that if the driver needs to be back to base by "17:30:00", then anything up to "17:30:59" will be OK.

Days and Times array example

The Driver keys: ShiftEarlStartTimeArray, ShiftLateStartTimeArray and the vehicle keys: StartTimeArray and MaximumWorkingTimeArray all have this type of array as their value, providing a time for each day of the plan.

Times as a fixed time in a day:

"EarlyStartTimeArray": [       {
                                        "Day":  1,
                                        "Time":   "07:00:00"
                                }, {
                                        "Day":  2,
                                        "Time":   "07:00:00"
                                }, {
                                        "Day":  3,
                                        "Time":   "07:15:00"
                                }],

The example listed is a driver's EarlyStartTimeArray for a 3 day plan, where she gets a very brief lie-in on the third day:












Times as durations example:

"MaximumWorkingTimeArray": [       {
                                        "Day":  1,
                                        "Time":   "16:00:00"
                                }, {
                                        "Day":  2,
                                        "Time":   "16:00:00"
                                }, {
                                        "Day":  3,
                                        "Time":   "16:00:00"
                                }],

In this next example, of a vehicle's MaximumWorkingTimeArray, the times are durations, but otherwise the same format applies. This vehicle can generally be run for 16 hours a day (presumably not all with one driver):

VehicleProfiles array items

Here is an example of a VehicleProfiles array:

"VehicleProfiles":
     [
        {"Type":"BigVan", 
         "TransportMode": "truck", 
         "SpeedProfiles":[
            { "SpeedsMetersPerSecond":[ 55, 50, 50, 40, 40, 20, 20] }
           ],
         "ChargingParameters":{
        RechargeRankeKm: 210
     }
        },
        {"Type":"Car", 
         "TransportMode": "car",
         "MaximumSpeedKPH": 94,
         "SpeedProfiles":[
            { "SpeedFactor": 1.1,  "TimeFrom":"00:00:00", "TimeTo":"07:00:00"},
            { "SpeedFactor": 0.9,  "TimeFrom":"07:00:00", "TimeTo":"07:30:00"},
            { "SpeedFactor": 0.8,  "TimeFrom":"07:30:00", "TimeTo":"08:45:00"},
            { "SpeedFactor": 0.825,  "TimeFrom":"08:45:00", "TimeTo":"09:30:00"},
            { "SpeedFactor": 0.95,  "TimeFrom":"09:30:00", "TimeTo":"12:30:00"}
          ]
        },
     ]
Key Description
AvoidRoad Default:[]. This is a list of road types that cannot be used by the vehicle. Supported values are: TOLL_ROAD, MOTORWAY, TUNNEL, BOAT_FERRY, PARK, DIRT_ROAD. e.g. ["TUNNEL", "TOLL_ROAD"] means the associated vehicles cannot travel in tunnels nor toll roads.
ChargingParameters Default:{}. This object may contain a value for the vehicle's maximum range in km, keyed by RechargeRangeKm, as in the example above.
HeightMetre Default:0. The vehicle's height in meters.
LengthMetre Default:0. The vehicle's length in meters.
MaximumSpeedKPH Default:100. This is an absolute maximum speed, over-riding the travel time in cases where a high "SpeedFactor" might be faster.
SpeedProfiles Default:null. An array of 'SpeedProfile` objects.
SpeedProfile Default:null. This an object containing a speed profile. There are two distinct types of speed profile, and only one type is allowed in each VehicleProfile object. The main type of speed profile (as in the "Car" example above), comprises an array of objects that each indicate a "SpeedFactor" for a given time window. The time window is conveyed via "TimeFrom" and "TimeTo", the arguments to which are time strings (see Time Strings Format), which in this context represent absolute times of day. If this type of speed profile is used, then we will obtain travel times for each journey from the chosen matrix service as usual, and the speed factor instructs us to speed up or slow down relative to these travel times for particular parts of the day. The example above shows how speed factors might be configured for the morning rush hour. For any time period not covered in the SpeedProfiles array the SpeedFactor defaults to 1. Finally, when this type of speed profile is used, we use MaximumSpeedKPH (defaulting to 100 kilometres per hour) as an absolute maximum speed, over-riding the travel time in cases where a high "SpeedFactor" might be faster. A further type of speed profile, as in the "BigVan" example, is currently possible, but likely to be phased out soon. This is an array of seven numbers indicating speeds in meters per second for up to seven road types. By convention, the array of speeds has its elements in order of the 'rank' of the road -- e.g. motorways first, then major roads, etc... and finally country lanes.
TransportMode **Default:"truck"*. The transport mode to be used in time/distance matrices. Supported values are: TRUCK, CAR.
Type Mandatory. The type of the vehicle.
WeightKg Default:0. The vehicle's weight in kg.
WeightPerAxleKg Default:0. The vehicle's weight per axle in kg.
WidthMetre Default:0. The vehicle's width in meters.

Vehicle Key/Value pairs

Key Description
AdditionalDoorstepTime Default:"0m". This is an amount of time which will be added to every job done on this vehicle (i.e. every collection, delivery and visit). A possible use case is that the vehicle may be temperamental, needing added time to get (re)started . If, say, it is set to "00:01:30", then a delivery that would otherwise take 6 minutes will take 7 and a half minutes if done using this driver. Go to Action Key/Value pairs to see how this is reflected in the output. This additional time is independent of, and hence additional to, any such additional time included based on the driver (see Driver Key/Value Pairs).
IdString Mandatory. A unique ID for the vehicle.
DeliveriesFirst Default:false. If true, all deliveries by this vehicle should happen first, followed by any collections.
Capacities Default:[0]. Optional. Array of numbers defines the capacities of the vehicle. See Capacities, Quantities and Stock. If the vehicle has more than one compartment, then this object should not be included, a Compartments array is included instead.
CleanBetweenShifts Default:true. This is relevant only if there are Stock Sequence Rules in place and Numdays is more than 1; essentially this controls whether or not the stock sequence rules apply to the first stock loaded on day N+1. If true, then anything can be in the first load; if false, we need to consider the sequence rules with regard to the last stock unloaded from this vehicle on day N.
Compartments Default:null. If the vehicle has more than one compartment, then this array provides capacity and associated details for each compartment. See Compartments.
CarbonPerKmKg Default:0. Carbon emissions for this vehicle per Km in Kg.
CarryingWeightKg Default:0. The total body weight of the vehicle when empty (with nothing loaded).
Characteristics Default:[]. List of strings representing the skills that this vehicle has.
IncludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for drivers to be able to use this vehicle. See Characteristics, Include and Exclude examples
ExcludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a driver from using this vehicle. See Characteristics, Include and Exclude examples
CostPerDay Default:0. Fixed daily cost for operating the vehicle.
CostPerDayofNonUse Default:0. Fixed daily cost if the vehicle is not used.
CostPerKm Default:1. Ongoing cost of driving the vehicle, mainly fuel and maintenance costs per km.
DepotLoadingTime Default:"0s" .Loading time needed in the depot for this vehicle. Total loading time is calculated using depot loading time and vehicle loading time and job requests loading times all together. This one is a fixed cost for the vehicle (not per item loaded/unloaded).
IdString Mandatory. A unique ID for the vehicle.
StartLocationGeohash Default:null. The start location geohash of the vehicle, this should be defined and cannot left empty unless the vehicle has OpenStart turned on.
StartLocationLatLong Default:[]. The vehicle's start location latitude/longitude pairs. Please note that if StartLocationGeohash is already populated, the geographic location would be calculated using only the geohash value. In other words StartLocationGeohash has priority over StartLocationLatLong.
EndLocationGeohash Default:null. The End location geohash of the vehicle, if not specified it will decided by the algorithm. The default position, if this is undefined, is that the vehicle can finish at any of the depots it can use (from 'VehicleDepotMap').
EndLocationLatLong Default:[]. The vehicle's end location latitude/longitude pairs. Please note that if EndLocationGeohash is already populated, the geographic location would be calculated using only the geohash value. In other words EndLocationGeohash has priority over EndLocationLatLong.
MaximumLoad UnloadTime Default:"12h". The Maximum amount of time needed for loading the whole vehicle. This is an upper bound for loading/unloading time calculation for this vehicle no matter how many jobs requests.
MaxLoadWeightKg Default:0. This should be set accurately for the vehicle if it is possible that its route might need to avoid bridges (for example) that have a maximum weight requirement. In that case, users should take care to set each of CarryingWeightKg, MaxLoadWeightKg, and the WeightKg field for each job. Otherwise, these need not be populated. (Note: to generally account for not exceeding a vehicle's weight capacity, the mechanisms that should be used are those provided by the Quantities and Capacities arrays).
StartTime Default:"04:00:00". The earliest time that the vehicle can start; this will be the time used for each day of the plan, unless the StartTimeArray overrides it.
StartTimeArray Default:null. A day/times array with "Time" set at StartTime for each day. An optional array of objects, each of which specifies a day of the plan and the start time for that day. See Days and Times array example
MaximumWorkingTime Default:"24h". The amount of time the vehicle can be used in a day (perhaps by different drivers). There is usually no restriction, hence the default is 24 hours. Note that the vehicle can work for this length of time, starting at any time after its eariest start. For example, if the vehicle's earliest start time is 04:00 and its maximum working time is 12 hours, then it can work from 04:00 to 16:00, or from (for example) 11:00 to 23:00.
MaximumWorking TimeArray Default:null. A day/times array with "Time" set atMaximumWorkingTime for each day. An optional array of objects, to be used if there are different MaximumWorkingTimes to be set for this vehicle for different days of the plan. See Days and Times array example.
OpenStart Default:false. true or false to define whether the vehicle can start from the location of first job it is servicing.
OpenEnd Default:false. true or false to define whether the vehicle can finish at the location of the last job it is servicing.
OperatingRadiusKm Default:1000 .The vehicle cannot travel more than this distance away from its start location.
PossibleDays Default:[1,2,3,...,Numdays]. List of integers defines the possible days numbers which this vehicle is available on. e.g. [1, 5] means that it is available on the first day of the schedule and the fifth day of the schedule.
PreLoaded Default:false. true or false to define whether the vehicle is already loaded before the start of the shift.
RangeKm Default:null .The vehicle cannot travel more than this total distance in one shift. If negative, the range is treated as unlimited; if it is a positive number, e.g. 100, then the vehicle's shift is limited to 100Km of total travel.
SequenceGroupId Default:null. Optional. A string, identifying the specific vehicle that this vehicle object maps to. See SequenceGroupOrder below.
SequenceGroupOrder Default:-1. Optional - An integer, identifying the position of this vehicle object within its sequence group. Together with SequenceGroupId, this key is for use by customers whose practice is to re-use a physical vehicle for a sequence of shifts during the day. E.g. one driver may do the 09:00--15:00 shift, and the next driver will do the 15:00--21:00 shift. The two shifts will be represented for vortex as two distinct vehicles. However, in order to make sure that we correctly handle any overtime (overtime in an earlier shift could force later start of next shift), and also to handle vehicle range (see "Charging Parameters"), vortex needs to be told which shifts are in fact the same physical vehicle, and the time ordering of those shifts. See the Linking Shifts example.
SuppressDepotReload Default:false. true or false value to define whether the vehicle is prevented from reloading during the shift time.
Type Default:null. A mandatory string identifying the type of the vehicle; this will link the vehicle to the VehicleProfiles array which provides a speed profile for.
UnloadAfterShift Default:true. true or false to define whether the vehicle will unload after shift, in other words, unloading time will not be accounted for in the end of the shift.

Some additional Key/Value pairs are noted below, which we generally recommend should not be used (i.e. the default values should normally be left in place). Using non-default values for these Key/Value pairs are for specialised use-cases and could have a range of unexpected side-effects, so it is best to discuss their use first with the vortex team.

Key Description
CompromisedDays Default:null. Currently this is only fully operational in FastMode. CompromisedDays is a list of integers defining day numbers for which this vehicle cannot be used. E.g. [1, 5] means that this vehicle is unusable on the first day of the schedule and the fifth day of the schedule. In fastmode, since only the first day is available, the only valid value (apart from null) is [1]. The intended use-case relates to vehicle shifts that are in the developing plan, but have suddenly become unplannable due to, for example, the driver calling in sick. See Compromised Shifts and Jobs for a full explanation of this facility.
EndEachDayEmpty Default:true. Usually the customer expects each vehicle to end each day with nothing on board -- that is, everything that was loaded or collected that day has been delivered. However, in some cases the customer will be content with (or indeed encourage) situations where jobs might be loaded or collected on day N, and delivered on day N+1; hence they finish on day N with goods on board, typically to deliver early the next day. To allow this, EndEachDayEmpty should be set to false. if Numdays is 1, this will always be true (independent of the input setting).
LoadPriority Default:null. In general, vortex will distribute jobs among the vehicles in the way that best optimizes mileage, cost, etc. However, in some cases users may wish for some vehicles to be used more than others. For example, the overall intent might be to balance the workload over the available drivers, so drivers who had short shifts yesterday might be tagged for longer shifts today. To cater for this, vehicles can be given a LoadPriority (a positive integer). Vortex will then make sure that the vehicles with highest LoadPriority will be doing the most work, specifically in terms of having the longest shifts (meaning difference between end of shift time and start of shift time). As with Job priorities, the lower the numeric value, the higher the priority (e.g. vehicles with LoadPriority: 1 will be preferred over vehicles with LoadPriority: 2, and so on). Note: LoadPriority will be considered secondary to optimization concerns. For example, if there are vehicles with different capacities, then it will usually make sense for the highest-capacity vehicle to be doing the most work, even if it does not have the best LoadPriority. Finally, if there are multiple vehicles with a range of LoadPriority values, then (subject to optimization concerns) vortex will distribute the workload accordingly. That is, sorting the vehicles according to LoadPriority will be equivalent to sorting them by working time.
LockStatus Default:null. This is only used by a subset of customers for particular scenarios, typically involving same-day delivery. See Shift Locking.
MaximumExcessPercentQ1 Default: 0. In the fastmode/EOS context (see FastMode), a vehicle is allowed to be temporarily overloaded. This can happen when customers are allowed to increase the weight of their existing booked orders; the sudden potential shock to the vehicle plan is initially absorbed by simply allowing the overload and incurring a penalty; in the next few minutes of optimization, this overload (along with the allowance for it) will typically reduce as jobs get re-arranged across shifts. The role of MaximumExcessPercentQ1 is to provide an absolute cap on the largest overload that can be initially absorbed in this way. For example, if this was set to 10, then, in order to avoid a reload causing lates and/or overtime, a vehicle with capacity 1000 can be temporarily loaded to 1100 (allowing an extra 10 per cent); if the increased weight would take it to 1101 or more, then the plan will resort to doing a reload instead, leading to overtime and lates. The most cautious approach is for users should set this to the maximum overload that they would typically be able to accommodate. E.g., in the worst case, if a customer adds more weight very close to the booking cutoff time, there may be not enough opportunity for the overload to be 'optimized out', and hence the overload has to be implemented on the road. So, a value of 20 would be appropriate in scenarios where vans with 20 per cent larger capacity are available, or if such capacity has been kept in reserve.
OrganizeAsLoads Default:false. A 'Load' is a sequence of activities which starts with the vehicle empty, then has a series collections (from one or more depots or otherwise), and finally has a series of deliveries, which deliver all of those collections, leaving the vehicle empty again. If OrganizeAsLoads is set to true, then every shift should be a sequence of loads. So, the vehicle will be loaded, then emptied, then loaded, then emptied, and so on.Note, however, that if EndEachDayEmpty is false, the shift might start with deliveries from a load that started the previous day, and it might end with a load not fully delivered (to be finished the next day).(Note:OrganizeAsLoadsis primarily for use by businesses that exclusively operate Delivery jobs. It can be used when other types of jobs are involved, however, if so, and if Runorderis used, it should only apply to the standard Delivery jobs ).

~

VehicleDepotMap Array items

Key Description
DepotId Default:null. The depot ID which need to be mapped to the vehicle specified in VehicleId
VehicleId Default:null. The vehicle ID which need to be mapped to the depot specified in DepotId

VehicleDriverMap Array items

Key Description
Day Default:null. The day (positive integer) relevant to this specific mapping. If absent or null, this DriverId/VehicleId pairing is assumed to be in place for every day of the plan
DriverId Default:null. The driver ID which need to be mapped to the vehicle specified in VehicleId
VehicleId Default:null. The vehicle ID which need to be mapped to the driver specified in DriverId

Job Key/Value pairs

Types of Jobs

In most cases, a job is either a Collection (it has to be collected at a customer site, and afterwards unloaded at a depot), or a Delivery (it has to be loaded at a depot, and later Delivered to a customer site). In either of these cases, the task is represented by a single Job object, with a unique IdString, and with details of the customer site, quantities, and so on.

In some cases, however, the task is to collect a package from one customer site and deliver it to another customer site, with no depot loading or unloading involved. This is a 'courier'-style task, which needs to be represented by two Job objects: one for the Collection, and one for the Delivery. These two job objects should still have unique IdStrings, but the Delivery one should have its Connected key set to the IdString of the Collection part.

Key Description
IdString Mandatory. A unique ID for the job.
Connected Default:null. If this job is the Delivery part of a 'courier'-style task, then this is the IdString of the Collection part. (see notes above).
Vehicle Default:null. The vehicle which has done the job, or needs to do the job. This is useful in benchmark situations when we want to check the quality and feasibility of the customer's example solution (see Benchmarking. Note: if "Vehicle" is set using this key, and if the "Day" field is also set, then it will definitely be scheduled (on the specified vehicle, naturally, on the specified day), irrespective of any violations of constraints or capacities; assigning a vehicle via this method tells vortex: "schedule this job on the indicated vehicle/day, come what may". If, rather than this, you simply want to indicate that "if this job is scheduled at all, it should be this vehicle, but not if it causes problems", then this can be done by appropriate use of Characteristics in the Vehicle object, and IncludeVehicleCharacteristics in the Job Object -- See Characteristics, Include and Exclude examples.
RunOrder Default:null. This key is intended for use in benchmarking a plan that has been generated by a manual planner, or by other software. If RunOrder is set, then both the Day and Vehicle keys must also be set for this job. See Benchmarking for a full explanation.
DepotRunOrder Default:null. This key is intended for use in benchmarking a plan that has been generated by a manual planner, or by other software. If DepotRunOrder is set, then both the Day and Vehicle keys must also be set for this job. See the benchmarking section for a full explanation.
SequenceGroupId Default:null. This is only for use in conjunction with SequenceOrder (see below, and Using SequenceOrder);
SequenceOrder Default:null. A valid non-null SequenceOrder is any positive integer; the SequenceOrder specifies a job's position in relation to any other jobs on the same vehicle that also have their SequenceOrder set, as long as these jobs share the same SequenceGroupId. For details and examples, see Using SequenceOrder.
Day Default:null. If set, this specifies the specific day of the plan on which this job should be done. This is meant for use in benchmarking situations (Benchmarking) in conjunction with Vehicle, and RunOrder. If the context is not benchmarking, but the intention is to limit the job to a specific day of the plan (or in fact any subset of days), this should be done via PossibleDays.
CanSplitOver Compartment Default:false. true or false value to specify whether the job can be done in parts. Not currently supported.
IncludeDepot Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for depots to be able to serve this job. See Characteristics, Include and Exclude examples
ExcludeDepot Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a depot from servicing this job. See Characteristics, Include and Exclude examples
IncludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for drivers to be able to service this job. See Characteristics, Include and Exclude examples
ExcludeDriver Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a driver from servicing this job. See Characteristics, Include and Exclude examples
IncludeVehicle Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics necessary for a vehicle to be able to service this job. See Characteristics, Include and Exclude examples
ExcludeVehicle Characteristics Default:[]. An array of arrays of strings representing alternative groups of characteristics that would disqualify a vehicle from servicing this job. See Characteristics, Include and Exclude examples
FirstDrop Default:false. This can apply only to Delivery jobs (it will be ignored if set for other types of job), and relates to a business rule operated by a small number of potential users. IfFirstDropif set to true, vortex will ensure that this job is the first delivery of its shift. Two FirstDrop jobs will never appear in the same shift, unless they are part of a DeliverTogether group (in which case their ordering within the group will be arbitrary, but the entire group will be delivered before all other deliveries in that shift).
FixedVisitDuration Default:"1m". A fixed amount of time needed on-site before servicing this job request (see notes below).
ServiceVisitDuration Default:"5m". The amount of time which is needed for to service this job request (see notes below).
LoadTime Default:"1m". The amount of time needed at the depot for leading/unloading the job request to/from the vehicle.
LocationGeohash Default:null. The geographic location geohash of the job request.
LocationLatLong Default:[]. The job's latitude/longitude pairs. Please note that if LocationGeohash is already populated, the geographic location would be calculated using only the geohash value. In other words LocationGeohash has priority over LocationLatLong.
PossibleDays Default:[1,2,3,...,Numdays]. List of integers represents the days number in which this job request can be done. e.g. [1, 5] means that it can be done on the first and fifth days of the schedule.
Quantities Default:[0]. This is mandatory when the Service (see below) is collection or delivery, but it is not required for visit jobs. For collection and delivery jobs, this indicates the Quantities involved in the job, which is an array of numbers. This needs to map to Capacities in the Vehicle object. See Capacities, Quantities and Stock.
WeightKg Default:0 .An optional weight in kg for the object being carried (if any). This would be used to improve the accuracy of estimates of the schedule's carbon footprint, along with similar calculations such as TonneKm.
RevenueIncome Default:0. Amount of money which will be lost if the job left unplanned, a high value in this field makes the algorithm reluctant to keep the job unplanned.
Service Default:delivery. Service type of the job request, allowed values are collection, delivery and visit
TimeWindows Default:see Default TimeWindows. Indicating when the customer will be available for this job to be started; an array of time-window objects, each of which provides a list of one or more time windows for each day of the schedule. See Example TimeWindows array.
Type Default: either null, or the first stocktype. This is only necessary in situations where one or more Depots have "Stock" objects (See Capacities, Quantities and Stock and The Stock Array). If so, this must be one of the stocktypes that are named in a "Stock" array, the default value being the first stocktype mentioned in the input.
Priority Default:null. A priority value for this job, which should be a positive integer. 1 means highest priority, 2 means next highest, etc. If no priorities are provided for any jobs, all will have priority 1. If priority values are provided for some jobs, but not for others, then the priorities of the latter will be set to N+1, where N is the largest number explicitly assigned as a priority (hence lowest priority).
ADRTunnel RestrictionCode Default:null The tunnel code that this job should satisfy. Accepted values are "A", "B", "C", "D" and "E". For more details see: ADR Tunnel Restriction Codes
CustomerIdString Default:null . This optional key can be used to indicate the name of the company being delivered to (or collected from/visited). It will be particularly useful if your jobs involve visits to different companies at the same location. E.g. suppose "Bricks" and "Mortar" are the IdStrings of two different jobs that have the same location (perhaps to two neighbouring companies on an industrial estate). Since the location is the same, vortex will assume that these involve the same site, and will only incur the FixedVisitDuration for the first of these jobs. However if these jobs are given different values for CustomerIdString, then the correct FixedVisitDuration will be incurred for each company visit.

Note

The time taken to do a single job at a specific site will normally be the sum of FixedVisitDuration and ServiceVisitDuration. For example, FixedVisitDuration might be the time taken to get through site security, and ServiceVisitDuration is the time to load or unload the package. However, if the vehicle's schedule contains two or more jobs at the same site, they will only need the FixedVisitDuration time for the first of these jobs; subsequent jobs will only incur their ServiceVisitDuration time.

Additional Job Keys

Some additional Key/Value pairs are noted below, which are for specialised use-cases. Please discuss their use first with the vortex team.

Key Description
CutOffDateTime Default:null. This is a datetime -- in the format DD/MM/YYYY HH:MM:SS ; if set, it will be used to rule out certain shifts. That is, the job won't be placed on a shift which starts loading before this point in time. The datetime is meant to indicate by when (at the latest) the order will be prepared and ready to load, and is expected to be used by customers involved in certain flavours of same-day ordering and planning. Customers who use this must also set CurrentDateTime appropriately at top level (see V2 JSON Request).

ADR Tunnel Restriction Codes

Example

{
  "Jobs":[
    {
    ...
    "ADRTunnelRestrictionCode":"B"
    ...
    }
  ]
}

There are three major dangers in relation goods and tunnels which can cause multiple casualties or significant damage to tunnels which are:

  • Explosions
  • Release of toxic gas or volatile toxic liquid
  • Fires

The tunnel codes are categorised from "A" to "E". The algorithm shall avoid routing vehicles carrying hazardous goods through tunnels of the relevant ADR category.

  • null The same as "A", no restriction.
  • "A" No restriction (not sign posted).
  • "B" Passage forbidden through tunnels of category "B", "C", "D" and "E"
  • "C" Passage forbidden through tunnels of category "C", "D" and "E"
  • "D" Passage forbidden through tunnels of category "D" and "E"
  • "E" Passage forbidden through tunnels of category "E"

The algorithm uses the worst case category, assumes maximum load, ignores other carage and load based on weight for example: C500D.

Characteristics, Include and Exclude examples

Each Driver, Vehicle and Depot object has an associated list of Characteristics. For example, this is a potential list of driver characteristics:

"Characteristics": ["Devon", "nut-allergy", "Cert-HGV", "Cert-Toxic"]

This driver is based at the Devon branch, is allergic to nuts, and is certified to drive HGVs and to handle toxic materials.

An example list of characteristics for a depot might be:

"Characteristics": ["Batteries"]

... perhaps indicating that this depot can supply batteries. If you are not making use of the "Stock" object, and looking for a simpler way to ensure some of the jobs get served by the correct depots, this is obviously a useful way to do it (in combination with IncludeDepotCharacteristics below).

Meanwhile a vehicle's characteristics might be:

"Characteristics": ["Devon", "Wide"]

... perhaps indicating the intention that it sticks to Devon depots, and has a wide wheelbase.

What makes all these Characteristics come into force are the Include... and Exclude... arrays that we look at next.

A Vehicle object has IncludeDriverCharacteristics and ExcludeDriverCharacteristics arrays, which express requirements that a Driver must meet if they are to drive this Vehicle. Each of these is a list of lists, to be interpreted as alternative groups. This enables anything from very simple to very sophisticated logic to be applied.

For example, if the only requirement is that the driver is certified for HGV, then the Vehicle object will include the following:

"IncludeDriverCharacteristics": [["Cert-HGV"]],

"ExcludeDriverCharacteristics": [],

There might be more complex requirements. E.g. in the case below, the vehicle needs either an HGV-certified driver, or, failing that, an LGV licence plus 5-years' experience; however, for business reasons, drivers supplied by 'contractor5' are not trusted.

"IncludeDriverCharacteristics": [["Cert-HGV"],["Cert-LGV","5+Years"]],

"ExcludeDriverCharacteristics": [["contractor5"]],

Depots have Include... and Exclude... arrays too, available for Driver and Vehicle This could be used to specify regional constraints, among other things, e.g.:

"IncludeDriverCharacteristics": [["Cornwall","Security-Level-3"]],

"ExcludeDriverCharacteristics": [["nut-allergy"],["wheat-allergy"]],

Meanwhile, Job objects have a full set of Include... and Exclude... arrays available, for each of Depot, Driver and Vehicle.

"IncludeDepotCharacteristics": [["TV"],["Hub","Devon"]],

"ExcludeDepotCharacteristics": [],

"IncludeDriverCharacteristics": [["Cert-Electrical","Cert-Customer"]],

"ExcludeDriverCharacteristics": [],

"IncludeVehicleCharacteristics": [],

"ExcludeVehicleCharacteristics": [["Wide"]],

In this case, the job might be to collect a TV from a customer for repair; so it needs a depot that can receive TVs, or a local 'hub' depot (from where it can be relocated later). The driver needs to have some certification in dealing with electrical goods, and in customer facing, so that he can do some quick checks first. Finally, the customer's location is such that we can't use a wide wheelbase vehicle.

Job Constraints

The JobConstraints object is an array of constraints. Each constraint itself is simply an array, containing (i) an identifier as the first element (indicating what type of constraint it is), then (ii) a qualifier as the second element, basically indicating whether "All"(meaning all or none) of the jobs must be planned, or whether it is OK to plan "Some" of the jobs in the constraint; (iii) the remaining elements are the IdStrings of the jobs involved in that constraint. There are three types of constraint, as follows:

SameVehicle

A SameVehicle constraint starts with "SameVehicle" and then continues with two or more job IdStrings, indicating that the given list of jobs need to be processed by the same Vehicle. This is an example:

             ["SameVehicle", "Some", "Job_28", "Job_44", "Job_95"]

The precise meaning of a "SameVehicle" constraint is that the jobs will be collected and/or loaded by the same vehicle in the same shift. Note that any vehicle could be used to handle the jobs in the constraint, as long as all the jobs are done by that vehicle. if the intention is that these jobs are done by a specific vehicle, e.g. the vehicle with "IdString": "G358BBM", then this is not the way to specify that. Instead that would be done by setting "Vehicle": "G358BBM" inside the Job object for each of the relevant jobs.

DeliverTogether

The typical scenario for a "DeliverTogether" constraint is that we have one or more jobs that are for the same customer, but they might need to be collected from different depots and/or at different times, e.g. bricks might come from one depot and cement might come from another, but from the customer's viewpoint it's a single job and the want them delivered together. A DeliverTogether constraint starts with "DeliverTogether" and then continues with two or more job IdStrings, indicating that the given list of jobs need to be processed by the same Vehicle, with the customer-facing parts (usually the delivery) happening contiguously. This is an example:

             ["DeliverTogether", "All", "Fred's-TV", "Fred's-SetTopBox"]

With a "DeliverTogether" constraint, we assume "SameVehicle" applies, with the additional constraint that the deliveries should happen one after the other with no other jobs in-between (but not necessarily in the order given in the constraint).

Before

In case that one job has to be planned before another job, we have the "Before" constraint, e.g.:

             ["Before", "All", "Tarpaulin", "PaintPots"]

In this case, the "Tarpaulin" must be delivered before the "PaintPots", presumably because the customer needs to store the latter on top of the former. Unlike the case with the previous constraints, the jobs in a before constraint do not need to involve the same vehicle. If it is nevertheless intended that they be delivered by the same vehicle, then both types of constraint should be expressed, e.g. as follows:


"JobConstraints": [
                   ...,
                   ["Before", "All", "Tarpaulin", "PaintPots"],
                   ["SameVehicle", "All", "Tarpaulin", "PaintPots"],
                   ...,
                  ]

Note that the "Before" constraint is meant for situations like the above example, where occasional pairs of jobs, which may be delivered or collected by different vehicles, need to be done in a particular order. It is generally expected that there will not be many such constraints.

If the intention is instead to make sure that a sequence (perhaps many more than two) of jobs need to be delivered in that precise sequence by the same vehicle, then please instead use the SequenceOrder facility: Using SequenceOrder.

It means what would be intuitively expected; that is, the timing of the jobs should reflect their ordering in the constraint.

Shift Locking

A shift is a specific Vehicle on a specific day. For most use-cases (which are 1-day problems), a shift is simply a vehicle and can therefore be identified by the Vehicle "IdString". But in general we also need to remember that, for N-day problems, any given vehicle may be involved in up to N shifts. Meanwhile, a run indicates a vehicle loading up at the depot, making some deliveries, and then returning to the depot. Any shift might be made up of any number of such runs .

Vortex provides Shift Locking functionality to support certain use-cases for (typically) same-day home delivery scenarios. With Shift Locking, certain shifts can have their latest plans fixed in place, or fixed up to a certain time, while other vehicles' plans are still being optimized. Shift Locking works in tandem with Seeding (see Seeding the input); if a shift gets locked, this means that its plan remains the same as it was in the seed.

In more detail, vortex shift locking functionality makes it possible to have the following three types of shift in the same vortex input:

  • Unlocked: the default case; orders can be added to or removed from this shift during optimization as normal;
  • Locked, with EndTime HH:MM:SS: any runs already on this shift which started before HH:MM:SS will be unchanged, but orders can be optimized as usual beyond this time.
  • Locking: orders can be added to or removed from this shift during optimization as normal, but vortex will place extra emphasis on trying to 'cram' orders onto this shift, since it will soon be locked, thereby releasing capacity on unlocked vehicles.

In this way, by setting a shift's "LockStatus", selected vehicles can be dispatched for deliveries, collections, or service, while slot booking and optimisation continues for other vehicles as new customer orders continue to come in.

This is achieved in the json input via the attribute "LockStatus" at the Vehicle level. "LockStatus" is an array of objects that is designed for use in the EOS/vortex slot booking context, particularly for same-day planning, and helps to control aspects of booking jobs onto a shift before it (or part of it) gets 'locked'. If a shift is fully locked, this means its delivery plan is now fixed, and, hence the driver can be informed of the itinerary, the initial depot loading can start, and so on. More generally, a shift can be locked up to a given "EndTime". This means that the deliveries loaded at the depot before that time, and hence the entire plan to deliver those jobs and then return to base, cannot be changed. But, as long as there is still enough time to do so, more jobs can be added after that point.

Before a shift (or part of it) is locked, the user may wish to ensure that the utilization of that shift is maximised. Hence, the user may wish to 'cram in' jobs from other shifts (which are not due to be locked in the near future). The main advantage of such cramming is to increase the available capacity on the other (unlocked) shifts and hence potentially increase productivity. When this is happening, we consider the status of the vehicle to be "Locking", rather than "Locked". However, if left unchecked, in some circumstances the cramming process could lead to an unfortunate deterioration in KPIs. For example, a locking shift may have no additional opportunities for jobs along its existing route, so it might squeeze in a job from a different shift whose route is focussed several miles away. This causes a decay in Km per Job, which may be unacceptable to some users. To address this, we use a parameter called "KmPerJobCramLimit", which controls the level of increase in Km-per-job that is acceptable. The default is 1.05, which means that, during the "Locking" phase, the Km per Job KPI for the locking vehicles will be kept at no more than 5% higher than it was at the beginning of the phase. Note that the default can be changed by setting "DefaultKmPerJobCramLimit" at top level.

A vehicle's LockStatus array conveys details of the lock status for every shift in which that vehicle is involved (equivalently, one lock status object for each day of the plan). Each lock status object has up to four keys: "Day": 1, "Status": null, "EndTime": "1d", and KmPerJobCramLimit: 1.05. "Day" identifies the specific shift. In the main use case, the array will contain only one object and "Day" may be omitted since the default of 1 will apply. "Status" will be set to "Locking" or "Locked"; if null, then this is equivalent to having no locking in place for the given (or default) day. "EndTime" is to be set to a valid time string. Finally, "KmPerJobCramLimit" is provided at this level, in case there is a requirement to overwrite the default".

Here is an example: ``` }, { "IdString": "3456_Van 1-20200730-1", "Type": "3456", "CostPerDay": 100, "CostPerKm": 10, "CostPerDayOfNonUse": 0, "Capacities": [943, 27], "LockStatus":[ { "Day":1, "Status": "Locked", "EndTime": "19:30:00" }, { "Day":2, "Status": "Locking", "EndTime": "12:30:00", "KmPerJobCramFactor": 1.1 } ], "StartLocationLatLong": [50.1234, -1.2345], "StartLocationGeohash": "ghabcdefgh", "StartTime": "0m", "EndLocationLatLong": [50.1234, -1.2345], "EndLocationGeohash": "ghabcdefgh", "SuppressDepotReload": true, },

The EndTime in LockStatus should be interpreted as meaning "For the target vehicle, freeze the plan on a specified day for all runs which start before the EndTime".  Note: The resulting locked plan may have runs which end before or after the EndTime. The EndTime is not related to the runs return to base time; its only used to identify runs which start before the EndTime. Multiple calls are allowed, this will just update the EndTime (forward or backwards), so you can unlock as well. 

It's not possible to lock part of a run as this would not make sense, since any changes to the run will often have implications before the EndTime. 

When the vehicle's status is "locking", EOS will keep offering slots based on the current vehicle's availability as usual. However, when vehicle locking status is set to "Locked", EOS will not offer slots anymore. Thus, EOS will only use non "Locked" vehicles to calculate available slots.


<a name="linkingshifts"></a>
## Linking Shifts

This section shows how to use the `Vehicle` attributes `SequenceGroupId` and `SequenceGroupOrder`, as defined in [Vehicle Key/Value Pairs](#vehicle_parameters) (note, these are not to be confused with similarly named attributes of Jobs). These attributes are useful for is for users whose practice is to re-use a physical vehicle for a sequence of shifts during the day.  E.g. one driver may do the 07:00--12:00 shift, another will do a 12:30--17:30 shift, and then another may do the final 17:30--22:30 shift. Because (i) each shift used different drivers, which may have different break settings and working time details, and  (ii) the vehicle needs to be back at the depot at particular times, the best way to express this situation in a vortex input is to use three distinct vehicle objects, once for each driver. 
However, for scenarios in which overtime is allowed, or may happen, this could cause problems. Overtime in a shift means that the second shift will in reality need to start later, with further knock-on effects. This situation won't be represented in vortex, because it believes these to be distinct vehicles. 

However, we can tell vortex that these shifts are linked, by using `SequenceGroupId` and `SequenceGroupOrder`.  Each vehicle object simply needs to have these two values appropiately set in the input. Basically, 'SequenceGroupId' is a string that identifies the physical vehicle (e.g. it could be the registration plate), and `SequenceGroupOrder` is an integer that identifies the order of a vortex vehicle object in the sequence of vehicles that have the same `SequenceGroupId`. The example below shows how this might be done.

"Vehicles": [{ ... "IdString": "OurTruck-1-001", “StartTime”: “07:00:00”, "SequenceGroupId: "Truck1", "SequenceGroupOrder: 1,
... }, { ... "IdString": "OurTruck-1-002", “StartTime”: “12:30:00”, "SequenceGroupId: "Truck1", "SequenceGroupOrder: 2,
... }, { ... "IdString": "OurTruck-1-003", “StartTime”: “17:30:00”, "SequenceGroupId: "Truck1", "SequenceGroupOrder: 3,
... }]


In the above, the `SequenceGroupOrder`s are simply 1, 2 and 3 -- but they could equally have been 100, 200 and 300, or 700, 1230 and 1730. Note that the shift structure also happens to be encoded in the vehicle object's `IdString`s. This is a common practice among vortex user customers. Essentially, `SequenceGroupId` and `SequenceGroupOrder` provide the facility for representing this structure in a way that EOS/vortex will understand.

*Important Note*, for EOS users who have maintain multiple sessions associated with different vehicle depots: If you are using a simple scheme such as "Truck A", "Truck B", etc... for each depot/session, then for some users this will be fine.  However, if your setup allows for transfers of jobs between depots, then this won't work. 
In that case we need to  ensure that each depot is somehow identified in the 'SequenceGroupIds', to distinguish "Truck A" of depot1 from "Truck A of depot2.  For example: "N15TB Truck A", "N15TB Truck B", ...,  "EH104ER Truck A", "EH104ER Truck B", or perhaps   "Depot023 Truck A", "Depot023 Truck B", ...,  "Depot164 Truck A", "Depot164 Truck B".

Finally, having linked the shifts in this way, we can also properly control any range constraint on the vehicle, which would appear in the associated `VehicleProfile` object. 

<a name="compromised"></a>
## Compromised Shifts and Jobs
(currently only operational in [Fastmode](#fastmode))

A 'compromised' shift is one that has been part of the developing plan for some time (so, it has jobs on it), but it has suddenly become unavailable. E.g. the intended driver may have called in sick and there is no obvious immediate replacement, or the vehicle itself has just failed its MOT, and so on.  

The common, and perhaps obvious way for a vortex/EOS user to deal with this situation is simply to remove that vehicle from the ongoing session ( -- gracefully -- i.e. also removing it from driver and depot map arrrays). Typically this means that several jobs are suddenly unplanned.   What then happens is as follows:
 - if `PlanAllJobs` is `true`, then many jobs will suddenly be unplanned. Typically -- if the disrupting event happened when the plan was quite busy -- there will suddenly be significant levels of overtime and/or late penalties. Vortex/eos will do its best to mitigate the damage, but the end result may involve a number of shifts that will probably need to be modifed by the user (via cancelling selected jobs).
  - if 'PlanAllJobs' is `false` (default), then these jobs will simply shift to the 'unplanned' list, and vortex/Eos will do its best to see if it can plan them without incurring any infeasibilies.

Although it can be used in both scenarios, the idea of the Compromised Shifts facility is to provide a better way to deal with the `PlanAllJobs: true` scenario above.TThe challenge  is that there can be high levels of overtime/lates, which by itself leads to knock-on effects, including complications when it comes time to finalise the shift plans and determine which jobs should be cancelled.

To use the facility, instead of removing a shift, the user simply adds `CompromisedDays:[1]` as an attribute of the compromised vehicle. (Note that the other aspects (VehicleDriver and VehicleDepot maps, and the remaining attributes of this vehicle, should be left unchanged). This has the following effect.

 1. The `Compromised`  shift remains in the output plans -- its output is the same as the output of an empty shift, with an addition described next.
 2. The 'Compromised' shift's output will contain a list called `CompromisedJobs`. This is a list of the job `IdString`s that were on that vehicle, and have not yet been moved to uncompromised vehicles. I.e. it is basically a list of unplanned jobs that were originally on this shift.
 3. While planning, vortex will do its best to move jobs from the `CompromisedJob` list(s) to other shifts, without introducing penalties. 

In short, this means that the 'CompromisedJob' list(s) will be dealt with more cleanly, without the disruptive effects of a 'penalty storm', becoming de facto lists of jobs that need to be cancelled. Of course, in some cases, these lists may end up empty.

<a name="locations"></a>
## Locations and Distance Matrices

Each vortex algorithm will need an array of Locations and an array of DistanceMatrices in its input. These may be added locally after the run command is executed.

The locations array gives the geohash, latitude and longitude for each location involved in the problem (in the depot, vehicle and jobs arrays, and maybe other arrays too). 

> The locations array looks like this:

```json
"Locations": [
  {
    "Latitude": 51.51276,
    "LocationGeohash": "gcptncte",
    "Longitude": -1.0475999
  },
  {
    "Latitude": 53.49178,
    "LocationGeohash": "gcw2k3bk",
    "Longitude": 0.455261394
  },
  {
    "Latitude": 51.43853,
    "LocationGeohash": "u10hts4g",
    "Longitude": -1.305042375
  },
  {
    "Latitude": 50.43063,
    "LocationGeohash": "gbvnfktg",
    "Longitude": 0.023553
  }
]

Each DistanceMatrix has four parts: (i) its index (implicit -- e.g. the first matrix is 0, second is 1, etc...) (ii) its time window, (iii) an array of vehicle types, and (iv) the matrix itself.

The first distance matrix in the array (index 0) is always the 'default' matrix, to be used by every vehicle type. The vehicle array for this matrix will always contain every vehicle type defined in the problem, and the time window will always be the whole day (e.g. from 0 to 86400). Also, this matrix will contain information for the complete set of distinct location-pairs. (distances between a location and itself are always zero and will generally be omitted to save payload).

Every other matrix indicates an over-ride to the distance information for a subset of location-pairs, relevant to a given time window and a given set of vehicle types.

For example, suppose a problem has two vehicle types: "StandardVan", "TallVan" and "Bicycle". The second DistanceMatrix might be relevant only to "TallVan", and the location-pairs part will contain alternative 'bridge-free' distance profiles for routes which, in the default matrix, pass under low bridges. The third matrix may relate to the "Bicycle", and will contain alternatives for all of the routes which, in the default matrix, used motorways.

Regarding matrix time windows: normally, every matrix will cover the whole day (with TimeWindow '[0 86400]'); however, there are cases where different routes need to be taken at different times of day for some (or all) vehicle types.

The actual matrix part is an array called "DistanceMetre"; each element of the array is of the form "[A, B, [D1, D2, D3, D4, D5]]", where A, B and all the Ds are integers. This indicates that the best route from the location with index A to the location with index B is a route that involves D1 metres on road type 1, D2 metres on road type 2, etc. The location indices are their positions in the provided Locations array (starting from zero), and the road types align with those used by the routing service.

To summarise and simplify: for any given vehicle type, say "TypeA", is distance matrix can be constructed from the default (first) matrix, and then by overriding elements of that matrix from any others which have "TypeA" in the vehicle list. The time matrix for "TypeA" can then be made by transforming the distance matrix using the "TypeA" VehicleProfile.

Here is an example DistanceMatrices array:


"DistanceMatrices": [{
      "VehicleTypeArray": ["HGV","TallVan","Bicycle"],
      "TimeFrom": "00:00:00",
      "TimeTo": "24:00:00",
      "DistancesMetre": [
        [0, 1, [52, 36, 162, 10, 63, 8, 0]], [0, 2, [52, 136, 62, 10, 63, 8, 0]], [0, 3, [52, 36, 62, 110, 63, 8, 0]],
        [1, 0, [36, 25, 236, 98, 45, 8, 0]], [1, 2, [36, 225, 36, 98, 45, 8, 0]], [1, 3, [52, 36, 62, 210, 63, 8, 0]],
        [2, 0, [36, 25, 336, 98, 45, 8, 0]], [2, 1, [36, 325, 36, 98, 45, 8, 0]], [2, 3, [52, 36, 62, 310, 63, 8, 0]],
        [3, 0, [36, 25, 436, 98, 45, 8, 0]], [3, 1, [36, 425, 36, 98, 45, 8, 0]], [3, 2, [52, 36, 62, 410, 63, 8, 0]],
      ]
    },
    {
      "VehicleType": "HGV",
      "TimeFrom": "06:00:00",
      "TimeTo": "18:00:00",
      "DistancesMeter": [
        [0, 1, [62, 36, 75, 130, 12, 8, 0]]
      ]
    },
    {
      "VehicleType": ["HGV","TallVan"],
      "TimeFrom": "00:00:00",
      "TimeTo": "24:00:00",
      "DistancesMeter": [
        [2, 0, [36, 25, 36, 98, 145, 8, 0]], [2, 1, [36, 125, 36, 98, 45, 8, 0]], [2, 3, [152, 36, 62, 10, 63, 8, 0]],
        [0, 2, [36, 25, 36, 98, 145, 8, 0]], [1, 2, [136, 25, 36, 98, 45, 8, 0]], [3, 2, [152, 36, 62, 10, 63, 8, 0]],
      ]
    }
  ] 

Benchmarking

'Benchmarking' refers to the use of vortex to evaluate a 'benchmark plan', i.e. one that has already been prepared. The idea is that vortex is provided with the full details of the plan, so that it can reproduce the same plan in its output (with no changes or additional optimizations), and thus provide the various KPIs for this plan, such as the mileage, driver hours, the extent to which deliveries were late, and so on. This might be a manual plan, or a plan output from different software. In order to do benchmarking, vortex obviously needs to be informed of exactly which jobs are done by which vehicles in the plan, on what day, and in which precise order. At first thought, it might seem that this can be done by making using of the Characteristics, JobConstraints, PossibleDays, and SequenceOrder facilities. However, the latter methods may not be sufficient to persuade vortex to reproduce the desired plan. This is simply because the desired plan may contain infeasibilities. For example, to plan the jobs on a vehicle in the specificed sequence may result in a shift that is a few minutes longer than the maximum length, and so vortex will leave the last job unplanned to make sure the maximum shift length is not violated. We find that such infeasibilities are very common in the benchmarking context.

In order to force jobs to be planned in a certain way for benchmarking purposes, whether or not there are infeasibilities, we therefore provide the following keys at the Job level: RunOrder, DepotRunOrder, Day and Vehicle.

For example, suppose the pre-existing plan is as follows: vehicle "JackVan" loads up at the depot on day 1 of the plan, and then delivers jobs "JobA", "JobB", and "JobC", in that order. Meanwhile, on day 2 of the plan, "JillVan" collects "JobP", "JobQ" and "JobR", in that order, and then unloads them at the depot. We can ensure that vortex will reproduce this exactly if we make use of RunOrder, Day and Vehicle within the Jobs array as follows:

  • in the object representing "JobA" (that is, the object in the job array with "Idstring":"JobA"), we set "Day":1, "Vehicle":"JackVan", "RunOrder":1;
  • in the object representing "JobB", we set "Day":1, "Vehicle":"JackVan", "RunOrder":2;
  • in the object representing "JobC", we set "Day":1, "Vehicle":"JackVan", "RunOrder":3; and
    • in the object representing "JobP", we set "Day":2, "Vehicle":"JillVan", "RunOrder":1;
  • in the object representing "JobQ", we set "Day":2, "Vehicle":"JillVan", "RunOrder":2;
  • in the object representing "JobR", we set "Day":2, "Vehicle":"JillVan", "RunOrder":3;

Note: instead of setting the RunOrders as 1, 2 and 3, we could have just as well set them as 267, 492, and 76549; all that matters as that the ordering of the numbers follows the required job sequence.

Depot Reloads and DepotRunORder

In the above example, we use `RunOrder' to impose a sequence on the 'customer-facing' parts of the jobs. However, the deliveries done by "JackVan" need to be loaded up at the depot first; meanwhile, the jobs done by "JillVan" are collections, and they need to be unloaded at the depot at the end of the shift. By setting only "Vehicle", "Day" and "RunOrder" for a Delivery job, we haven't actually specified when it is loaded up at the depot. Similarly, in setting "Vehicle", "Day" and "RunOrder" for a Collection job, we still leave open the question of when that job should be unloaded. In many circumstances, this turns out to be unimportant. If "SuppressDepotReload":true applies, then all loading is done at the start of the shift and all unloading is done at the end of the shift, and so there is no ambiguity. Even if depot reloads are allowed (with "SuppressDepotReload":false), in most cases vortex will experiment briefly with depot reloads (that is: additional depot loading and unloading somewhere in the middle of the shift) and find that leads to worse mileage and time, and the loads/reloads will consequently migrate to the start and end of the shift.

However, in some cases, the benchmark plan itself may contain depot visits partway through the shift. To handle this, and make sure that vortex reproduces the exact sequence, we can use the "DepotRunOrder" key. While "RunOrder" enables us to indicate the sequence position of the customer-facing part of a job, the "DepotRunOrder" enables us to specify the sequence position of the depot part.

For example, suppose the benchmark plan for vehicle "AliceVan" is like this: - start at depot: load jobs "JobP" and "JobQ" - deliver "JobP" - collect "JobR" - deliver "JobQ" - return to depot: unload "JobR" and load "JobS" and "JobT" - deliver "JobS" - deliver "JobT" - collect "JobU" - return to depot and unload "JobU"

To make sure vortex follows the same plan, we simply need to provide a suitable sequence number for each of the depot visits, and then make sure that the RunOrder values for the customer-facing visits fit around them.

Let's do that in two steps; first, arbitrary but suitable sequence numbers for the depot visits

  • start at depot: load jobs "JobP" and "JobQ" : 10
  • deliver "JobP"
  • collect "JobR"
  • deliver "JobQ"
  • return to depot: unload "JobR" and load "JobS" and "JobT" ; 20
  • deliver "JobS"
  • deliver "JobT"
  • collect "JobU"
  • return to depot and unload "JobU" : 30

Now, choose arbitrary but suitable runorder numbers for the customer facing visits:

  • start at depot: load jobs "JobP" and "JobQ" : 10
  • deliver "JobP" : 12
  • collect "JobR" : 14
  • deliver "JobQ" : 16
  • return to depot: unload "JobR" and load "JobS" and "JobT" ; 20
  • deliver "JobS" : 22
  • deliver "JobT" : 24
  • collect "JobU" : 26
  • return to depot and unload "JobU" : 30

Using the sequence numbers of the depot visits for "DepotRunOrder", and using the sequence numbers of the customer-facing visits for "RunOrder", we can now specify how to ensure that Vortex produces the correct plan:

  • in the "JobP" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":10 and "RunOrder":12
  • in the "JobQ" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":10 and "RunOrder":16
  • in the "JobR" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":20 and "RunOrder":14
  • in the "JobS" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":20 and "RunOrder":22
  • in the "JobT" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":20 and "RunOrder":24
  • in the "JobU" object, we set "Day":1, "Vehicle":"AliceVan", "DepotRunOrder":30 and "RunOrder":26

Remember that the precise numbers are quite arbitrary; they just need to be positive integers and in the correct order. In a similar vein, when several jobs are loaded and/or loaded in the same depot visit -- such as jobs "JobR", "JobS" nd "JobT" above -- it is convenient to give them all the same "DepotRunOrder". However this is not necessary. All that matters in this example is that the DepotRunOrder values for each of these jobs is larger than 16 and smaller than 22. Vortex will ignore the ordering of jobs that are in a single depot visit, although for capacity reasons it will assume that unloading happens before loading.


Using SequenceOrder

Sometimes there is a requirement for a certain group of deliveries (and/or collections) to be done by the same vehicle, and in a pre-determined sequence. For example, This might be due to the fleet manager's local knowledge concerning the best way to move from place to place in a certain village.

The appropriate way to express such a sequence requirement to Vortex is to use the job-level keys: SequenceOrder and SequenceGroupId. For example, suppose jobs "A", "B", "C", "D", and "E" need to be done by the same vehicle and in that order (from A to E) because we know that's what works best in that local region. Then, we would express this in vortex as follows (where 'in job "X"' means as one of the keys in the object in the Job array whose IdString is "X" ):

  • in job "A" set "SequenceOrder":1
  • in job "B" set "SequenceOrder":2
  • in job "C" set "SequenceOrder":3
  • in job "D" set "SequenceOrder":4
  • in job "E" set "SequenceOrder":5

If there is only have one such group of jobs having such a sequence requirment, then the above is all we need to do. However, if we have other such groups -- different groups of jobs that don't necessarily interact with each other -- then we will also need to set SequenceGroupId.

For example, suppose we have the above requirement in place for Jobs A--E; but at the same time, jobs "P", "Q", "R" and "S" also need to be done in that order, and by the same vehicle, however they are in a different region.

So, it is fine, for example, for the A-E and P-S groups to be done by different vehicles; and if they happen to be done by the same vehicle, then it is fine for the A-E group to be done before the P-S group, and equally fine for the P-S group to be done before the A-E group.

In this case we, would express the sequencing constraints for the two groups as follows:

  • in job "A" set "SequenceOrder":1, and "SequenceGroupId":"1stGroup";
  • in job "B" set "SequenceOrder":2, and "SequenceGroupId":"1stGroup";
  • in job "C" set "SequenceOrder":3, and "SequenceGroupId":"1stGroup";
  • in job "D" set "SequenceOrder":4, and "SequenceGroupId":"1stGroup";
  • in job "E" set "SequenceOrder":5, and "SequenceGroupId":"1stGroup";

  • in job "P" set "SequenceOrder":1, and "SequenceGroupId":"2ndGroup";

  • in job "Q" set "SequenceOrder":2, and "SequenceGroupId":"2ndGroup";

  • in job "R" set "SequenceOrder":3, and "SequenceGroupId":"2ndGroup";

  • in job "S" set "SequenceOrder":4, and "SequenceGroupId":"2ndGroup";

Notes

Note that there is no limit on the number of such sequence groups. Also, the SequenceGroupId is an arbitrary string, which just needs to be different for different groups. Finally, the numbers used in "SequenceOrder" are arbitrary, as long as they are positive integers in the correct order.

Expressing partial orderings with SequenceOrder

By setting the numbers appropriately SequenceOrder can be used to impose a more flexible constraint; for example consider the following group of settings for jobs with the same SequenceGroupId:

  • job "A", "SequenceOrder":3;
  • job "B", "SequenceOrder":3;
  • job "C", "SequenceOrder":6;
  • job "D", "SequenceOrder":8;
  • job "E", "SequenceOrder":8;
  • job "F", "SequenceOrder":8;
  • job "G", "SequenceOrder":20;
  • job "H", "SequenceOrder":25;

With this group, "A" and "B" can be in any order relative to each other, but must be before "C" (and hence before all the other jobs); meanwhile, "D, "E" and "F" can be in any order in relation to each other, but the group must come after job "C" and before job "G". So, for example, each of the following are valid sequences:

  • A, B, C, D, E, F, G, H
  • A, B, C, F, D, E, G, H
  • B, A, C, D, F, E, G, H

Other jobs and Unplanned jobs

Finally, two important points:

First, if there are jobs without SequenceOrder set (equivalently, any jobs with SequenceOrder:null), then they could be planned in any position and on any vehicle (if not barred by other constraints). For example, suppose the first sequence group above, for jobs A to E, has been imposed, meaning that any subset of jobs A to E that are planned must appear strictly in that order and on the same vehicle. Suppose also that jobs F, G and H are not in any sequence group. Then, any of the following are valid:

  • A, B, C, D, E, F, G, H
  • A, B, F, C, H, D, G, E
  • A, G, H, B, C, F, D, E

Second, just like any other job, there is no guarantee (under normal circumstances) that all of the jobs in any sequence group will be planned. However, whatever subset of jobs within a sequence group are planned, that subset will honour the SequenceOrder values.


Seeding the input

It is sometimes very useful to bootstrap a vortex run with the outcomes of a previous run invovling the same data. For example, suppose you ran vortex to handle a group of 500 deliveries, with a 30 minute time limit, and the end result was that 20 jobs were unplanned. Although we would normally advise to allow more time than this, this was nevertheless a quite efficient outcome within expectations, and you may then go forward with that plan, inform drivers and depot loading staff, etc. However, there are several alternative possible use-cases that might follow from this:

  • Allow more time: you might decided to try again with the same data, in hope of getting more jobs planned, this time running for 45 mins instead of 30 mins.
  • It turns out that additional vehicles/drivers have become available, so you might decided to try again -- i.e. another 30-minute run -- with updated input.
  • More orders have come in, and/or some other orders have been cancelled, so you want to run vortex again with the new data to get a valid plan.

The outcomes for each of these use-cases can be significantly improved (and/or accelerated), by using a 'seed'. The 'seed' is simply the "SolutionSet" obtained in the output of the original run (see RESTful V2 example for an example). If the input includes a "SolutionSet", complete and unchanged from how it appeared in the original output, then Vortex will start and improve from that point onwards. The SolutionSet does add any constraints (i.e. jobs can still change their ordering and move to other vehicles), it simply provides a starting point. If some of the input data is changed, the seed solutions are simply re-interpreted in the light of these changes. So, if a job named in the seed solution no longer exists, it will simply be ignored.

By using the original seed in each of the above use cases, we can take advantage of some of the work already done, and the outcomes will better and/or save time. In the first use case -- simply 'trying again for longer', instead of running from scratch for 45 minutes, seeding allows us to achieve the same result by running for only 15 minutes.

V2 JSON Response

import websocket

def on_message(ws, message):
    if message.Result == "SCHEDULES":
      # Update GUI
      pass
    elif message.Result == "STATS":
      # Do something else
      pass
    elif message.Result == "ERROR":
      # Handle error message
      pass

    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("# closed #")

def on_open(ws):
    ws.send('''{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[],"Locations":[{"LocationGeohash":"u10q4r59","Latitude":51.71837,"Longitude":-1.0475999},{"LocationGeohash":"gcp7b4v4","Latitude":51.29964,"Longitude":0.455261394}]}}''')


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}')


ws.onmessage = function (event) {
  var message = event.data;

  if (message.Result === "SCHEDULES") {
    // Update GUI
  } else if (message.Result === "STATS") {
    // Do something else
  } else if (message.Result === "ERROR") {
    // handle the error message
  }

  console.log(message);
}
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
> {"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":22,"CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}
...
< {"Result":"SCHEDULES","Data":{"SolutionSet":[{"Objectives":{"BalanceSecond":0,"CarbonKg":3.17571,"Cost":1181.12,"DistanceKm":317.571,"OvertimeSecond":0,"PlannedJobsNumber":1,"ShiftsNumber":1,"TotalTimeSecond":18030,"UnplannedJobsNumber":0,"Penalties":0},"Schedule":[{"Actions":[{"ActionType":"SHIFTSTART","Day":1,"StartLocationGeohash":"u10q4r59","StartLatLon":[51.7184,0.455418],"StartTime":"06:00:00","Drivers":["Driver_1"]},{"ActionType":"BRIEF","Day":1,"StartLocationGeohash":"u10q4r59","StartLatLon":[51.7184,0.455418],"StartTime":"06:00:00","EndTime":"06:15:00","Drivers":["Driver_1"]},{"ActionType":"LOAD_UNLOAD","Depot":"Depot_1","Day":1,"StartLocationGeohash":"u10q4r59","StartLatLon":[51.7184,0.455418],"StartTime":"06:15:00","EndTime":"06:21:00","Drivers":["Driver_1"],"Unload":[],"Load":["Job_1"],"OnBoard":[["Compartment1","Job_1","100",[20,21,22],[200,400,400]],["Overload",[0,0,0]]]},{"ActionType":"TRAVEL","Day":1,"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"gcp7b4v4","StartLatLon":[51.7184,0.455418],"EndLatLon":[51.2997,-1.04765],"StartTime":"06:21:00","EndTime":"08:31:00","DistanceKm":156.85,"Drivers":["Driver_1"]},{"ActionType":"DELIVER","Day":1,"IdString":"Job_1","StartLocationGeohash":"gcp7b4v4","StartLatLon":[51.2997,-1.04765],"StartTime":"08:31:00","EndTime":"08:47:00","OnBoard":[["Compartment1",[0,0,0],[200,400,400]],["Overload",[0,0,0]]],"Drivers":["Driver_1"]},{"ActionType":"TRAVEL","Day":1,"StartLocationGeohash":"gcp7b4v4","EndLocationGeohash":"u10q4r59","StartLatLon":[51.2997,-1.04765],"EndLatLon":[51.7184,0.455418],"StartTime":"08:47:00","EndTime":"11:00:00","DistanceKm":160.721,"Drivers":["Driver_1"]},{"ActionType":"SHIFTEND","Day":1,"StartLocationGeohash":"u10q4r59","StartLatLon":[51.7184,0.455418],"StartTime":"11:00:00","Drivers":["Driver_1"]}],"VehicleId":"Vehicle_1"}],"UnplannedJobs":[]}],"ExecutionTimeSecond":99},"TimestampUnix":1553243894,"ScheduleId":"d6ac016787d057e8d04d27204500aa0ac55476826cfbaf3962ff7dc33d1d003a","Finished":true}

Schedule result has the following JSON format

{
  "Result": "SCHEDULES",
  "Data": {
             "SolutionSet": [{
                                "Objectives":   {
                                        "BalanceSecond":        0,
                                        "CarbonKg":     3.98804,
                                        "Cost": 1451.3139999999999,
                                        "DistanceKm":   398.804,
                                        "OvertimeSecond":       1800,
                                        "PlannedJobsNumber":    1,
                                        "ShiftsNumber": 1,
                                        "TotalTimeSecond":      23670,
                                        "UnplannedJobsNumber":  1
                                },
                                "Schedule":     [{
                                                "Actions":      [{
                                                                "ActionType":   "SHIFTSTART",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "u10q4r59",
                                                                "StartLatLon":  [51.71837, -1.0475999],
                                                                "StartTime":    "06:00:00",
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "BRIEF",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "u10q4r59",
                                                                "StartLatLon":  [51.71837, -1.0475999],
                                                                "StartTime":    "06:00:00",
                                                                "EndTime":      "06:15:00",
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "LOAD_UNLOAD",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "u10q4r59",
                                                                "StartLatLon":  [51.71837, -1.0475999],
                                                                "StartTime":    "06:15:00",
                                                                "EndTime":      "06:21:00",
                                                                "Drivers":      ["Driver_1"],
                                                                "Unload":       [],
                                                                "Load": ["Job_1"]
                                                        }, {
                                                                "ActionType":   "TRAVEL",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "u10q4r59",
                                                                "EndLocationGeohash":   "gcp7b4v4",
                                                                "StartLatLon":  [51.71837, -1.0475999],
                                                                "EndLatLon":    [51.29964, 0.455261394],
                                                                "StartTime":    "06:21:00",
                                                                "EndTime":      "08:57:00",
                                                                "DistanceKm":   199.402,
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "DELIVER",
                                                                "Day":  1,
                                                                "IdString":     "Job_1",
                                                                "StartLocationGeohash": "gcp7b4v4",
                                                                "StartLatLon":  [51.29964, 0.455261394],
                                                                "StartTime":    "08:57:00",
                                                                "EndTime":      "09:13:00",
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "TRAVEL",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "gcp7b4v4",
                                                                "EndLocationGeohash":   "u10q4r59",
                                                                "StartLatLon":  [51.29964, 0.455261394],
                                                                "EndLatLon":    [51.71837, -1.0475999],
                                                                "StartTime":    "09:13:00",
                                                                "EndTime":      "12:34:00",
                                                                "DistanceKm":   199.402,
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "BREAK",
                                                                "Day":  1,
                                                                "StartTime":    "11:07:00",
                                                                "EndTime":      "11:52:00",
                                                                "Drivers":      ["Driver_1"]
                                                        }, {
                                                                "ActionType":   "SHIFTEND",
                                                                "Day":  1,
                                                                "StartLocationGeohash": "u10q4r59",
                                                                "StartLatLon":  [51.71837, -1.0475999],
                                                                "StartTime":    "12:34:00",
                                                                "Drivers":      ["Driver_1"]
                                                        }],
                                                "VehicleId":    "Vehicle_1"
                                        }],
                                "UnplannedJobs":        []
                        }],
                "ExecutionTimeSecond":  60
        }
  }

Statistics result has the following format

{
  "Result": "STATS",
  "Data": {
    "BestBalanceImprovement": 0,
    "BestBalanceSolution": 0,
    "BestCarbonImprovement": 0,
    "BestCarbonSolution": 4.0929,
    "BestCostImprovement": 0,
    "BestCostSolution": 1476.8252,
    "BestFeasibilityImprovement": 0,
    "BestFeasibilitySolution": 0,
    "BestDistanceImprovement": 0,
    "BestDistanceSolution": 409.292,
    "BestOvertimeImprovement": 0,
    "BestOvertimeSolution": 0,
    "BestProductivityImprovement": 0,
    "BestProductivitySolution": 1,
    "BestVehiclesImprovement": 0,
    "BestVehiclesSolution": 1,
    "BestTimeImprovement": 0,
    "BestTimeSolution": 190.7092,
    "EtaMinutes": 3.4997,
    "ImprovementsFreqPerMin": 0,
    "NumberOfSolutions": 1,
    "ProgressPercentageEstimate": 12,
    "SolutionsFreqPerMin": 0
  }
}

Note result has the following format

{
  "Result": "NOTE",
  "Data": {
    "Message": "Distance matrices claculation is complete after: 1 second(s)"
  }
}

The results represents a set of best schedules at the time of sending. During the execution of the algorithm, best results so far are sent back to the client. These schedules will be optimized further and the client is updated on the regular basis. Eventually, when there is no new improvements to be done, the algorithm finally terminates.

Data Key/value pairs associated with Schedule result

Key Description
SolutionSet An array of alternative solutions for the given routing problem, for more info please check Solution Object

Solution Object Key/Value pairs

Key Description
Objectives An object defining the characteristics and performance indicators of the plan, e.g. cost, time, carbon emissions etc. For more info please check Objectives Object.
Schedule An array of vehicle-schedules, containing a schedule of activities (perhaps covering several days) for each vehicle used in the plan. For more info please check Vehicle plan output.
UnplannedJobs Array of job IdStrings which are not planned in this Solution.

Objectives Key/Value pairs

Key Description
BalanceSecond This is only shown if the Balance objective is active, or if ShowBalance is true (see additional Key/Value pairs in V2 json request). This is the 'Seconds' version of BalanceMinutes.
BalanceMinutes This is only shown if the Balance objective is active, or if ShowBalance is true (see additional Key/Value pairs in V2 json request). Each vehicle used in the schedule operates for a certain amount of time. E.g. suppose there are 3 vehicles, which operate for 680 minutes, 720 minutes, and 770 minutes respectively. BalanceMinute is the difference between the largest and smallest amounts of time. In this example it would be 90 minutes. So, low values of Balance indicate equal distribution of the work across the vehicles.
BalanceValue This is only shown if the Balance objective is active (see additional Key/Value pairs in V2 json request). Usually, this is a coarser-grained version of the BalanceMinutes value, depending on the setting for BalanceGrainTime (see additional Key/Value pairs in V2 json request).
CarbonKg Total carbon emission in Kg.
Cost Total cost of the plan.
DistanceKm Total distance travelled.
OvertimeSecond Amount of overtime duration in seconds.
Penalties If this is more than 0, then for some reason(s) the plan is infeasible. This will only happen as a result of user constraints or pre-assignments.
PlannedJobsNumber Number of jobs that are planned
PlanProfile this is included only if it is a multi-day problem and the top-level key "PlanProfile" is set to "Frontload", "Backload" or "Display". It shows the job distribution as an array. E.g. "PlanProfile":[7, 0, 2] indicates that 7 jobs are done on day 1, none on day 2, and 2 on day 3.
ShiftsNumber Number of vehicle shifts used to complete the jobs (e.g. 2 vehicles each used for 3 days totals six shifts).
TotalTimeSecond Total amount of time in seconds needed for the whole plan.
UnplannedJobsNumber Number of unplanned jobs

Vehicle plan output

Key Description
Actions An ordered list of actions done by the vehicle. For more info about Action Item please refer to Action Key/Value pairs.
Capacities Only present if OutputPayloadQuantities is true. This is the array of quantities that constitute the capacity for the frist Stocktype of of the first compartment of this vehicle.
Drivers Only present if the AdditionalDriverOutput array is populated. This is an array of driver objects (normally just one object), containing selected information about the driver that is convenient for the user to consume from the output.
Vehicle Only present if the AdditionalVehicleOutput array is populated. This is an object containing selected information about the vehicle that is convenient for the user to consume from the output.
VehicleId The IdString of the Vehicle that is doing the actions.

Action Key/Value pairs

Key Description
ActionType The type of the action which can be one of the following: SHIFTSTART, BRIEF, DEBRIEF, WAIT, BREAK, CHARGE, TRAVEL, TRAVEL_PART,LOAD_UNLOAD, DELIVER, COLLECT, SHIFTEND, OVERNIGHT, VISIT.
DistanceKm The distance in Km travelled during this action.
Day The Day number of the action.
Drivers An array containing the IdString of the driver involved in this action.
Load An array containing the IdStrings of any jobs loaded onto the vehicle from a depot.
UnLoad An array containing the IdStrings of any jobs unloaded from the vehicle into a depot.
OnBoard A structure indicating the jobs that are on the vehicle at the end of this action, with details of how those jobs are distributed across the vehicle's compartments.
EndDay By default this is only used for the OVERNIGHT action type; this indicates the day number on completion of the overnight; usually it will be Day + 1, but not always -- e.g. someone who works a late shift may finish at 03:00, and start work again at 19:00 on the same day. If OutputEndDay is set to true, then `EndDay" will also be provided for any action which starts and finishes on different days -- e.g. a travel action that starts at 23:55:00 and finishes the next day at 00:15:32.
IncludesAdditionalDriverTime An amount of time, only present if the driver involved in this action has AdditionalDoorstepTime set.
IncludesAdditionalVehicleTime An amount of time, only present if the vehicle involved in this action has AdditionalDoorstepTime set.
Payload An array of quantities, indicating what is on the first compartment of the vehicle immediately after the action - appears only if OutputPayloadQuantities is true.
Quantities An array of quantities, indicating the quantities of the job that has just been collected or delivered by the action; this appears only if OutputPayloadQuantities is true.
StartLocationGeohash The start location geohash when the action starts.
EndLocationGeohash The end location geohash after doing the action.
OriginGeohash This will appear in a TRAVEL_PART action in place of StartLocationGeohash, in the case where the action starts after the beginning of the travel, and so does not have a specified start location.
DestinationGeohash This will appear in a TRAVEL_PART action in place of EndLocationGeohash, in the case where the action ends before the end of the travel, and so does not have a specified end location.
StartLatLon an array of two numbers: the Latitude and Longitude of the start location of the action.
OriginLatLon This will appear in a TRAVEL_PART action in place of StartLatLon, in the case where the action starts after the beginning of the travel, and so does not have a specified start location.
EndLatLon an array of two numbers: the Latitude and Longitude of the end location of the action.
DestinationLatLon This will appear in a TRAVEL_PART action in place of EndLatLon, in the case where the action ends before the end of the travel, and so does not have a specified end location.
StartTime The start time of the action on the correspondingDay.
EndTime The end time of the action on the corresponding Day.
JobId The 'IdString' of the job involved; this is only present in COLLECT, DELIVER and VISIT actions.

Depending on the ActionType, the other key/value pairs present in the action will vary as in the following table. In general, when there is a start location but no end location, the action stays (starts and ends) at the start location. When there is a start time but no end time, this means that the action has no duration; it simply indicates a point in time.

ActionType Contents of the action
SHIFTSTART Day, StartLocationGeohash, StartLatLon, StartTime,Drivers
BRIEF Day, StartLocationGeohash, StartTime, StartLatLon,EndTime,Drivers
DEBRIEF Day, StartLocationGeohash, StartTime, StartLatLon, EndTime,Drivers
WAIT Day, StartLocationGeohash, StartTime, StartLatLon, EndTime,Drivers
BREAK Day, StartTime, EndTime,Drivers - see notes below about BREAK actions.
TRAVEL Day, StartLocationGeohash, EndLocationGeohash, StartTime, StartLatLon, EndTime, EndLatLon, DistanceKm,Drivers
TRAVEL_PART Day, StartLocationGeohash, OriginGeohash,EndLocationGeohash, DestinationGeohash,StartTime, StartLatLon, OriginLatLon,EndTime,EndLatLon, DestinationLatLon, DistanceKm,Drivers
CHARGE Day, StartLocationGeohash, StartTime, StartLatLon, EndTime,Drivers
DELIVER Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers,Payload,Quantities
COLLECT Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers,Payload,Quantities
VISIT Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers,Payload,Quantities
LOAD_UNLOAD Day, StartLocationGeohash, StartLatLon, Load (an array of Job IdString strings), Unload (an array of Job IdString strings), StartTime, EndTime, Drivers, OnBoard, Payload
OVERNIGHT Day, StartLocationGeohash, StartTime, StartLatLon, EndDay,EndTime,Drivers
SHIFTEND Day, StartLocationGeohash, StartTime, StartLatLon, Drivers

In the above, (i) the IdString field is always the IdString from the Job object; note that the action will additionally contain CustomerIdString whenever a job provided with that in the input, this will additionally be (ii) Drivers is an array of Driver IdStrings. Usually there will be only one driver in the array.

Note also that, in addition to the fields listed, COLLECT, DELIVER and VISIT actions may also contain an arbitrary collection of key/value pairs from the job object associated with the action. This will be the case when the user has populated the AdditionalServiceActionOutput array - see Output Control).

About BREAK actions in Default BreaksMode

In the ordered list of actions in an Actions array, a BREAK will only ever appear in one of two contexts: (i) immediately following a TRAVEL action, or (ii) immediately following another BREAK action. To put that another way, the Actions array may contain a number of groups of multiple BREAKs; the first BREAK in each of this groups will always appear immediately following a TRAVEL action. This group of BREAKs indicates breaks that are scheduled to take place during and/or immediately after the associated TRAVEL action. The BREAK action itself indicates the start and stop time of the break; in general the location is unknown, because it will happen en route between the start and end locations of the assocaited TRAVEL action. The last (or only) BREAK in such a group might occur in time immediately after the TRAVEL action, in which case it takes place at the end location of the TRAVEL action. An example of a group of breaks following travel might be:

  • TRAVEL from 11:00 location A to 18:30 location B
  • BREAK from 12:00 to 12:45
  • BREAK from 17:15 to 18:00

In the above case, first there is travel starting at location A, from 11:00 to 12:00, and then the driver stops to take a break; then further travel from 12:45 to 17:15, followed by another break; finally travel from 18:00 to 18:30, arriving at location B.

  • TRAVEL from 11:00 location A to 17:30 location B
  • BREAK from 13:00 to 13:30
  • BREAK from 17:30 to 18:00

In the above case, first there is travel starting at A, from 11:00 to 13:00, and then the driver stops to take a break; then further travel from 13:30 to 17:30, arriving at B. This is immediately followed by a break at B from 17:30 to 18:00, arriving at location B.

About LOAD_UNLOAD actions

These may appear at the beginning of a shift, when the vehicle loads up with any deliveries that it has to make, and/or towards the end of a shift, when the vehicle unloads any collections that it made. It may also appear any number of times within a shift, in which case it may involve both loading and unloading.

here is a mid-shift example:

              {
                "ActionType": "LOAD_UNLOAD",
                "Day": 1,
                "EndTime": "19:32:00",
                "Load":["Job_1","Job_4","Job_7"],
                "StartLatLon": [51.148972, 1.5488821],
                "StartLocationGeohash": "gcv0d8yr",
                "StartTime": "19:22:00",
                "Unload":["Job_3","Job_5"]
              },

In the above case, the vehicle must be at a depot, and it will first unload the jobs whose unique IdStrings are "Job_3" and "Job_5", and then it will load the jobs whose unique IdStringss are "Job_1", "Job_4" and "Job_7".

About Multiple Actions at the same customer site

In some circumstances, there will be a series of more than one collections,and/or deliveries and /or visits with no intervening travel. This represents multiple deliveries (for example) to the same customer, and at the same customer site. By default, vortex provides only outline information for such a series.

              {
                "ActionType":   "DELIVER",
                "Day":  1,
                "IdString":     "Job123",
                "StartTime":    "08:00:00",
                "EndTime":      "09:16:00",
                //  etc.
              }, {
                "ActionType":   "COLLECT",
                "Day":  1,
                "IdString":     "Job234",
                "StartTime":    "08:00:00",
                "EndTime":      "09:16:00",
                //  etc.
             }. {
                "ActionType":   "COLLECT",
                "Day":  1,
                "IdString":     "Job678",
                "StartTime":    "08:00:00",
                "EndTime":      "09:16:00",
                //  etc.
            }

In the above, for example, we see a string of three activities at the same customer site, with no intervening travel. The visit to this site starts at 08:00 and ends at 09:16, and all the activties get done in that time-frame. Tthis is reflected in the StartTime and EndTime for each task. Thus, with default settings, when multiple activities are scheduled together at the same site, vortex only provides an overall time frame for the site visit, but does not provide task-specific timing. However, in some situations the user may wish to see the detailed task-specific timing; this might be particularly useful when the jobs have varied service windows, and varied fixed visit and service visit durations; also, the overall time frame may include waiting time, which won't be visible in the default output. To see these details the user can set OutputMultipleServiceDetails to true (see Output Control). In that case, extra task-specific timing and waiting time between activities is provided as, illustrated below:

              {
                "ActionType":   "DELIVER",
                "Day":  1,
                "IdString":     "Job123",
                "StartTime":    "08:00:00",
                "EndTime":      "08:15:00",
                //  etc.
              }, {
                "ActionType":   "COLLECT",
                "Day":  1,
                "IdString":     "Job234",
                "StartTime":    "08:15:00",
                "EndTime":      "08:26:00",
                //  etc.
              }, {
                "ActionType":   "WAIT",
                "Day":  1,
                "StartTime":    "08:26:00",
                "EndTime":      "09:00:00",
                //  etc.
              }, {
                "ActionType":   "COLLECT",
                "Day":  1,
                "IdString":     "Job678",
                "StartTime":    "09:00:00",
                "EndTime":      "09:16:00",
                //  etc.
            }

Now we can see the key details within the overal 08:00--09:16 timeframe. First, "Job123" is servied between 08:00 and 08:15; next, "Job234" is servied between 08:15 and 08:26. There is then a wait between 08:26 and 09:00 (which would mean that the service window for the next job doesn't open until 09:00), and we finally service "Job678" from 09:00 to 09:16.

Notice that, in such a group of jobs for the same customer served at the same site, the difference between FixedVisitDuration and ServiceVisitDuration comes into play. In particular: (i) the duration of the first job includes a fixed visit time, plus that job's service visit time; (ii) the duration of each subsequent job is only its ServiceVisitDuration. It is possible that the jobs in the group each have different FixedVisitDurations; if so, then the longest of these is the time used for FixedVisitDuration for the first job.

Final notes on Output

In the majority of cases, a job is either a collection that eventually gets unloaded at a depot, or a delivery that is initially loaded at a depot. So, usually we will see every job twice in the output: once as part of a LOAD_UNLOAD action, and once in either a DELIVER or COLLECT action. However, there are sone exceptions: - a 'courier'-style job will involve two jobs with different IdStrings, one being a Collection from one customer and the other being the Delivery of that package to another customer, so this will not involve any loading/unloading at a depot. - if a job is Unplanned, then we will only see it in the UnplannedJobs array.

Error Result

Error message example, command is not recognised.

from websocket import create_connection
ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY")

ws.send("{\"Command\":\"foo\",\"Data\":{}}")
print(ws.recv())
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");
ws.send('{"Command":"foo","Data":{}}');

ws.onmessage = function (event) {
  console.log(event.data);
}
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
> {"Command":"foo","Data":{}}
< {"Result":"ERROR","Data":{"code":400,"message":"Command FOO is not recognized, supported values are: RUN, TERMINATE"}}

Recieved message:

{
  "Result": "ERROR",
  "Data": {
    "code": 400,
    "message": "Command FOO is not recognized, supported values are: RUN, TERMINATE"
  }
}

Another example, validation error

from websocket import create_connection
ws = create_connection("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY")
ws.send('''{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":"kj","CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}''')

print(ws.recv())
var ws = new WebSocket("wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY");

ws.send('{"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":"kj","CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}')


ws.onmessage = function (event) {
  console.log(event.data);
}
wscat -c "wss://api-core01.trakm8.net:8443/bdservices/vortex/api/latest?apikey=APIKEY"
connected (press CTRL+C to quit)
> {"Command":"Run","Data":{"NumDays":1,"PlanDate":"17/12/2018","ContinueWithoutImprovementTime":"90s","TotalRunTime":"5m","Depots":[{"IdString":"Depot_1","TimeWindows":[{"Day":1,"Windows":[["06:00:00","20:00:00"]]}],"PossibleDays":[1],"LoadingTime":"00:00:30","LocationGeohash":"u10q4r59","IncludeDriverCharacteristics":[],"Attributes":["GoodsSupply","GoodsReceipt"],"Characteristics":[]}],"Drivers":[{"IdString":"Driver_1","BriefingTime":"00:15:00","DebriefingTime":"00:00:00","Characteristics":[],"CostPerDay":"kj","CostPerHour":3.5,"Availability":[{"Day":1,"Number":9}],"PossibleDays":[1],"MaximumOvertime":"00:30:00","OvernightRadiusKm":180,"OvernightCost":75,"OvertimeCostPerHour":7,"ShiftEarlyStartTime":"06:00:00","ShiftLateStartTime":"08:00:00"}],"VehicleDriverMap":[],"VehicleDepotMap":[{"VehicleId":"Vehicle_1","DepotId":"Depot_1"}],"Vehicles":[{"IdString":"Vehicle_1","DeliveriesFirst":false,"PossibleDays":[1],"CarbonPerKmKg":0.01,"CarryingWeightKg":1200,"CostPerDay":30,"CostPerDayofNonUse":20,"CostPerKm":3.5,"MaxLoadWeightKg":3000,"DepotLoadingTime":"5m","Capacities":[200,400,400],"Characteristics":[],"StartLocationGeohash":"u10q4r59","EndLocationGeohash":"u10q4r59","OpenStart":false,"OpenEnd":false,"MaximumLoadUnloadTime":"12:00:00","MaximumWorkingTime":"15:00:00","OperatingRadiusKm":1000000,"PreLoaded":false,"UnloadAfterShift":true,"SuppressDepotReload":false,"Type":"HGV","StartTime":"04:00:00"}],"VehicleProfiles":[{"Type":"HGV","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,20,20,20,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"TallVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[20,20,20,20,20,10,10],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}},{"Type":"MiniVan","SpeedProfiles":[{"SpeedsMetersPerSecond":[25,25,25,22,22,20,20],"TimeFrom":"00:00:00","TimeTo":"24:00:00"}],"ChargingParameters":{}}],"Jobs":[{"IdString":"Job_1","Service":"delivery","Vehicle":null,"RunOrder":null,"Day":null,"CanSplitOverCompartment":false,"PossibleDays":[1],"Quantities":[20,21,22],"Type":"stocktype_2","TimeWindows":[{"Day":1,"Windows":[["07:00:00","20:00:00"]]}],"FixedVisitDuration":"15:00","ServiceVisitDuration":"01:00","LoadTime":"1m","RevenueIncome":100,"Priority":1,"LocationGeohash":"gcp7b4v4"}],"JobConstraints":[]}}
< {"Result":"ERROR","Data":{"Message":"Driver: Driver_1 with invalid CostPerDay value, this should be a positive number","Code":400},"TimestampUnix":1553268013,"ScheduleId":"28add61701ebcac0993aea168ea0d7bb48849847f32c3971faed03c56f22823f"}

Recieved message:

{
  "Result": "ERROR",
  "Data": {
    "Message": "Driver: Driver_1 with invalid CostPerDay value, this should be a positive number",
    "Code": 400
  },
  "TimestampUnix": 1553268013,
  "ScheduleId": "28add61701ebcac0993aea168ea0d7bb48849847f32c3971faed03c56f22823f"
}

Errors might occur due to different reasons, error message Data has the following format:

Key Description
Message Defines the error message value
Code Determines the error code

Error Codes

Vortex uses the following error codes:

Error Code Meaning
400 Bad Request -- Your request is invalid.
404 Not Found -- Your requested schedule is not found (could be deleted).
424 Failed Dependency -- One or more Vortex dependencies have failed, usually the routing service.
500 Internal Server Error -- We had a problem with Vortex. Try again later.
501 Not implemented -- The requested functionality is not implemented yet.