NAV
shell python javascript

Elastic Optimisation Service (EOS)

In Greek mythology the Goddess Eos is literally the dawn. She is the titan which brings light everyday.

EOS provides both Slot booking and dynamic scheduling capabilities to extend the Vortex algorithm product. It provides the following key benefits:

The Vortex algorithm has many features and an extremely rich and expressive API:

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

EOS Dynamic Scheduler

EOS is a dynamic scheduler which provides automated schedule optimisation for a wide range of vehicle routing problems including deliveries, service, and collections.

This service provides incremental and background optimisation; it supports adding / deleting / updating jobs and slot booking. The optimised plan can be requested at any time to provide a very quick response.

This service is closely related to the Vortex optimisation algorithm. The service shares many data types with Vortex. Under the hood this service uses the Vortex algorithm.

The key features are:

A key feature of this service is that the schedule can be requested at any time. Optimisation continues automatically in the background until completed.

Note: Schedule plans may not necessarily be completed or optimal at the time you request the plan. The plan indicates if its finished or not. Its necessary to allow some time after adding the last job for the optimisation to complete.
The amount of time depends on the complexity of the problem which include the number of orders and the constraints.

Slot booking

Last mile delivery problems often require a near instant response for integration with a customer facing shopping basket. The shopping basket user will want to choose a home delivery time slot from a list of choices. The full list of choices need to be calculated based on availability of vehicles, drivers, spare capacity and geography. Presenting many delivery options to the customer requires an algorithm to quickly calculate all the possibilities. Full optimisation takes a long time; far too long for shopping basket integration where the response to the customer needs to be sub 500ms. To overcome this a slotting algorithm provides a very fast way to quickly establish the available time slots without the need for full optimisation.

A delivery slot is a wide time window constraint (e.g. 2-hour slot, 2pm to 4pm).

Job booking

Real-time job booking applications require jobs to be added throughout the day and schedules to be created when required without having to wait. This service supports the add hoc adding of jobs throughout the day while optimising in the background. The schedule can be requested at any time.

Multiple sessions

The API supports multiple sessions and uses a simple OPEN and CLOSE approach. Typically each depot would have its own session and a session may be kept open indefinitely as long as there is activity on the session. A single depot could have multiple sessions if there are multiple unrelated delivery problems to solve. This allows depots to be created, deleted, and even for small groups of depots to work together on a single scheduling problem - for example if you want to share vehicles between depots.

API Summary

The EOS Scheduler supports HTTPS Restful web services and uses the create, read, update, and delete (CRUD) verbs with a JSON structured payload. The API uses the Vortex algorithm objects. The notation VortexDriversObject means the Vortex JSON object for Drivers. The Vortex algorithm documentation can be used.

The general sequence for slot booking is as follows:

Sequence API Description
1 GET session Open a session for each depot. You should leave the session open indefinetely. It will return a sessionID which is required for all subsequent API calls relating to this session. The session will automatically timeout and close after idle [time]. Its acceptable to have a long idle time such as 24hrs. The purpose of the timeout is to automatically clean up old sessions. A session ideally would have a single depot, but may have multiple depots.
2 POST resources Add depot, vehicles and drivers into the session. The optimiser will use these resources for creating schedules. Don't add the same resource into multiple sessions.
3 POST slots Add slots for the next month(s) into the session. You can add slots for a specific day, or by pattern. When you add slots you specify the start and end time for the slot.
4 GET slots/available Call Get slots available given a specified date range for a job. Get slots available will calculate which slots have enough capacity and travel time for the given job. It returns a list of slots available with capacity details.
5 POST slots/book Book the selected slot with the given job.
6 Repeat sequence (4..5) to continue to do online booking.
7 Update resources Change driver, vehicle availability if necessary (e.g. driver is poorly, or vehicle service)
8 GET plan Get the plan for a specific date (e.g. tomorrow). Check to make sure the plan is finished - check for a HTTP 200 response. If you get a HTTP 202, then the algorithm is still processing and may need more time.

API Details

This section details the API paramters and discussed how to use them.

Session Parameters

Key Description
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.
OutputInterval Default:"60s". The time interval between interim outputs showing the latest collection of solutions.
StatisticsInterval Default:"5s". The time interval between interim outputs showing summary statistics of the run so far.
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".
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.
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.
WalkToNearbyJobs Default:false. In normal (default) circumstances, each job will be served one after the other in sequence, in the following way: the vehicle services job N, travels to job N+1, services job N+1, travels to job N+2, services job N+2, and so on. However in some cases, when job N+1 (and perhaps N+2 and beyond) are within walking distance pf job N. In that case, it might make better sense for the vehicle to stay parked at job N's location, and for jobs N+1, etc... to be serviced by the driver walking to those job locations, eventually walking back to the vehicle. Then, the vehicle travels to job N+3 (say) which is more than walking-distance away from job N. Setting WalkToNearbyJobs to 'true' will lead to this behaviour. The relevant walking speed and walking distance parameters can be set for each driver - see Driver Key/Value pairs.
MaxDailyJobsNumber Default:null. Optional: EOS can limit the max total jobs bookable or transferable, except for when force mode used. If an attempt is made to add a job when this limit is reached, the job will be rejected. If the session has more than one depot this limit applies to all depots in that session. This limit only applies to a single day. In addition, It is possible to customize this limit; So, different limits might be used for different days. Default: Null = no limit.

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.
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.
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.
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.
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.
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.

Resources

Key Description
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.
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.

Depot Key/Value pairs

Key Description
IdString Mandatory. A unique ID for the depot.
TimeWindows An array of time-window objects, each of which provides a list of one or more time windows for 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).
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
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:[].

Example TimeWindows array

TimeWindws example:

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

EOS only supports single-day optimisation problems. 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.).

