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:
- Significantly reduce last-mile delivery failures.
- Meet customers’ home delivery expectations efficiently.
- Reduce fleet costs and decrease harmful environmental emissions.
- Simple to use slot booking HTTP RESTful JSON API with low time to market.
- Flexible slots. For example: 4-hour slots or all day.
- Dynamic optimisation: add or remove deliveries, vehicles, drivers – optimisation continuously runs in the background.
The Vortex algorithm has many features and an extremely rich and expressive API:
- Regional optimisation where several stores collaborate together sharing delivery resources.
- Auto scaling on demand high performance cloud computing
- Scenario planning is supported as the API allows multiple sessions to be open in parallel to allow “sandbox” exploration and “what-if” planning.
- Map updated automatically
- Comprehensive constraint model for vehicles, drivers, depots and tasks to ensure business rules are met. For example working time, shift patterns, rest breaks, overtime, tacho rules, HSE rules and vehicle width, weight, height routing constraints.
- Grouped deliveries calculate individual driver walking distance and speed to increase efficiency.
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:
- Real time online optimisation
- Slot booking
- Dynamic scheduling
- Enterprise optimisation
- Parallel processing
- Incremental optimisation
- Auto scales up and down
- Multi-day slot booking – e.g. > 21 days, or 3 months for Christmas
- Fast response time < 0.5 seconds
- Support same day delivery
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 Visit s, 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 Driver s' Availability constraints, and, if any, the Vehicle s' 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:OrganizeAsLoads is 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 Runorder is 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. IfFirstDrop if 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:
- Cost £1,000, Distance 10,000 km
- Cost £1,010, Distance 9,000 km
- Cost £1,100, Distance 8,800 km
- Cost £1,400, Distance 8,700 km
- 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:
1×BalanceMinutes
+ 10×Cost
+ 2.5×DistanceKm
+ 1×CarbonKg
+ 0.1×OvertimeSecond
+ 5×ShiftsNumber
+ 1×TotalTimeSecond
If there is one key objective of interest, but some concern to tradeoff appropriately against one or two others, then using FinalOutputWeights
is recommended.
However, if you need to carefully consider the many tradeoffs between several different objectives, you may want to do modelling with spreadsheets under various scenarios, do various experiments with vortex, etc, before deciding on a set of weights. In such circumstances it may be wiser simply not to use FinalOutputWeights
at all, and simply obtain the full set of solutions each time and inspect them before making a choice.
Time Strings Format
Many of the values in the json input objects are time values, Often these are specific times (e.g. when a driver can start in the morning), and often these are durations of time (e.g. the maximum working time allowed in a shift). Sometimes time values are standalone, and sometimes they are elements of arrays. In all cases the format rules are the same, and is as follows:
For example, to express a default
MaximumWorkingTime
for a driver of 11 hours, you can say:
...
"MaximumWorkingTime": "11h",
...
However if readability is less important to you than, for example, consistency, then any of the following are fine instead:
...
"MaximumWorkingTime": "660m"
"MaximumWorkingTime": "39600s"
"MaximumWorkingTime": "11:00:00"
"MaximumWorkingTime": "00 11:00:00"
...
A time string must be in one of the following formats:
"SS"
; e.g."30"
(meaning 30 seconds, or 30 seconds past midnight),"00"
(meaning zero duration),"01"
(meaning 1 second duration, or 1 second past midnight)."MM:SS"
; e.g."00:30"
(meaning 30 seconds, or 30 seconds past midnight),"01:50"
(meaning 110 seconds),"59:00"
(meaning 59 minutes, or 1 minute before 01:00 AM)."HH:MM:SS"
; e.g."00:30:00"
(30 minutes, or half past midnight),"01:50:00"
(110 minutes, or ten to two in the morning),"23:10:00"
(23 hours and 10 minutes, or 10 past 11 pm) , or"00:23:10"
(23 minutes and 10 seconds, or 23 minutes and 10 seconds past midnight)."DD MM:HH:SS"
; e,g"00 00:00:30"
(30 seconds),"00 00:30:00"
(30 minutes),"01 01:00:21"
(25 hours and 21 seconds)."NNN...s"
; e.g."1s"
means 1 second,"2001s"
means 2001 seconds;"NNN...m"
; e.g."1m"
means 1 minute,"100m"
means 100 minutes;"NNN...h"
; e.g."1h"
means 1 hour,"60h"
means 60 hours;"NNN...d"
; e.g."1d"
means 1 day,"365d"
means 365 days.
Sometimes there is a need to express times that start the day before. For example, drivers may have shifts that begin just before midnight, but where most of the work is concentrated in the early hours of the next day. In such a case, you would naturally set the "PlanDate"
to be the date where most of the work happens, but the start times for the drivers and vehicles, and the opening times of the depots, would actually be the previous day. To cover scenarios like this, you can use "-" in front of a time, as follows.
If "<time>" is a valid time string in any of the above formats, then "-<time>" is also a valid time string. The minus sign indicates counting back from midnight at the very start of the plan date. For example:
json
"ShiftEarlyStartTime":"-01:00:00"
"ShiftEarlyStartTime":"-60m"
"ShiftEarlyStartTime":"-1h"
each of the above indicates that this driver's shift can start one hour before midnight, i.e. at 11pm, on the previous day.
Finally, the following are also examples of allowed formats:
"+\<time>\>"
, where "<time>\" is any valid time that does not already start with a "+" or "-" ; e.g."+1h"
means 1 hour,"+12:05:00"
means "12:05:00", etc.`more than 24 hours; e.g. "26:05:00" means 5 past 2am tomorrow; "60:00:00" means noon the day after tomorrow.
You can freely use any mixture of these formats, choosing whichever is most readable and convenient in context.
Capacities, Quantities, and Stock
Objects available
Most of the time, the algorithm will be used to schedule the movements of vehicles that do the following:
- have goods loaded onto them at a depot, and then transport those goods to deliver them to customers;
- or, travel to various customer sites to collect goods, and then unload those goods at a depot.
So, the vehicles are generally carrying goods around, and we might need to know information about the sizes of those items, to make sure the vehicle has enough capacity to carry them. To provide this information, Job
, Vehicle
and Depot
objects have associated attributes as follows:
In a
Job
object, there is aQuantities
array, which can be populated with information about the size of that job. For example,"Quantities": [22,3]
might indicate that this job comprises 3 packages, weighing in total 22Kg.In a
Vehicle
object, aCapacities
array is available, to indicate its maximum capacity for jobs. So, for example,"Capacities": [500,50]
might indicate that the vehicle can carry at most 500 kg, and a maximum of 50 crates.In more complex cases, the
Capacities
object in aVehicle
may instead be an array of objects, indicating capacities for different types of stock.In even more complex cases, the vehicle will not have a
Capacities
object; instead it will have aCompartments
array, providing information about the capacities of different compartments on the vehicle.meanwhile,
Depot
objects have anAttributes
list available, which can contain "GoodsSupply", meaning it has enough stock to supply all the jobs, and/or "GoodsReceipt", indicating that it has enough capacity to receive all the collections.finally, for more complex situations, a
Depot
object may, instead of anAttributes
list, have aStock
object, which provides information about the depot's levels of, and storage capacities for, one or more different types of stock.
In the rest of this section, we explain when the above objects are needed, and how they are used.
Goods Scenarios
Sometimes, the algorithm doesn't need to know anything else about the goods, apart from where they are, and where they have to be. For example, a vehicle may have more than enough capacity to carry all of the jobs that it could possibly Collect and/or Deliver in a typical shift.
Often, however, the weight or volume (or other quantities) of the goods are an important aspect for the algorithm to consider. For example, we may be delivering around 50 washing machines per day, but each of our vehicles can only carry a maximum of 5 washing machines at a time.
Moreover, there may be different types of goods, and the algorithm may need to have information about the vehicle's capacity for each type individually. E.g. a vehicle may be able to carry either 5 washing machines, or 100 microwaves. Sometimes we could handle this by considering the weights and/or volumes of the items. But sometimes there are other factors that come into play; for example, if we only consider volume, we might calculate that we could fit 10 washing machines on the vehicle - however the limitation of 5 comes from the requirements for limitations on stacking and securing them in transit.
So, when it comes to capacities and quantities, there are three broad kinds of cases. In the following we consider each of these in turn, and explain how they can be dealt with in the Job
, Vehicle
and Depot
objects.
When Quantities Don't Matter
Below indicates an example where there are three depots; the first can handle both Collection and Delivery jobs, the second can only handle Delivery jobs, and third can only handle Collection jobs.
"Depots": [
{ "IdString": "MainDepot",
...
"Attributes": ["GoodsSupply","GoodsReceipt"],
...
},
{ "IdString": "SupplyCentre1",
...
"Attributes": ["GoodsSupply"],
...
},
{ "IdString": "Warehouse22",
...
"Attributes": ["GoodsReceipt"],
...
},
...
],
...
This is the case when a vehicle may have more than enough capacity to carry all of the jobs that it could possibly Collect and/or Deliver in a typical shift. We don't need to know any details about the size of the goods being carried, because we know that the vehicle will always have enough spare capacity to carry it. This is also the case when all of the jobs are Visit
s (e.g. for maintenance tasks), which don't involve carrying goods at all.
In this scenario, no information needs to be provided; the Quantities
array can be omitted from the Job
objects, and the Capacities
array can be omitted from the Vehicle
objects. The Depot
objects do need to have the Attributes
list appropriately populated, but no other information is required. Behind the scenes, the algorithm will assume that there is a single quantity value which is zero in all cases.
So, to sum up, in this scenario there is only one place in the input where anything concerning goods needs to be mentioned: if any of the jobs are Deliveries, then at least one depot needs to have the "GoodsSupply" attribute set, indicating that jobs can be loaded onto vehicles at that depot. If there are any Collection jobs, then at least one depot needs to have the "GoodsReceipt" attribute set.
A single type of stock
The quantities of jobs and the capacities of vehicles could then be expressed like this:
"Jobs": [
{ "IdString": "Smith's Nail Shop",
...
"Quantities": [2],
...
},
{ "IdString": "Jones' Cement Emporium",
...
"Quantities": [8],
...
}, ...
...
],
"Vehicles": [
{ "IdString": "Truck ABC123",
...
"Capacities": [150],
...
},
{ "IdString": "SmallVan22",
...
"Capacities": [40],
...
},
When quantities matter -- usually this is because the vehicle capacities limit the jobs that can be carried at one time -- it is very often the case that all of the goods can be considered to be the same type. For example, we may be delivering building materials. Even though there might be many specific types of items, we can safely characterize each job in terms of one or more quantities (e.g. weight and volume, perhaps roughly estimated), and these quantities can be set against the capacities of the vehicles, which would of course be expressed in the same units. For example, in our building materials example, we might roughly characterize each job as a number of boxes of a fixed size, while estimating that our main vehicles can carry 150 such boxes, and the smaller ones can carry 40.
Alternatively, we might need to use two or more quantities, such as weight, volume, and number of units, and some of these may only be relevant to some jobs. For example, a van might have a weight capacity of 500 kg, a volume capacity of 10 cubic metres, and, specifically for cross-trainers, a units capacity of 3. In terms of weight or volume, it would in theory be possible to carry more than 3 cross-trainers, however for other reasons only 3 can be carried at once. To handle this (or any) case, the first step is to decide on the structure of our quantities/capacities arrays; this must then be consistent across all objects where quantities or capacities appear. In this case, we would decide that there are three quantities: weight, volume, and cross-trainer-units, and (arbitrarily but consistently), we will put them in that order in the capacities and quantities arrays.
The following example, where only the third job involves cross-trainers:
"Jobs": [
{ "IdString": "Smith's White Goods",
...
"Quantities": [100, 6, 0],
...
},
{ "IdString": "Jones' Fitness equipment",
...
"Quantities": [40, 2, 0],
...
},
{ "IdString": "Brown's Exercise Kits",
...
"Quantities": [120, 6, 2],
...
}, ...
...
],
"Vehicles": [
{ "IdString": "Truck01",
...
"Capacities": [500, 10, 3],
...
},
{ "IdString": "SmallVan22",
...
"Capacities": [200, 4, 2],
...
},
Notice that the "Brown's Exercise Kits" job, with quantities [120,6,2]
, is presumably to Deliver 2 cross-trainers. Importantly, a weight and volume are also supplied for this job. Otherwise, when a vehicle is carrying this job, the algorithm would overestimate the remaining capacity for other types of goods. When expressing the quantities of jobs, the golden rule is to consider how this affects the estimation of the remaining capacity in a vehicle. This means that there needs to be at least one element of the quantity/capacity arrays that is used by all types of job. For example, the "Smith's White Goods" job, with "Quantities": [100, 6, 0]
, involves zero cross-trainers, but this doesn't mean, if this job is loaded onto "Truck01", that there will still be capacity for 3 cross-trainers on the Truck. The algorithm will consider the shared quantities, and note that in this case, based on volume, the "Smith's" job takes 60% of the van's capacity, and on that basis only 1 cross-trainer could now fit.
When it comes to Depots
, if we can safely assume that the depots have enough stock to supply all the jobs (and/or enough storage space to receive all the collections), then the requirements are exactly the same as in the 'When Quantities don't Matter' section above. That is, if we indicate that a Depot
has the "GoodsSupply" attribute, then by default it has enough goods to supply all of the jobs. However, if the levels or capacities of depots are something we need to deal with, and/or if we need to consider multiple types of stock independently, then we will probably need to introduce and populate a Stock
array in each Depot
object. This is discussed below, and also in the section devoted to the Stock
array.
Multiple types of stock
In the above scenario, we had different types of goods being moved around, but we could handle this with a single quantities/capacities array. Some types of goods -- cross-trainers in our example -- needed their own special place in those arrays, but nevertheless we could deal with all goods as if they were of a single type, with every payload able to be characterised by our single quantities array, and where all goods could be mixed together at will in our vehicles, and freely supplied or received at depots.
But, sometimes we will need to deal with more complex situations, which require us to keep track of different types of goods. Ww will refer to these as different types of 'stock', and the algorithm will deal with multiple 'stocktypes'. Happily, in all cases, we will always use a single, consistent Quantities/Capacities array (in terms of the number of entries, their ordering, and their meaning). However, as we will see, each Vehicle
might need a specific Capacities
array for each stocktype, while our Depots
will need a Stock
array, with an entry for each stocktype, indicating the levels, capacities and other information about that particular stocktype. Finally, each individual Job
will now need a Type
attribute, naming the stocktype of that Job
.
There are three independent reasons that would lead to us needing to add these things:
- levels and capacities at
Depot
s may be limited in relation to the amount of goods involved in the plan. E.g. the plan may involve the delivery of 100 washing machines and 100 microwaves. However, eachDepot
may only hold 20 of each, so we need to be careful that, over the course of the plan, we don't load more from any individual depot than it can supply. - There may be restrictions on carrying different types of stock in the same
Vehicle
at the same time. For example, raw-meat and cooked-meat can't be carried together. In some cases no combinations are allowed at all (e.g. a tanker carrying fuel cannot carry a mixture of diesel and petrol, unless it has multiple compartments, which we will look at later). In this sort of case we additionally need to considerStockMixingRules
. - Given the needs of the types of goods involved in the problem, and the different ways that they need to be packed in transit, it may simply not be possible (or too difficult) to characterise the capacity of a Vehicle with a single Capacities array; basically, there needs to be a different
Capacity
array for each stocktype (although they will retain the same number of elements).
So, in all such cases we need to have the stocktype identified in the Job
object, and we need to know each individual Vehicle
's capacity for each different stocktype. In the example listed, we have three stocktypes, treadmills, dumbbells, and cross-trainers, and we have decided that only two quantities are needed, respectively representing weight and number-of-cross-trainers.
We have three stocktypes, treadmills, dumbbells, and cross-trainers, and we have decided that only two quantities are needed, respectively representing weight and number-of-cross-trainers:
"Jobs": [
{ "IdString": "Smith's Lifestyle Goods",
...
"Quantities": [100,0],
"Type": "treadmills",
...
},
{ "IdString": "Jones' Fitness equipment",
...
"Quantities": [40, 0],
"Type": "dumbbells",
...
},
{ "IdString": "Brown's Exercise Kits",
...
"Quantities": [60, 2],
"Type": "cross-trainers",
...
}, ...
...
],
"Vehicles": [
{ "IdString": "Truck ABC123",
...
"Capacities": [{"Type":"treadmills", "Capacity": [400, 0]},
{"Type":"dumbbells", "Capacity": [500, 0]} ],
...
},
{ "IdString": "SmallVan22",
...
"Capacities": [{"Type":"treadmills", "Capacity": [400, 0]},
{"Type":"cross-trainers", "Capacity": [240, 8]} ],
...
},
In this case, the Truck is not allowed to carry cross-trainers at all, and the small van is not allowed to carry dumbbells. The truck, if only carrying treadmills, could carry 400kgs' worth; if only carrying dumbbells, it could carry 500 kgs' worth. If carrying a mixture of these two types, its load and spare capacity will be calculated proportionately. E.g. if it is already carrying 400 kg of dumbbells, then it is considered 80% full, and hence can only handle a 20% load of other items, i.e. a further 80Kg of treadmills, or a further 100kg of dumbbells.
In this scenario, it may still be true that we can consider Depot
s 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 anAllowedLoads
array, which indicates the capacity for a specific stocktype, and aPriorJobType
. 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:
"NoMixingAllowed"
: if this is the first item in the array, it indicates that, by default, no mixing is allowed between any pairs of stocktypes on the vehicle. The remainder of theStockMixingRules
array may then contain some exceptions to this rule."AllMixingAllowed"
: if this is the first item in the array, it indicates that, by default, any pair of stocktypes can be mixed on the vehicle, and in any compartment on the vehicle. The remainder of theStockMixingRules
array may then contain some exceptions to this rule.{"AllowedMixGroup": ["VehicleLevel","Bread","Biscuits","Oatcakes"]}
: if the first element of theStockMixingRules
array was"NoMixingAllowed"
, then all of the remaining elements need to be objects containing an"AllowedMixGroup"
, indicating exceptions to that rule. In this example, the three stocktypes "Bread", "Biscuits" and "Oatcakes" can freely be mixed together on the same vehicle, however, if relevant, this doesn't mean that they can be mixed in the same compartment of that vehicle. However,{"AllowedMixGroup": ["CompartmentLevel","Bread","Biscuits", "Oatcakes"]}
means that they can be freely mixed within a compartment.{"BannedMixGroup": ["VehicleLevel","cooked_meat","raw_meat"]}
: if the first element of theStockMixingRules
array was"AllMixingAllowed"
, then all of the remaining elements need to be objects containing a"BannedMixGroup"
object, indicating exceptions to that rule. In this example, the two stocktypes "cooked_meat" and "raw_meat" cannot be mixed together on the same vehicle. Logically this also means the ban is in place for any compartment on the vehicle. However, if it is OK to have them together on the vehicle, but not in the same compartment, then the following should be used instead: `{"BannedMixGroup": ["CompartmentLevel","cooked_meat","raw_meat"]}.
Note: even if NoMixingAllowed
is set, vortex will still assume that mixing of a stocktype with itself is still allowed, at both vehicle and compartment levels. If you wish to ban mixing a stocktype with itself, you will always need an explict BannedMixGroup
to express that.
Stock Sequencing Rules
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:
- Explosions
- Release of toxic gas or volatile toxic liquid
- Fires
The tunnel codes are categorised from "A"
to "E"
. The algorithm shall avoid routing vehicles carrying hazardous goods through tunnels of the relevant ADR category.
null
The same as"A"
, no restriction."A"
No restriction (not sign posted)."B"
Passage forbidden through tunnels of category"B"
,"C"
,"D"
and"E"
"C"
Passage forbidden through tunnels of category"C"
,"D"
and"E"
"D"
Passage forbidden through tunnels of category"D"
and"E"
"E"
Passage forbidden through tunnels of category"E"
The algorithm uses the worst case category, assumes maximum load, ignores other carage and load based on weight for example: C500D
.
Characteristics, Include and Exclude examples
Each Driver
, Vehicle
and Depot
object has an associated list of Characteristics
. For example, this is a potential list of driver characteristics:
"Characteristics": ["Devon", "nut-allergy", "Cert-HGV", "Cert-Toxic"]
This driver is based at the Devon branch, is allergic to nuts, and is certified to drive HGVs and to handle toxic materials.
An example list of characteristics for a depot might be:
"Characteristics": ["Batteries"]
... perhaps indicating that this depot can supply batteries. If you are not making use of the "Stock"
object, and looking for a simpler way to ensure some of the jobs get served by the correct depots, this is obviously a useful way to do it (in combination with IncludeDepotCharacteristics
below).
Meanwhile a vehicle's characteristics might be:
"Characteristics": ["Devon", "Wide"]
... perhaps indicating the intention that it sticks to Devon depots, and has a wide wheelbase.
What makes all these Characteristics
come into force are the Include...
and Exclude...
arrays that we look at next.
A Vehicle
object has IncludeDriverCharacteristics
and ExcludeDriverCharacteristics
arrays, which express requirements that a Driver
must meet if they are to drive this Vehicle
. Each of these is a list of lists, to be interpreted as alternative groups. This enables anything from very simple to very sophisticated logic to be applied.
For example, if the only requirement is that the driver is certified for HGV, then the Vehicle
object will include the following:
"IncludeDriverCharacteristics": [["Cert-HGV"]],
"ExcludeDriverCharacteristics": [],
There might be more complex requirements. E.g. in the case below, the vehicle needs either an HGV-certified driver, or, failing that, an LGV licence plus 5-years' experience; however, for business reasons, drivers supplied by 'contractor5' are not trusted.
"IncludeDriverCharacteristics": [["Cert-HGV"],["Cert-LGV","5+Years"]],
"ExcludeDriverCharacteristics": [["contractor5"]],
Depots
have Include...
and Exclude...
arrays too, available for Driver
and Vehicle
This could be used to specify regional constraints, among other things, e.g.:
"IncludeDriverCharacteristics": [["Cornwall","Security-Level-3"]],
"ExcludeDriverCharacteristics": [["nut-allergy"],["wheat-allergy"]],
Meanwhile, Job
objects have a full set of Include...
and Exclude...
arrays available, for each of Depot
, Driver
and Vehicle
.
"IncludeDepotCharacteristics": [["TV"],["Hub","Devon"]],
"ExcludeDepotCharacteristics": [],
"IncludeDriverCharacteristics": [["Cert-Electrical","Cert-Customer"]],
"ExcludeDriverCharacteristics": [],
"IncludeVehicleCharacteristics": [],
"ExcludeVehicleCharacteristics": [["Wide"]],
In this case, the job might be to collect a TV from a customer for repair; so it needs a depot that can receive TVs, or a local 'hub' depot (from where it can be relocated later). The driver needs to have some certification in dealing with electrical goods, and in customer facing, so that he can do some quick checks first. Finally, the customer's location is such that we can't use a wide wheelbase vehicle.
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
/plan
- Websocket endpoint
/plan/ws
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
IdString
s. 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 BREAK
s; the first BREAK
in each of this groups will always appear immediately following a TRAVEL
action. This group of BREAK
s indicates breaks that are scheduled to take place during and/or immediately after the associated TRAVEL
action. The BREAK
action itself indicates the start and stop time of the break; in general the location is unknown, because it will happen en route between the start and end locations of the assocaited TRAVEL
action.
The last (or only) BREAK
in such a group might occur in time immediately after the TRAVEL
action, in which case it takes place at the end location of the TRAVEL
action. An example of a group of breaks following travel might be:
TRAVEL
from 11:00 location A to 18:30 location BBREAK
from 12:00 to 12:45BREAK
from 17:15 to 18:00
In the above case, first there is travel starting at location A, from 11:00 to 12:00, and then the driver stops to take a break; then further travel from 12:45 to 17:15, followed by another break; finally travel from 18:00 to 18:30, arriving at location B.
TRAVEL
from 11:00 location A to 17:30 location BBREAK
from 13:00 to 13:30BREAK
from 17:30 to 18:00
In the above case, first there is travel starting at A, from 11:00 to 13:00, and then the driver stops to take a break; then further travel from 13:30 to 17:30, arriving at B. This is immediately followed by a break at B from 17:30 to 18:00, arriving at location B.
About LOAD_UNLOAD
actions. These may appear at the beginning of a shift, when the vehicle loads up with any deliveries that it has to make, and/or towards the end of a shift, when the vehicle unloads any collections that it made. It may also appear any number of times within a shift, in which case it may involve both loading and unloading.
here is a mid-shift example:
{
"ActionType": "LOAD_UNLOAD",
"Day": 1,
"EndTime": "19:32:00",
"Load":["Job_1","Job_4","Job_7"],
"StartLatLon": [51.148972, 1.5488821],
"StartLocationGeohash": "gcv0d8yr",
"StartTime": "19:22:00",
"Unload":["Job_3","Job_5"]
},
In the above case, the vehicle must be at a depot, and it will first unload the jobs whose unique
IdString
s are "Job_3" and "Job_5", and then it will load the jobs whose uniqueIdStrings
s 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.