Dependencies between visits
In field service routing it is often necessary to perform visits in a specific order. This could be multiple visits at the same location, such as, a plumbing visit may need to be completed before an electrical visit can be started. A third visit may even need to be completed after the electrical visit.
Sometimes it is necessary to include delays between visits, for example, if paint needs to dry before further work can start.
Visits at different locations can also be dependent visits, such as, if one task needs to be completed at a workshop before further work at the customer location can be performed.
There can also be requirements that the same technician attends multiple dependent visits.
This guide describes how to schedule visits in the correct order, with the following examples:
-
Visit dependency with minimum delay (expressed either as a duration or a point in time)
Prerequisites
To run the examples in this guide, you need to authenticate with a valid API key:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your username, and from the drop-down menu select API Keys.
-
Copy the default API key.
In the examples, replace <API_KEY>
with the API Key you just copied.
1. Single visit dependency
A visit may depend on another visit, meaning that the dependent visit can start no sooner than the visit it depends on ends.
For instance, if Carl is assigned Visit B in his shift itinerary, and Visit B is an electrician task that cannot start until after a related plumber task, Visit A, is completed:
Visit A can be performed by another technician on another day, but it needs to be completed before Visit B starts.
If Visit A needs to be completed by the same technician, see Visit dependency with coordination for details. |
Visit dependencies are defined by adding visitDependencies
to a visit and specifying a precedingVisit
:
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H30M",
"visitDependencies": [
{
"id": "Visit dependency B on A",
"precedingVisit": "Visit A"
}
]
}
]
In this example, visitDependencies
specifies Visit A is a precedingVisit
of Visit B. Visit A must be completed before Visit B can start.
Visits can depend on multiple visits. See Multiple visit dependencies for more information. |
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Visit dependencies example"
}
},
"modelInput": {
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-2-1",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z"
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H30M",
"visitDependencies": [
{
"id": "Visit dependency B on A",
"precedingVisit": "Visit A"
}
]
}
]
}
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
|
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans/<ID>
{
"run": {
"id": ID
"name": "Visit dependencies example",
"submitDateTime": "2024-08-05T06:24:59.072142988Z",
"startDateTime": "2024-08-05T06:25:04.91066022Z",
"completeDateTime": "2024-08-05T06:30:05.500772913Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-4171soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-2-1",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:29:57Z",
"startServiceTime": "2027-02-01T09:29:57Z",
"departureTime": "2027-02-01T10:59:57Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT29M57S",
"travelDistanceMetersFromPreviousStandstill": 31492,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:06:40Z",
"startServiceTime": "2027-02-01T11:06:40Z",
"departureTime": "2027-02-01T12:36:40Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT6M43S",
"travelDistanceMetersFromPreviousStandstill": 4123,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H9M31S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT6M43S",
"travelTimeFromLastVisitToEndLocation": "PT32M51S",
"totalTravelDistanceMeters": 71177,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 4123,
"travelDistanceFromLastVisitToEndLocationMeters": 35562,
"endLocationArrivalTime": "2027-02-01T13:09:31Z"
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT1H9M31S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT6M43S",
"travelTimeFromLastVisitToEndLocation": "PT32M51S",
"totalTravelDistanceMeters": 71177,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 4123,
"travelDistanceFromLastVisitToEndLocationMeters": 35562,
"totalUnassignedVisits": 0
}
}
modelOutput
contains the itinerary with Visit B scheduled after Visit A has ended.
What happens if Visit B was planned for Carl before Visit A finished? In this case, a hard constraint is violated, penalizing the solution by the number of seconds between Visit A ending and Visit B starting:
Learn about the hard and soft constraints in the Field Service Routing model. |
2. Multiple visit dependencies
Visits can declare multiple dependencies. In the following example:
-
Visit C depends on Visit A and Visit B
-
Visit D depends on Visit C
-
Visit E depends on Visit C
The dependency is defined as follows:
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H"
},
{
"id": "Visit C",
"location": [33.77432, -84.43844],
"serviceDuration": "PT1H30M",
"visitDependencies": [
{
"id": "Visit dependency C on A",
"precedingVisit": "Visit A"
},
{
"id": "Visit dependency C on B",
"precedingVisit": "Visit B"
}
]
},
{
"id": "Visit D",
"location": [33.77654, -84.43821],
"serviceDuration": "PT1H",
"visitDependencies": [
{
"id": "Visit dependency D on C",
"precedingVisit": "Visit C"
}
]
},
{
"id": "Visit E",
"location": [33.77322, -84.43934],
"serviceDuration": "PT1H",
"visitDependencies": [
{
"id": "Visit dependency E on C",
"precedingVisit": "Visit C"
}
]
}
]
3. Visit dependency with minimum delay
When Visit B depends on Visit A, you can specify a minimum delay between the end of Visit A and the start of Visit B.
Visit A might be a painting task and the follow-up Visit B task needs to wait at least 3 hours until the paint dries:
The minimum delay can be expressed in two ways:
-
Duration: a minimum number of days/hours/minutes/seconds that need to pass before Visit B can start.
-
Point in time: a specified date and time relative to the end of Visit A when Visit B can start. For example, next day (after Visit A ends) at 06:00.
The two ways of expressing minimum delay (duration and point in time) are mutually exclusive, specifying both for a single visit dependency results in a validation error. |
3.1. Visit dependency with minimum delay as duration
The minimum delay as duration is expressed as an ISO 8601 duration.
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H",
"visitDependencies": [
{
"id": "Visit dependency B on A",
"precedingVisit": "Visit A",
"minDelay": "PT3H"
}
]
}
]
3.2. Visit dependency with minimum delay as a point in time
The minimum delay as a point in time is expressed with minDelayTo with the following attributes:
-
minStartDateAdjuster
: (required) the name of the function (see below) that adjusts the date part of the delayed visit’s minimum start. Example:NEXT_DAY
,NEXT_MONDAY
. -
minStartDateAdjusterIncrement
: (optional) the increment which determines how many timesminStartDateAdjuster
is applied. The minimum value is1
, because theminStartDateAdjuster
has to be applied at least once. -
minStartTime
: (optional) the time part (ISO 8601 local datetime) of the delayed visit’s minimum start (inclusive). Being a local datetime, it does not include the timezone offset. The default value is midnight (start of day):00:00
. -
timezone
: (optional) the region-based TZDB identifier of the timezone which applies to theminStartDateAdjuster
andminStartTime
combination. Example:"America/New_York"
,"Europe/Brussels"
.
If the timezone is omitted, the zone offset of the visit dependency departureTime is used for the adjusted date. If DST changes need to be handled, the timezone must be supplied.
|
With the above, we can express the following conditions on the dependent visit’s minimum start time:
-
Visit B can start no sooner than midnight of the day after Visit A ends:
-
minStartDateAdjuster = NEXT_DAY
-
-
Visit B can start no sooner than 14:00 of the day after Visit A ends:
-
minStartDateAdjuster = NEXT_DAY
-
minStartTime = 14:00
-
-
Visit B can start no sooner than 12:00 of the next Monday after Visit A ends:
-
minStartDateAdjuster = NEXT_MONDAY
-
minStartTime = 12:00
-
-
Visit B can start no sooner than 12:00 of the day 2 days after Visit A ends:
-
minStartDateAdjuster = NEXT_DAY
-
minStartDateAdjusterIncrement = 2
-
minStartTime = 12:00
-
-
Visit B can start no sooner than midnight of the first day of the month, 6 months after visit A ends.
-
minStartDateAdjuster = NEXT_MONTH
-
minStartDateAdjusterIncrement = 6
-
-
Visit B can start no sooner than 12:00 of the first day of the next month after Visit A ends, taking a possible DST change in the
America/New_York
timezone into account:-
minStartDateAdjuster = NEXT_MONTH
-
minStartTime = 12:00
-
timezone = America/New_York
-
The second situation is depicted in the image below:
If Visit B can start no sooner than 08:00 on the Monday after Visit A has ended, taking a possible DST change in the America/New_York
timezone into account:
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H",
"visitDependencies": [
{
"id": "Visit dependency B on A",
"precedingVisit": "Visit A",
"minDelayTo": {
"minStartDateAdjuster": "NEXT_MONDAY",
"minStartDateAdjusterIncrement" : 1,
"minStartTime": "08:00",
"timezone": "America/New_York"
}
}
]
}
]
The list of available date adjusting functions (to be used in minStartDateAdjuster
):
Name | Description |
---|---|
SAME_DAY |
Keeps the same day - useful when specifying just time adjustment, such as "at 16:00 of the same day". |
NEXT_DAY |
Adjusts the date to the next day. |
NEXT_MONTH |
Adjusts the date to the first day of the next month. |
NEXT_MONDAY |
Adjusts the date to the next Monday. |
NEXT_TUESDAY |
Adjusts the date to the next Tuesday. |
NEXT_WEDNESDAY |
Adjusts the date to the next Wednesday. |
NEXT_THURSDAY |
Adjusts the date to the next Thursday. |
NEXT_FRIDAY |
Adjusts the date to the next Friday. |
NEXT_SATURDAY |
Adjusts the date to the next Saturday. |
NEXT_SUNDAY |
Adjusts the date to the next Sunday. |
We have intentionally omitted NEXT_WEEK (adjusting the date to the first day of the next week) as you can achieve its behaviour using NEXT_MONDAY/NEXT_SUNDAY/etc.
If you have a specific use-case demanding this functionality, please let us know.
|
If you need to specify a delay of a more specific amount of days/weeks, use the minStartDateAdjusterIncrement to apply the minStartDateAdjuster multiple times.
Example: you need to delay 2 days, use NEXT_DAY and set the increment to 2 .
|
4. Visit dependency with coordination
When Visit B depends on Visit A, you can specify additional conditions to hold between Visit A and Visit B by using visitDependency.coordination
attribute.
To specify that Visit B has to be assigned to the same vehicle (not to the same vehicle shift) as Visit A, use "coordination": "SAME_VEHICLE"
:
The following model input instructs Timefold to assign both Visit A and Visit B either to Carl or Beth:
{
"modelInput": {
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-2-1",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z"
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-2-1",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z"
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M"
},
{
"id": "Visit B",
"location": [33.78767, -84.43887],
"serviceDuration": "PT1H",
"visitDependencies": [
{
"id": "Visit dependency B on A",
"precedingVisit": "Visit A",
"coordination": "SAME_VEHICLE"
}
]
}
]
}
}
Visit dependency coordination can be combined with the minDelay or minDelayTo attributes.
|
The available visit dependency coordination types are:
Name | Description |
---|---|
NONE |
No additional coordination requirements. (default) |
SAME_VEHICLE |
Both visit and its dependency have to be assigned to the same vehicle. |
Next
-
Understand the constraints of the Field Service Routing model.
-
See the full API spec or try the online API.
-
Manage shift times with Time zones and daylight-saving time (DST) changes.
-
Learn about Shift hours and overtime.
-
Use Time windows to specify visit availability and limit when visits can be scheduled.