Multi-shift
Multi-Shift¶
Use multi-shift when a Driver works more than one Shift in the same request — several days, or a morning and afternoon Shift on the same day.
You still POST to /v2/vrp (or the polling equivalent). There is no
separate endpoint. The request becomes a multi-shift request when
every Driver has a multi_shift object instead of shift_start and
shift_end.
The idea is in Concepts. This page is the
request guide: where the multi_shift object goes, what belongs in it,
and how reset differs from continue.
Reset versus continue
Reset
Continue
Reset goes home every day. Continue starts Day 2 from where Day 1 stopped.
Where the multi_shift object goes¶
Multi-shift is not a single field. You add a multi_shift object in
two places, and you keep start/end locations on the Driver as usual.
| Where | What you put there | Required? |
|---|---|---|
driver.multi_shift |
mode and shifts |
Yes — on every Driver |
job.multi_shift |
serve_within (date windows for that Job) |
Only when the Job must fall on particular days |
driver.start / driver.end |
Locations, same as a single-day request | start is required; end is optional |
Rules:
- Every Driver omits
shift_startandshift_endand suppliesmulti_shiftinstead. You cannot mix the two shapes in one request, and you cannot put both on the same Driver. modeandshiftsbelong only on the Driver. Do not put them on Jobs.- Jobs keep their usual fields (
location,duration,parking_duration,pickup_from,later_than,size,unserved_penalty_factor, and so on). - Date windows belong on the Job, as
job.multi_shift.serve_within. Top-levelarrive_after/leave_byare still allowed; they become the same clock times on every Planning Day and cannot cross midnight. - Start and end locations stay on the Driver, not on each Shift.
A Driver with no work in this horizon still needs a multi_shift
object. Give them "shifts": [] rather than mixing schedule shapes.
Planning times¶
Every Shift bound, and every serve_within bound, is a planning time:
- Planning Day
1is the first day of this request.timealso accepts"08:00".
time also accepts "08:00" or "08:00:00". day is a one-based
Planning Day in this request (1 is the first day). The maximum is
100.
A Shift may cross midnight, for example day 1 at 22:00 to day 2 at 06:00. Two Shifts on the same Driver must not overlap. Back-to-back Shifts are allowed.
Reset mode¶
"mode": "reset" (the default if you omit mode) treats each Shift as
a fresh trip.
- Every Shift starts at
driver.start. - Every Shift respects
driver.end(return to start, finish anywhere, or a fixed location). - The vehicle is empty at the start of each Shift.
- A Pickup Delivery pair must be served in the same Shift. The Driver cannot pick up on Monday and deliver on Tuesday.
- Overnight travel between the depot and home is outside the optimisation.
Use reset for last-mile work that returns to a depot each day.
Example — two last-mile days
{
"drivers": [
{
"uid": "driver_1",
"start": {"lat": -33.867798, "lon": 151.166256},
"end": "start",
"multi_shift": {
"mode": "reset",
"shifts": [
{
"shift_start": {"day": 1, "time": 8},
"shift_end": {"day": 1, "time": 17}
},
{
"shift_start": {"day": 2, "time": 8},
"shift_end": {"day": 2, "time": 17}
}
]
}
}
],
"jobs": [
{
"uid": "monday_delivery",
"duration": 10,
"location": {"lat": -33.849489, "lon": 151.127482},
"multi_shift": {
"serve_within": [
{
"arrive_after": {"day": 1, "time": 8},
"leave_by": {"day": 1, "time": 17}
}
]
}
},
{
"uid": "either_day",
"duration": 10,
"location": {"lat": -33.880661, "lon": 151.183096}
}
],
"settings": {}
}
monday_delivery can only be served on day 1. either_day can be
served on day 1 or day 2 — whichever produces the better route.
Continue mode¶
"mode": "continue" treats all of that Driver's Shifts as one journey
with rest in between.
- Only the first Shift starts at
driver.start. - Between Shifts the Driver stays where they are: at the last finished Job, at the next Job if they already arrived, or partway along a drive.
- They cannot serve or make driving progress while off shift. An
activity may finish exactly at
shift_end. driver.endapplies to the final return after the last Job, which may happen in a later Shift that contains no Jobs.- Capacity and onboard pickups carry across Shifts. A Pickup Delivery pair must stay on the same Driver, but the delivery may be on a later Shift than the pickup.
Use continue for long-distance work with overnight rest.
Example — a two-day linehaul
The Driver leaves the depot on day 1, rests overnight wherever the route stops, and continues on day 2. The parcel stays on the vehicle.
{
"drivers": [
{
"uid": "linehaul_1",
"start": {"lat": -33.867798, "lon": 151.166256},
"end": {"lat": -27.469771, "lon": 153.025124},
"capacity": 20,
"multi_shift": {
"mode": "continue",
"shifts": [
{
"shift_start": {"day": 1, "time": 8},
"shift_end": {"day": 1, "time": 18}
},
{
"shift_start": {"day": 2, "time": 8},
"shift_end": {"day": 2, "time": 18}
}
]
}
}
],
"jobs": [
{
"uid": "pickup_depot",
"duration": 20,
"parking_duration": 10,
"location": {"lat": -33.867798, "lon": 151.166256},
"size": 8
},
{
"uid": "drop_destination",
"duration": 20,
"parking_duration": 10,
"location": {"lat": -27.469771, "lon": 153.025124},
"size": -8,
"pickup_from": "pickup_depot"
}
],
"settings": {}
}
If this Driver had used reset instead, the pickup and drop would have
to finish on the same day, and the vehicle would return to the Sydney
start at the end of day 1.
Job date windows (serve_within)¶
job.multi_shift.serve_within is a list of absolute periods. Each
period has arrive_after, leave_by, or both, as planning times.
The Job may be served in any one of those periods (OR). Inside one
period both bounds apply (AND). Combined with top-level
arrive_after / leave_by:
Omit multi_shift on the Job, or give "serve_within": [], to allow
any time in the planning horizon (subject to Shifts and every other
constraint).
Each period must set at least one bound. An empty {} is rejected.
serve_within is only valid when every Driver uses multi_shift.
Example — open Tuesday or Wednesday afternoon
{
"uid": "appointment",
"duration": 30,
"arrive_after": 13,
"leave_by": 17,
"location": {"lat": -33.849489, "lon": 151.127482},
"multi_shift": {
"serve_within": [
{
"arrive_after": {"day": 2, "time": "00:00"},
"leave_by": {"day": 2, "time": "23:59"}
},
{
"arrive_after": {"day": 3, "time": "00:00"},
"leave_by": {"day": 3, "time": "23:59"}
}
]
}
}
The recurring hours 13–17 apply on both days. The serve_within
list restricts the Job to day 2 or day 3.
How other constraints behave¶
| Constraint | reset |
continue |
|---|---|---|
| Pickup Delivery | Same Driver and same Shift. The group is all-or-none. | Same Driver; the pair may span Shifts. Still all-or-none. |
| Later Than | Time order only. The two Jobs may be different Shifts or different Drivers. | Same: time order only. |
| Capacity | Load resets at every Shift. | Load stays on the vehicle between Shifts. |
| Parking Duration | Unchanged. Parking is waived when driving time into the stop is zero. | Unchanged. A first Job at the overnight stop does not re-charge parking. |
| Route Segment | Start / end apply inside each Shift. | Start / end apply across the whole journey. |
| Unserved penalty factor | Applies across the horizon. Does not pin a Job to a day. | Same. |
costs.run |
Charged once per active Shift. | Charged once for the whole journey. |
Reading the response¶
The runs array is still flat. Each Shift produces one Run, even if
that Shift has no Jobs. Runs are ordered by Driver, then by Shift.
On a multi-shift response:
run.driver.multi_shift.modeis"reset"or"continue". Drivershift_start/shift_endare omitted.run.multi_shifthas the 1-basedshiftnumber,planned_start/planned_endas planning times, and the actual start and end locations for that Shift. Incontinuethose locations are often not the configured depot.- Each Job has
job.multi_shift.etaandjob.multi_shift.etdas planning times. Top-leveleta/etdare omitted. Unserved Jobs still have amulti_shiftobject, with null times.
{
"runs": [
{
"driver": {
"uid": "driver_1",
"location": {"lat": -33.867798, "lon": 151.166256},
"multi_shift": {"mode": "reset"}
},
"jobs": [
{
"uid": "monday_delivery",
"duration": 10,
"location": {"lat": -33.849489, "lon": 151.127482},
"run": 1,
"seq": 1,
"multi_shift": {
"eta": {"day": 1, "time": "08:25:00"},
"etd": {"day": 1, "time": "08:35:00"}
}
}
],
"multi_shift": {
"shift": 1,
"planned_start": {"day": 1, "time": "08:00:00"},
"planned_end": {"day": 1, "time": "08:50:00"},
"planned_start_location": {"lat": -33.867798, "lon": 151.166256},
"planned_end_location": {"lat": -33.867798, "lon": 151.166256}
}
}
],
"unserved_jobs": []
}
Prefer earlier Shifts¶
Travel time (and costs.run) do not care which day a Job lands on, so
a reset request can leave Monday thin and park work on Thursday if that
tour is shorter.
Set settings.later_shift_usage_penalty
to penalise used time on later declared Shifts. It applies to ordinary
single-Shift requests and to multi-shift (mode = reset and
mode = continue). It is not a hard constraint.
Prefer fewer vehicles¶
Set settings.active_vehicle_penalty
to penalise each compiled vehicle that serves at least one Job.
Ordinary and continue: once per Driver. Reset: once per Shift. Continue
does not pay per calendar day.
In cost mode this stacks with costs.run; they are different units.
It also stacks with later_shift_usage_penalty (on/off versus used
time on later Shifts). It is not a hard fleet cap.