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:

Prerequisites

To run the examples in this guide, you need to authenticate with a valid API key:

  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your username, and from the drop-down menu select API Keys.

  3. Copy the default API key.

In the examples, replace <API_KEY> with the API Key you just copied.

The times displayed in the visualizations are approximates only.

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 dependency

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:

visit dependency too early
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

visit multiple dependencies

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:

visit dependency delay

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.

  • 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 the minStartDateAdjuster and minStartTime 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 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:

visit dependency delay to

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

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

visit dependency 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