Driver Key/Value pairs

Key Description
IdString Mandatory. A unique ID for the driver.
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.
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.
MaximumOvertime Default:"0m". Maximum overtime duration (beyond 'MaximumWorkingTime') which the driver can do in one shift.
OvertimeCostPerHour Default:0. Hourly cost for overtime.
ShiftEarlyStartTime Default:"06:00:00". The earliest time that the driver can start their day's work.
ShiftLateStartTime Default:"08:00:00". The latest time that the driver can start their day's work.
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.
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
MaximumWalkingRadiusMetres Default:30. If WalkToNearbyJobs is true, drivers will walk from the current job location to the next jobs in the sequence (at speed WalkingSpeedMetresPerSecond), as long as the driver doesn't stray beyond MaximumWalkingRadiusMetres metres of the vehicle's last stop.
WalkingSpeedMetresPerSecond Default:1.8. If WalkToNearbyJobs is true, drivers will walk from the current job location to the next jobs in the sequence at this speed, as long as the driver doesn't stray beyond MaximumWalkingRadiusMetres metres of the vehicle's last stop.

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.

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], "TimeFrom":"00:00:00", "TimeTo":"08:00:00"},
            { "SpeedsMetersPerSecond":[ 60, 55, 55, 45, 45, 30, 30], "TimeFrom":"08:00:00", "TimeTo":"24:00:00"}
           ],
         "ChargingParameters":{}
        },
        {"Type":"WideVan", 
         "TransportMode": "truck",
         "SpeedProfiles":[
            { "SpeedsMetersPerSecond":[ 50, 40, 40, 40, 0, 0, 0], "TimeFrom":"00:00:00", "TimeTo":"08:00:00"},
            { "SpeedsMetersPerSecond":[ 40, 30, 30, 30, 0, 0, 0], "TimeFrom":"08:00:00", "TimeTo":"24:00:00"}
           ],
         "ChargingParameters":{}
        },
        {"Type":"Skateboard",
         "TransportMode": "car",
         "SpeedProfiles":[
            { "SpeedsMetersPerSecond":[ 0, 0, 20, 20, 20, 20, 20], "TimeFrom":"00:00:00", "TimeTo":"24:00:00"}
           ],
         "ChargingParameters":{}
        }
     ]
Key Description
Type Mandatory. The type of the vehicle.
TransportMode **Default:"truck"*. The transport mode to be used in time/distance matrices. Supported values are: TRUCK, CAR.
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.
LengthMetre Default:0. The vehicle's length in meters.
HeightMetre Default:0. The vehicle's height in meters.
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.
SpeedProfiles Default:null. An array of 'SpeedProfile` objects.

Each object contains a speed profile (array of seven numbers indicating speeds in meters per second, allowing for seven road types), and its associated time window. See examples below. 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. This will normally (but not always) mean that the speeds are monotonically decreasing with the array index.

Vehicle Key/Value pairs

Key Description
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.
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).
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.
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.
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.
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.
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 ).
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.
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.
SuppressDepotReload Default:false. true or false value to define whether the vehicle is prevented from reloading during the shift time.
Type Default:null. A string identifying the type of the vehicle; this will link the vehicle to the VehicleProfiles array.
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).

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
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 need 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. Note: if "Vehicle" is set using this key, it will definitely be scheduled (on the specified vehicle, naturally), irrespective of any violations of constraints or capacities; assigning a vehicle via this method tells vortex: "schedule this job on this vehicle, come what may". If, rather than this, you simply want to indicate that "if this job is schedueld 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*null. The run order of the job in the route, if left undefined or zero it means that the job can have any sequence inside its route. Please note that this settings doesn't make sense if the Vehicle field is left undefined.
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.
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:[["00:00:00", "24:00:00"]]. 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.

Attribute Details

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:

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:

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

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:

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 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:

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.







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

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

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:

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

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"]}
                     ]

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:

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.

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.

EOS Plan

Schedule result has the following JSON format

{
  "EndDate": "2020-08-04",
  "RequestId": "4e085de4ba4def0f6c293a28f1e341a681d662f608428d84127db0dbc30ab3d5",
  "Result": "SCHEDULES",
  "Issues": [],
  "TimestampUnix": 1596465298,
  "StartDate": "2020-08-04",
  "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
  }
}

EOS plans can be obtained using:

RESTful API call enables end-user to retrieve current plans on-demand. On the other hand, the WebSocket endpoint provides a mechanism to obtain plan updates automatically. Thus, the end-user doesn't have to keep polling EOS.

EOS WebSocket requires the SessionID as a parameter. The WebSocket client will receive updates on session plans as they evolve. Alternatively, it is possible to configure the WebSocket with both a SessionID and a PlanDate. In this case, the WebSocket client will receive plan updates on the given date only.

Please refer to the Examples Get Plan section for more details on WebSockets.

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-schedule Objects.
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-schedule Key/Value pairs

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.
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, 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 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.
StartLocationGeohash The start location geohash when the action starts.
EndLocationGeohash The end location geohash after doing the action.
StartLatLon an array of two numbers: the Latitude and Longitude of the start location of the action.
EndLatLon an array of two numbers: the Latitude and Longitude of the end location of the action.
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
CHARGE Day, StartLocationGeohash, StartTime, StartLatLon, EndTime,Drivers
DELIVER Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers
COLLECT Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers
VISIT Day, StartLocationGeohash, IdString, StartTime, StartLatLon, EndTime,Drivers
LOAD_UNLOAD Day, StartLocationGeohash, StartLatLon, Load (an array of Job IdString strings), Unload (an array of Job IdString strings), StartTime, EndTime, Drivers, OnBoard
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.

About BREAK actions: 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:

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.

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".

NOTE: 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.