Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Field Service Routing
  • Recommendations
  • Visit group time window recommendations

Field Service Routing

    • Introduction
    • Planning AI concepts
    • Metrics and optimization goals
    • Getting started with field service routing
    • Understanding the API
    • Constraints
    • Vehicle resource constraints
      • Shift hours and overtime
      • Lunch breaks and personal appointments
      • Fairness
      • Technician costs
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Visit requirements
      • Multi-vehicle visits
      • Priority visits and optional visits
    • Real-time planning
      • Real-time planning
      • Real-time planning: extended visit
      • Real-time planning: reassignment
      • Real-time planning: emergency visit
      • Real-time planning: no show
      • Real-time planning: technician ill
      • Real-time planning: pinning visits
    • Recommendations
      • Recommendations
      • Visit time window recommendations
      • Visit group time window recommendations
    • Time zones and daylight-saving time (DST)
    • New and noteworthy
    • Upgrading to the latest versions
    • Feature requests
    • Reference guide

Visit group time window recommendations

When visits require more than a single technician in a single vehicle, they are known as multi-vehicle visits. Learn more about Multi-vehicle visits.

Visit group recommendations provide a range of recommendations for multi-vehicle visits that include all the technicians who are required for the visits in the visit group.

This guide explains visit group recommendations with the following examples:

  • Visit group recommendations for unsolved route plans

  • Visit group recommendations for unsolved route plans with existing visits

  • Visit Group recommendations for published route plans

  • Include dependencies

  • Recommendations with pinned visits

Prerequisites

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

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

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the current model.

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

1. Visit group recommendations for unsolved route plans

Continuous planning includes four stages:

  1. Historic
    The historic stage includes schedules that have already been executed and cannot be changed.

  2. Published
    The published stage includes schedules that have been shared with your customers and technicians and should only be changed in special circumstances.

  3. Draft
    The draft stage includes schedules that can change without impacting customers or technicians. At some point, draft schedules will be solved and moved to the published stage.

  4. Unplanned
    The unplanned stage is too far away to start planning schedules.

When customers request work during a time period that is still in the draft phase, you can use recommendations to provide the customer with time windows to choose from.

The customer’s chosen time window can be added to the draft schedule, which will be optimized before being published.

1.1. The recommendation input dataset

A recommendation input dataset includes the following:

1.1.1. Max number of recommendations per time window

The maximum number of recommendations to make per time window specified in the input dataset.

The recommendation will consider technicians' vehicle shifts that overlap the specified time window and make up to the number of recommendations specified in maxNumberOfRecommendationsPerTimeWindow.

{
  "maxNumberOfRecommendationsPerTimeWindow": 2
}

1.1.2. Fit visit group ID

The ID of the visit group to make the recommendations for. The provided fitVisitGroupID must match an ID from a visit group in the modelInput.

{
  "fitVisitGroupId": "Group A"
}

1.1.3. Time windows

The time windows to be considered for the recommendation. For instance, 09:00 to 13:00 on February 1, 2027 and 13:00 to 17:00 on February 1, 2027.

Time windows can cover an entire day or even longer, but as time windows get longer, you reduce the number of recommendations and the customer’s options.

{
  "timeWindows": [
    {
      "startTime": "2027-02-01T09:00:00Z",
      "endTime": "2027-02-01T13:00:00Z"
    },
    {
      "startTime": "2027-02-01T13:00:00Z",
      "endTime": "2027-02-01T17:00:00Z"
    }
  ]
}

1.1.4. Model input

The model input must include the technician’s vehicles and shifts and the visit group the recommendations are for.

To learn more about vehicle shifts and visits, see Shift hours and overtime.

{
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.77301, -84.43899],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visitGroups": [
      {
        "id": "Group A",
        "visits": [
          {
            "id": "Visit A1",
            "name": "visit group A 1/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H"
          },
          {
            "id": "Visit A2",
            "name": "visit group A 2/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H"
          }
        ]
      }
    ]
  }
}

1.2. Submit the input dataset

Submit the recommendations input dataset to the API endpoint: /v1/route-plans/recommendations/visit-group-recommend-time-windows.

If you want individual constraint match justifications included in the response, you can specify an optional boolean includeJustifications query parameter: /v1/route-plans/recommendations/visit-group-recommend-time-windows?includeJustifications=true.

The HTTP response provides the recommendations.

  • Input

  • Output (without justifications)

  • Output (with justifications)

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:

To get recommendations without constraint match justifications (default):

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/recommendations/visit-group-recommend-time-windows [email protected]

To get recommendations with constraint match justifications:

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/recommendations/visit-group-recommend-time-windows?includeJustifications=true [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitGroupId": "Group A",
  "timeWindows": [
    {
      "startTime": "2027-02-01T09:00:00Z",
      "endTime": "2027-02-01T13:00:00Z"
    },
    {
      "startTime": "2027-02-01T13:00:00Z",
      "endTime": "2027-02-01T17:00:00Z"
    }
  ],
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.77301, -84.43899],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visitGroups": [
      {
        "id": "Group A",
        "visits": [
          {
            "id": "Visit A1",
            "name": "visit group A 1/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H"
          },
          {
            "id": "Visit A2",
            "name": "visit group A 2/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H"
          }
        ]
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/20000medium/-86077soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-285soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-81237soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-4555soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Carl-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:09:52Z",
              "startServiceTime": "2027-02-01T09:26:56Z",
              "departureTime": "2027-02-01T11:26:56Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT9M52S",
              "travelDistanceMetersFromPreviousStandstill": 9445,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT19M3S",
            "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT9M11S",
            "totalTravelDistanceMeters": 16561,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9445,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 7116,
            "endLocationArrivalTime": "2027-02-01T11:36:07Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:26:56Z",
              "startServiceTime": "2027-02-01T09:26:56Z",
              "departureTime": "2027-02-01T11:26:56Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT26M56S",
              "travelDistanceMetersFromPreviousStandstill": 29981,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT56M52S",
            "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 64676,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29981,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T11:56:52Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H15M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT36M48S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT39M7S",
        "totalTravelDistanceMeters": 81237,
        "travelDistanceFromStartLocationToFirstVisitMeters": 39426,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 41811,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 49.7,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/20000medium/-86438soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-646soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-81237soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-4555soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Carl-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:09:52Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT9M52S",
              "travelDistanceMetersFromPreviousStandstill": 9445,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT19M3S",
            "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT9M11S",
            "totalTravelDistanceMeters": 16561,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9445,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 7116,
            "endLocationArrivalTime": "2027-02-01T15:09:11Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:26:56Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT26M56S",
              "travelDistanceMetersFromPreviousStandstill": 29981,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT56M52S",
            "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 64676,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29981,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T15:29:56Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H15M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT36M48S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT39M7S",
        "totalTravelDistanceMeters": 81237,
        "travelDistanceFromStartLocationToFirstVisitMeters": 39426,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 41811,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 49.94,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/20000medium/-86077soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-285soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-285soft",
              "justification": {
                "unfairnessScore": 285,
                "description": "Unfairness score for the time utilization of vehicles '285'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-81237soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-16561soft",
              "justification": {
                "visitId": "Visit A1",
                "travelDistanceMeters": 16561,
                "description": "Visit (Visit A1) has a travel distance of '16561' meters."
              }
            },
            {
              "score": "0hard/0medium/-64676soft",
              "justification": {
                "visitId": "Visit A2",
                "travelDistanceMeters": 64676,
                "description": "Visit (Visit A2) has a travel distance of '64676' meters."
              }
            }
          ],
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-4555soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-1143soft",
              "justification": {
                "visitId": "Visit A1",
                "travelTime": "PT19M3S",
                "description": "Visit (Visit A1) has travel time of 'PT19M3S'."
              }
            },
            {
              "score": "0hard/0medium/-3412soft",
              "justification": {
                "visitId": "Visit A2",
                "travelTime": "PT56M52S",
                "description": "Visit (Visit A2) has travel time of 'PT56M52S'."
              }
            }
          ],
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A1",
                "description": "Mandatory visit (Visit A1) has been left unassigned."
              }
            },
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A2",
                "description": "Mandatory visit (Visit A2) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Carl-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:09:52Z",
              "startServiceTime": "2027-02-01T09:26:56Z",
              "departureTime": "2027-02-01T11:26:56Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT9M52S",
              "travelDistanceMetersFromPreviousStandstill": 9445,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT19M3S",
            "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT9M11S",
            "totalTravelDistanceMeters": 16561,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9445,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 7116,
            "endLocationArrivalTime": "2027-02-01T11:36:07Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:26:56Z",
              "startServiceTime": "2027-02-01T09:26:56Z",
              "departureTime": "2027-02-01T11:26:56Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT26M56S",
              "travelDistanceMetersFromPreviousStandstill": 29981,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT56M52S",
            "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 64676,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29981,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T11:56:52Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H15M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT36M48S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT39M7S",
        "totalTravelDistanceMeters": 81237,
        "travelDistanceFromStartLocationToFirstVisitMeters": 39426,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 41811,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 49.7,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/20000medium/-86438soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-646soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-646soft",
              "justification": {
                "unfairnessScore": 646,
                "description": "Unfairness score for the time utilization of vehicles '646'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-81237soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-16561soft",
              "justification": {
                "visitId": "Visit A1",
                "travelDistanceMeters": 16561,
                "description": "Visit (Visit A1) has a travel distance of '16561' meters."
              }
            },
            {
              "score": "0hard/0medium/-64676soft",
              "justification": {
                "visitId": "Visit A2",
                "travelDistanceMeters": 64676,
                "description": "Visit (Visit A2) has a travel distance of '64676' meters."
              }
            }
          ],
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-4555soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-1143soft",
              "justification": {
                "visitId": "Visit A1",
                "travelTime": "PT19M3S",
                "description": "Visit (Visit A1) has travel time of 'PT19M3S'."
              }
            },
            {
              "score": "0hard/0medium/-3412soft",
              "justification": {
                "visitId": "Visit A2",
                "travelTime": "PT56M52S",
                "description": "Visit (Visit A2) has travel time of 'PT56M52S'."
              }
            }
          ],
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A1",
                "description": "Mandatory visit (Visit A1) has been left unassigned."
              }
            },
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A2",
                "description": "Mandatory visit (Visit A2) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Carl-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:09:52Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT9M52S",
              "travelDistanceMetersFromPreviousStandstill": 9445,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT19M3S",
            "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT9M11S",
            "totalTravelDistanceMeters": 16561,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9445,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 7116,
            "endLocationArrivalTime": "2027-02-01T15:09:11Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:26:56Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT26M56S",
              "travelDistanceMetersFromPreviousStandstill": 29981,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT56M52S",
            "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 64676,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29981,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T15:29:56Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H15M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT36M48S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT39M7S",
        "totalTravelDistanceMeters": 81237,
        "travelDistanceFromStartLocationToFirstVisitMeters": 39426,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 41811,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 49.94,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

1.3. The recommendations

In this example, there are 2 recommendations.

  1. 09:00-13:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

  2. 13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

recommendations visit group time window unsolved

Each recommendation includes the following:

1.3.1. ScoreDiff

The score diff is based on the difference before the new visit group has been scheduled and after the new visit group has been scheduled.

{
  "scoreDiff": "0hard/20000medium/-86077soft",
}

1.3.2. Constraint scores without justifications

The score for each constraint that has changed, for instance:

{
    "score": "0hard/0medium/-81237soft",
    "constraintName": "Minimize travel distance",
    "matchCountDiff": 2
}

1.3.3. Constraint scores with justifications

The score and justifications for each constraint that has changed, for instance:

{
  "score": "0hard/0medium/-81237soft",
  "constraintName": "Minimize travel distance",
  "matchesDiff": [
    {
      "score": "0hard/0medium/-16561soft",
      "justification": {
        "visitId": "Visit A1",
        "travelDistanceMeters": 16561,
        "description": "Visit (Visit A1) has a travel distance of '16561' meters."
      }
    },
    {
      "score": "0hard/0medium/-64676soft",
      "justification": {
        "visitId": "Visit A2",
        "travelDistanceMeters": 64676,
        "description": "Visit (Visit A2) has a travel distance of '64676' meters."
      }
    }
  ],
  "matchCountDiff": 2
}

1.3.4. The time window

The time window the recommendation is for:

{
  "timeWindow": {
    "minStartTime": "2027-02-01T09:00:00Z",
    "maxStartTime": null,
    "maxEndTime": "2027-02-01T13:00:00Z"
  }
}

1.3.5. The vehicle shifts

The vehicle shift the recommendation applies to and the shift itinerary, metrics, and KPIs.

{
  "vehicleShifts": [
    {
      "id": "Carl-2027-02-01",
      "startTime": "2027-02-01T09:00:00Z",
      "itinerary": [
        {
          "id": "Visit A1",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T09:09:52Z",
          "startServiceTime": "2027-02-01T13:00:00Z",
          "departureTime": "2027-02-01T15:00:00Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT9M52S",
          "travelDistanceMetersFromPreviousStandstill": 9447,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        }
      ],
      "metrics": {
        "totalTravelTime": "PT19M3S",
        "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT9M11S",
        "totalTravelDistanceMeters": 16563,
        "travelDistanceFromStartLocationToFirstVisitMeters": 9447,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 7116,
        "endLocationArrivalTime": "2027-02-01T15:09:11Z"
      }
    },
    {
      "id": "Ann-2027-02-01",
      "startTime": "2027-02-01T09:00:00Z",
      "itinerary": [
        {
          "id": "Visit A2",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T09:26:56Z",
          "startServiceTime": "2027-02-01T13:00:00Z",
          "departureTime": "2027-02-01T15:00:00Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT26M56S",
          "travelDistanceMetersFromPreviousStandstill": 29982,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        }
      ],
      "metrics": {
        "totalTravelTime": "PT56M52S",
        "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT29M56S",
        "totalTravelDistanceMeters": 64677,
        "travelDistanceFromStartLocationToFirstVisitMeters": 29982,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 34695,
        "endLocationArrivalTime": "2027-02-01T15:29:56Z"
      }
    }
  ]
}

1.4. Implementing the recommendation

The recommendation output for this example includes two recommendations:

  1. 09:00-13:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

  2. 13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

The recommendations include specific times, however, the plan is still a draft and those times will likely change as additional visits are added to the plan.

If the customer agrees to one of the time windows, it must be added to the recommendation input dataset (the draft plan).

In this instance, the customer accepted the second recommendation:

  • 13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

First, add the visits to Ann’s and Carl’s itineraries:

Where there are multiple visits to include in the itinerary, they must be included in the order they occur on the schedule.
{
  "id": "Ann",
  "shifts": [
    {
      "id": "Ann-2027-02-01",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-01T09:00:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z",
      "itinerary": [
        {
          "id": "Visit A2",
          "kind": "VISIT"
        }
      ]
    },
    {
      "id": "Carl",
      "shifts": [
        {
          "id": "Carl-2027-02-01",
          "startLocation": [33.77301, -84.43899],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "itinerary": [
            {
              "id": "Visit A1",
              "kind": "VISIT"
            }
          ]
        }
      ]
    }
  ]
}

Next, add the time window to the visits in the visit group:

{
  "visitGroups": [
    {
      "id": "Group A",
      "visits": [
        {
          "id": "Visit A1",
          "name": "visit group A 1/2",
          "location": [ 33.80209, -84.40296 ],
          "serviceDuration": "PT2H",
          "timeWindows": [
            {
              "minStartTime": "2027-02-01T13:00:00Z",
              "maxEndTime": "2027-02-01T17:00:00Z"
            }
          ]
        },
        {
          "id": "Visit A2",
          "name": "visit group A 2/2",
          "location": [ 33.80209, -84.40296 ],
          "serviceDuration": "PT2H",
          "timeWindows": [
            {
              "minStartTime": "2027-02-01T13:00:00Z",
              "maxEndTime": "2027-02-01T17:00:00Z"
            }
          ]
        }
      ]
    }
  ]
}
It is important to decide how to manage concurrent requests. If multiple requests are made for recommendations, which recommendation is added to the input dataset and what happens to other recommendations?

2. Visit group recommendations for unsolved route plans with existing visits

When additional customers call to request technicians to complete work at their premises, their requests can be added to the recommendation input dataset.

In the following example, a customer has called and needs two technicians to complete work. Group B is the new visit group:

  • 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/recommendations/visit-group-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitGroupId": "Group B",
  "timeWindows": [
    {
      "startTime": "2027-02-01T09:00:00Z",
      "endTime": "2027-02-01T13:00:00Z"
    },
    {
      "startTime": "2027-02-01T13:00:00Z",
      "endTime": "2027-02-01T17:00:00Z"
    }
  ],
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit A2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.77301, -84.43899],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit A1",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visitGroups": [
      {
        "id": "Group A",
        "visits": [
          {
            "id": "Visit A1",
            "name": "visit group A 1/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          },
          {
            "id": "Visit A2",
            "name": "visit group A 2/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          }
        ]
      },
      {
        "id": "Group B",
        "visits": [
          {
            "id": "Visit B1",
            "name": "visit group B 1/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H"
          },
          {
            "id": "Visit B2",
            "name": "visit group B 2/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H"
          }
        ]
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/20000medium/-37219soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/242soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-35091soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-2370soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:12:00Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT12M",
              "travelDistanceMetersFromPreviousStandstill": 9019,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:45:11Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT33M11S",
              "travelDistanceMetersFromPreviousStandstill": 38376,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H15M7S",
            "travelTimeFromStartLocationToFirstVisit": "PT12M",
            "travelTimeBetweenVisits": "PT33M11S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 82090,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
            "travelDistanceBetweenVisitsMeters": 38376,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T15:29:56Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:10:37Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT10M37S",
              "travelDistanceMetersFromPreviousStandstill": 8843,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT21M15S",
            "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT10M38S",
            "totalTravelDistanceMeters": 17677,
            "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 8834,
            "endLocationArrivalTime": "2027-02-01T11:22:38Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H55M25S",
        "travelTimeFromStartLocationToFirstVisit": "PT32M29S",
        "travelTimeBetweenVisits": "PT33M11S",
        "travelTimeFromLastVisitToEndLocation": "PT49M45S",
        "totalTravelDistanceMeters": 116328,
        "travelDistanceFromStartLocationToFirstVisitMeters": 27307,
        "travelDistanceBetweenVisitsMeters": 38376,
        "travelDistanceFromLastVisitToEndLocationMeters": 50645,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 3,
        "workingTimeFairnessPercentage": 73.65,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-9745hard/20000medium/-42113819soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/379soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "-5537hard/0medium/0soft",
          "constraintName": "Max shift end time (hard)",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-42080000soft",
          "constraintName": "Minimize the visit completion risk",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-31924soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-2274soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        },
        {
          "score": "-4208hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:10:37Z",
              "startServiceTime": "2027-02-01T15:35:04Z",
              "departureTime": "2027-02-01T17:35:04Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT10M37S",
              "travelDistanceMetersFromPreviousStandstill": 8843,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT21M15S",
            "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
            "travelTimeBetweenVisits": "PT0S",
            "travelTimeFromLastVisitToEndLocation": "PT10M38S",
            "totalTravelDistanceMeters": 17677,
            "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
            "travelDistanceBetweenVisitsMeters": 0,
            "travelDistanceFromLastVisitToEndLocationMeters": 8834,
            "endLocationArrivalTime": "2027-02-01T17:45:42Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:26:56Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T15:00:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT26M56S",
              "travelDistanceMetersFromPreviousStandstill": 29981,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit B2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T15:35:04Z",
              "startServiceTime": "2027-02-01T15:35:04Z",
              "departureTime": "2027-02-01T17:35:04Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT35M4S",
              "travelDistanceMetersFromPreviousStandstill": 40245,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H13M31S",
            "travelTimeFromStartLocationToFirstVisit": "PT26M56S",
            "travelTimeBetweenVisits": "PT35M4S",
            "travelTimeFromLastVisitToEndLocation": "PT11M31S",
            "totalTravelDistanceMeters": 78923,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29981,
            "travelDistanceBetweenVisitsMeters": 40245,
            "travelDistanceFromLastVisitToEndLocationMeters": 8697,
            "endLocationArrivalTime": "2027-02-01T17:46:35Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H53M49S",
        "travelTimeFromStartLocationToFirstVisit": "PT47M25S",
        "travelTimeBetweenVisits": "PT35M4S",
        "travelTimeFromLastVisitToEndLocation": "PT31M20S",
        "totalTravelDistanceMeters": 113161,
        "travelDistanceFromStartLocationToFirstVisitMeters": 48269,
        "travelDistanceBetweenVisitsMeters": 40245,
        "travelDistanceFromLastVisitToEndLocationMeters": 24647,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 3,
        "workingTimeFairnessPercentage": 88.95,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

The recommendation output for this example includes two recommendations:

  1. 09:00-13:00: Ann is assigned to Visit B1 and Beth is assigned to Visit B2.

  2. 13:00-17:00: Ann is assigned to Visit B2 and Beth is assigned to Visit B1.

The second recommendation breaks hard constraints and is not a feasible schedule.

recommendations visit group time window unsolved with existing visits

Because Ann already has a visit on the draft plan for the second recommendation, scheduling Visit B2 during this time window with Ann breaks the following hard constraints and is not a feasible schedule:

  • Max shift end time (hard)

  • Require service max end time

{
  "score": "-5537hard/0medium/0soft",
  "constraintName": "Max shift end time (hard)"
},
{
  "score": "-4208hard/0medium/0soft",
  "constraintName": "Require service max end time"
}
Depending on the urgency of the situation, it might be possible to reschedule the other visit group during that time window in Ann’s shift, so that Visit B2 can take this time window.

3. Visit Group recommendations for published route plans

If a customer visit needs to be added to a published schedule, the process for requesting recommendations is the same as above.

The following input dataset generated the schedule that is currently being executed:

  • 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": "Published sample schedule"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit B1",
                "kind": "VISIT"
              },
              {
                "id": "Visit A2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit B2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.77301, -84.43899],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit A1",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visitGroups": [
      {
        "id": "Group A",
        "visits": [
          {
            "id": "Visit A1",
            "name": "visit group A 1/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          },
          {
            "id": "Visit A2",
            "name": "visit group A 2/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          }
        ]
      },
      {
        "id": "Group B",
        "visits": [
          {
            "id": "Visit B1",
            "name": "visit group B 1/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          },
          {
            "id": "Visit B2",
            "name": "visit group B 2/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          }
        ]
      }
    ]
  }
}
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": "Published sample schedule",
    "submitDateTime": "2025-01-28T04:31:53.522648502Z",
    "startDateTime": "2025-01-28T04:32:00.35717356Z",
    "activeDateTime": "2025-01-28T04:32:00.670623262Z",
    "completeDateTime": "2025-01-28T04:37:00.750822425Z",
    "shutdownDateTime": "2025-01-28T04:37:01.205303486Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-123659soft",
    "tags": [],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit B1",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:12:00Z",
                "startServiceTime": "2027-02-01T09:12:00Z",
                "departureTime": "2027-02-01T11:12:00Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT12M",
                "travelDistanceMetersFromPreviousStandstill": 9019,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A2",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:45:11Z",
                "startServiceTime": "2027-02-01T13:00:00Z",
                "departureTime": "2027-02-01T15:00:00Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT33M11S",
                "travelDistanceMetersFromPreviousStandstill": 38376,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H15M7S",
              "travelTimeFromStartLocationToFirstVisit": "PT12M",
              "travelTimeBetweenVisits": "PT33M11S",
              "travelTimeFromLastVisitToEndLocation": "PT29M56S",
              "totalTravelDistanceMeters": 82090,
              "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
              "travelDistanceBetweenVisitsMeters": 38376,
              "travelDistanceFromLastVisitToEndLocationMeters": 34695,
              "endLocationArrivalTime": "2027-02-01T15:29:56Z",
              "technicianCosts": 0
            }
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit B2",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:10:37Z",
                "startServiceTime": "2027-02-01T09:12:00Z",
                "departureTime": "2027-02-01T11:12:00Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT10M37S",
                "travelDistanceMetersFromPreviousStandstill": 8843,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT21M15S",
              "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT10M38S",
              "totalTravelDistanceMeters": 17677,
              "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 8834,
              "endLocationArrivalTime": "2027-02-01T11:22:38Z",
              "technicianCosts": 0
            }
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A1",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:09:52Z",
                "startServiceTime": "2027-02-01T13:00:00Z",
                "departureTime": "2027-02-01T15:00:00Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT9M52S",
                "travelDistanceMetersFromPreviousStandstill": 9447,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT19M3S",
              "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT9M11S",
              "totalTravelDistanceMeters": 16563,
              "travelDistanceFromStartLocationToFirstVisitMeters": 9447,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 7116,
              "endLocationArrivalTime": "2027-02-01T15:09:11Z",
              "technicianCosts": 0
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT1H55M25S",
    "travelTimeFromStartLocationToFirstVisit": "PT32M29S",
    "travelTimeBetweenVisits": "PT33M11S",
    "travelTimeFromLastVisitToEndLocation": "PT49M45S",
    "totalTravelDistanceMeters": 116330,
    "travelDistanceFromStartLocationToFirstVisitMeters": 27309,
    "travelDistanceBetweenVisitsMeters": 38376,
    "travelDistanceFromLastVisitToEndLocationMeters": 50645,
    "totalUnassignedVisits": 0,
    "workingTimeFairnessPercentage": 73.65,
    "totalTechnicianCosts": 0
  }
}

modelOutput contains Ann’s, Beth’s, and Carl’s itineraries.

If a customer calls to request a technician on the same day, the following recommendations input dataset will return time window recommendations for the request (Group C):

  • 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/recommendations/visit-group-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitGroupId": "Group C",
  "timeWindows": [
    {
      "startTime": "2027-02-01T09:00:00Z",
      "endTime": "2027-02-01T13:00:00Z"
    },
    {
      "startTime": "2027-02-01T13:00:00Z",
      "endTime": "2027-02-01T17:00:00Z"
    }
  ],
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit B1",
                "kind": "VISIT"
              },
              {
                "id": "Visit A2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit B2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.77301, -84.43899],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit A1",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visitGroups": [
      {
        "id": "Group A",
        "visits": [
          {
            "id": "Visit A1",
            "name": "visit group A 1/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          },
          {
            "id": "Visit A2",
            "name": "visit group A 2/2",
            "location": [ 33.80209, -84.40296 ],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
              }
            ]
          }
        ]
      },
      {
        "id": "Group B",
        "visits": [
          {
            "id": "Visit B1",
            "name": "visit group B 1/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          },
          {
            "id": "Visit B2",
            "name": "visit group B 2/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT2H",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          }
        ]
      },
      {
        "id": "Group C",
        "visits": [
          {
            "id": "Visit C1",
            "name": "visit group C 1/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H"
          },
          {
            "id": "Visit C2",
            "name": "visit group C 2/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H"
          }
        ]
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/20000medium/-80850soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/141soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-75986soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-5005soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:12:00Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT12M",
              "travelDistanceMetersFromPreviousStandstill": 9019,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit C1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:35:23Z",
              "startServiceTime": "2027-02-01T11:35:23Z",
              "departureTime": "2027-02-01T12:35:23Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M23S",
              "travelDistanceMetersFromPreviousStandstill": 21217,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T13:22:38Z",
              "startServiceTime": "2027-02-01T13:22:38Z",
              "departureTime": "2027-02-01T15:22:38Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT47M15S",
              "travelDistanceMetersFromPreviousStandstill": 51350,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H52M34S",
            "travelTimeFromStartLocationToFirstVisit": "PT12M",
            "travelTimeBetweenVisits": "PT1H10M38S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 116281,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
            "travelDistanceBetweenVisitsMeters": 72567,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T15:52:34Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:10:37Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT10M37S",
              "travelDistanceMetersFromPreviousStandstill": 8843,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit C2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:35:23Z",
              "startServiceTime": "2027-02-01T11:35:23Z",
              "departureTime": "2027-02-01T12:35:23Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M23S",
              "travelDistanceMetersFromPreviousStandstill": 21217,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H7M13S",
            "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
            "travelTimeBetweenVisits": "PT23M23S",
            "travelTimeFromLastVisitToEndLocation": "PT33M13S",
            "totalTravelDistanceMeters": 59472,
            "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
            "travelDistanceBetweenVisitsMeters": 21217,
            "travelDistanceFromLastVisitToEndLocationMeters": 29412,
            "endLocationArrivalTime": "2027-02-01T13:08:36Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT3H18M50S",
        "travelTimeFromStartLocationToFirstVisit": "PT32M29S",
        "travelTimeBetweenVisits": "PT1H34M1S",
        "travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
        "totalTravelDistanceMeters": 192314,
        "travelDistanceFromStartLocationToFirstVisitMeters": 27307,
        "travelDistanceBetweenVisitsMeters": 93784,
        "travelDistanceFromLastVisitToEndLocationMeters": 71223,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 6,
        "assignedMandatoryVisits": 6,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 3,
        "workingTimeFairnessPercentage": 85.31,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-1031hard/20000medium/-80850soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/141soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "-1031hard/0medium/0soft",
          "constraintName": "Max shift end time (hard)",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-75986soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-5005soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:10:37Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT10M37S",
              "travelDistanceMetersFromPreviousStandstill": 8843,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit C1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:35:23Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T14:00:00Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M23S",
              "travelDistanceMetersFromPreviousStandstill": 21217,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H7M13S",
            "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
            "travelTimeBetweenVisits": "PT23M23S",
            "travelTimeFromLastVisitToEndLocation": "PT33M13S",
            "totalTravelDistanceMeters": 59472,
            "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
            "travelDistanceBetweenVisitsMeters": 21217,
            "travelDistanceFromLastVisitToEndLocationMeters": 29412,
            "endLocationArrivalTime": "2027-02-01T14:33:13Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit B1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:12:00Z",
              "startServiceTime": "2027-02-01T09:12:00Z",
              "departureTime": "2027-02-01T11:12:00Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT12M",
              "travelDistanceMetersFromPreviousStandstill": 9019,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit C2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:35:23Z",
              "startServiceTime": "2027-02-01T13:00:00Z",
              "departureTime": "2027-02-01T14:00:00Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M23S",
              "travelDistanceMetersFromPreviousStandstill": 21217,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit A2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T14:47:15Z",
              "startServiceTime": "2027-02-01T14:47:15Z",
              "departureTime": "2027-02-01T16:47:15Z",
              "effectiveServiceDuration": "PT2H",
              "travelTimeFromPreviousStandstill": "PT47M15S",
              "travelDistanceMetersFromPreviousStandstill": 51350,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H52M34S",
            "travelTimeFromStartLocationToFirstVisit": "PT12M",
            "travelTimeBetweenVisits": "PT1H10M38S",
            "travelTimeFromLastVisitToEndLocation": "PT29M56S",
            "totalTravelDistanceMeters": 116281,
            "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
            "travelDistanceBetweenVisitsMeters": 72567,
            "travelDistanceFromLastVisitToEndLocationMeters": 34695,
            "endLocationArrivalTime": "2027-02-01T17:17:11Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT3H18M50S",
        "travelTimeFromStartLocationToFirstVisit": "PT32M29S",
        "travelTimeBetweenVisits": "PT1H34M1S",
        "travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
        "totalTravelDistanceMeters": 192314,
        "travelDistanceFromStartLocationToFirstVisitMeters": 27307,
        "travelDistanceBetweenVisitsMeters": 93784,
        "travelDistanceFromLastVisitToEndLocationMeters": 71223,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 6,
        "assignedMandatoryVisits": 6,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 3,
        "workingTimeFairnessPercentage": 88.16,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

The recommendation output for this example includes two recommendations:

  1. 09:00-13:00: Ann is assigned to Visit C1 and Beth is assigned to Visit C2.

  2. 13:00-17:00: Beth is assigned to Visit C1 and Ann is assigned to Visit C2.

recommendations visit group time window published route plan

The second recommendation breaks hard constraints and would result in Ann arriving home after 17:00. This is not a feasible solution.

The customer can only accept the first recommendation.

After the customer has accepted the time window, create a real-time plan to include the visit in the optimized plan for the day. Failure to update the real-time plan will lead to errors with Visit Group C not being included if there are further updates to the plan throughout the day.

4. Include dependencies

The recommendations output only includes vehicle shifts that are directly impacted by the recommendations. However, there are times when recommendations also change vehicle shifts that are not part of the recommendations. For instance, some jobs require multiple visits in a specific order to complete the work (learn more about this topic in Visit dependencies).

In the previous example Visit Group A is changed to accommodate Visit Group B. Carl is assigned to Visit A1 which is part of Visit Group A.

To include all dependencies in the recommendations output, include "includeDependencies": "ALL" in the recommendations input dataset:

{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitGroupId": "Group C",
  "includeDependencies": "ALL"
}

"includeDependencies" can be set to ALL or NONE. The default value is NONE.

With "includeDependencies" set to true, the recommendation output from the previous example would also include Carl’s shift:

{
  "vehicleShifts": [
    {
      "id": "Ann-2027-02-01",
      "startTime": "2027-02-01T09:00:00Z",
      "itinerary": [
        {
          "id": "Visit B1",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T09:12:00Z",
          "startServiceTime": "2027-02-01T09:12:00Z",
          "departureTime": "2027-02-01T11:12:00Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT12M",
          "travelDistanceMetersFromPreviousStandstill": 9019,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        },
        {
          "id": "Visit C1",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T11:35:23Z",
          "startServiceTime": "2027-02-01T11:35:23Z",
          "departureTime": "2027-02-01T12:35:23Z",
          "effectiveServiceDuration": "PT1H",
          "travelTimeFromPreviousStandstill": "PT23M23S",
          "travelDistanceMetersFromPreviousStandstill": 21217,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        },
        {
          "id": "Visit A2",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T13:22:38Z",
          "startServiceTime": "2027-02-01T13:22:38Z",
          "departureTime": "2027-02-01T15:22:38Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT47M15S",
          "travelDistanceMetersFromPreviousStandstill": 51351,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        }
      ],
      "metrics": {
        "totalTravelTime": "PT1H52M34S",
        "travelTimeFromStartLocationToFirstVisit": "PT12M",
        "travelTimeBetweenVisits": "PT1H10M38S",
        "travelTimeFromLastVisitToEndLocation": "PT29M56S",
        "totalTravelDistanceMeters": 116282,
        "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
        "travelDistanceBetweenVisitsMeters": 72568,
        "travelDistanceFromLastVisitToEndLocationMeters": 34695,
        "endLocationArrivalTime": "2027-02-01T15:52:34Z",
        "technicianCosts": 0
      }
    },
    {
      "id": "Beth-2027-02-01",
      "startTime": "2027-02-01T09:00:00Z",
      "itinerary": [
        {
          "id": "Visit B2",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T09:10:37Z",
          "startServiceTime": "2027-02-01T09:12:00Z",
          "departureTime": "2027-02-01T11:12:00Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT10M37S",
          "travelDistanceMetersFromPreviousStandstill": 8843,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        },
        {
          "id": "Visit C2",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T11:35:23Z",
          "startServiceTime": "2027-02-01T11:35:23Z",
          "departureTime": "2027-02-01T12:35:23Z",
          "effectiveServiceDuration": "PT1H",
          "travelTimeFromPreviousStandstill": "PT23M23S",
          "travelDistanceMetersFromPreviousStandstill": 21217,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        }
      ],
      "metrics": {
        "totalTravelTime": "PT1H7M13S",
        "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
        "travelTimeBetweenVisits": "PT23M23S",
        "travelTimeFromLastVisitToEndLocation": "PT33M13S",
        "totalTravelDistanceMeters": 59472,
        "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
        "travelDistanceBetweenVisitsMeters": 21217,
        "travelDistanceFromLastVisitToEndLocationMeters": 29412,
        "endLocationArrivalTime": "2027-02-01T13:08:36Z",
        "technicianCosts": 0
      }
    }
  ],
  "kpis": {
    "totalTravelTime": "PT3H18M50S",
    "travelTimeFromStartLocationToFirstVisit": "PT32M29S",
    "travelTimeBetweenVisits": "PT1H34M1S",
    "travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
    "totalTravelDistanceMeters": 192317,
    "travelDistanceFromStartLocationToFirstVisitMeters": 27309,
    "travelDistanceBetweenVisitsMeters": 93785,
    "travelDistanceFromLastVisitToEndLocationMeters": 71223,
    "totalUnassignedVisits": 0,
    "workingTimeFairnessPercentage": 85.31,
    "totalTechnicianCosts": 0
  },
  "dependentVehicleShifts": [
    {
      "id": "Carl-2027-02-01",
      "startTime": "2027-02-01T09:00:00Z",
      "itinerary": [
        {
          "id": "Visit A1",
          "kind": "VISIT",
          "arrivalTime": "2027-02-01T09:09:52Z",
          "startServiceTime": "2027-02-01T13:22:38Z",
          "departureTime": "2027-02-01T15:22:38Z",
          "effectiveServiceDuration": "PT2H",
          "travelTimeFromPreviousStandstill": "PT9M52S",
          "travelDistanceMetersFromPreviousStandstill": 9447,
          "minStartTravelTime": "2027-02-01T00:00:00Z"
        }
      ],
      "metrics": {
        "totalTravelTime": "PT19M3S",
        "travelTimeFromStartLocationToFirstVisit": "PT9M52S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT9M11S",
        "totalTravelDistanceMeters": 16563,
        "travelDistanceFromStartLocationToFirstVisitMeters": 9447,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 7116,
        "endLocationArrivalTime": "2027-02-01T15:31:49Z",
        "technicianCosts": 0
      }
    }
  ]
}

5. Recommendations with pinned visits

When a plan has been published, it will often include visits that can’t be moved to accommodate new visits on the schedule.

Recommendations can reschedule visits and visit groups to accommodate new visits, however, visits can be pinned to prevent them from being moved.

In the following example there is 1 visit group and 2 visits that have been scheduled.

published route plan

The published schedule was previously generated from the following input dataset:

  • 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": "Published route plan"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H"
      }
    ],
    "visitGroups": [
      {
        "id": "Group C",
        "visits": [
          {
            "id": "Visit C1",
            "name": "visit group C 1/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H30M",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T12:00:00Z"
              }
            ]
          },
          {
            "id": "Visit C2",
            "name": "visit group C 2/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H30M",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T12:00:00Z"
              }
            ]
          }
        ]
      }
    ]
  }
}
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": "Published route plan",
    "submitDateTime": "2025-01-30T09:18:18.596847518Z",
    "startDateTime": "2025-01-30T09:18:25.217188031Z",
    "activeDateTime": "2025-01-30T09:18:25.935527926Z",
    "completeDateTime": "2025-01-30T09:23:26.007621776Z",
    "shutdownDateTime": "2025-01-30T09:23:26.278569611Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-210676soft",
    "tags": [],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit C1",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:25:22Z",
                "startServiceTime": "2027-02-01T09:33:20Z",
                "departureTime": "2027-02-01T11:03:20Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT25M22S",
                "travelDistanceMetersFromPreviousStandstill": 23325,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T12:00:28Z",
                "startServiceTime": "2027-02-01T12:00:28Z",
                "departureTime": "2027-02-01T13:00:28Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT57M8S",
                "travelDistanceMetersFromPreviousStandstill": 52223,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:32:13Z",
                "startServiceTime": "2027-02-01T13:32:13Z",
                "departureTime": "2027-02-01T14:32:13Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT31M45S",
                "travelDistanceMetersFromPreviousStandstill": 34237,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H22M4S",
              "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
              "travelTimeBetweenVisits": "PT1H28M53S",
              "travelTimeFromLastVisitToEndLocation": "PT27M49S",
              "totalTravelDistanceMeters": 139006,
              "travelDistanceFromStartLocationToFirstVisitMeters": 23325,
              "travelDistanceBetweenVisitsMeters": 86460,
              "travelDistanceFromLastVisitToEndLocationMeters": 29221,
              "endLocationArrivalTime": "2027-02-01T15:00:02Z",
              "technicianCosts": 0
            }
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit C2",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:33:20Z",
                "startServiceTime": "2027-02-01T09:33:20Z",
                "departureTime": "2027-02-01T11:03:20Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT33M20S",
                "travelDistanceMetersFromPreviousStandstill": 29441,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H6M33S",
              "travelTimeFromStartLocationToFirstVisit": "PT33M20S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT33M13S",
              "totalTravelDistanceMeters": 58853,
              "travelDistanceFromStartLocationToFirstVisitMeters": 29441,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 29412,
              "endLocationArrivalTime": "2027-02-01T11:36:33Z",
              "technicianCosts": 0
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT3H28M37S",
    "travelTimeFromStartLocationToFirstVisit": "PT58M42S",
    "travelTimeBetweenVisits": "PT1H28M53S",
    "travelTimeFromLastVisitToEndLocation": "PT1H1M2S",
    "totalTravelDistanceMeters": 197859,
    "travelDistanceFromStartLocationToFirstVisitMeters": 52766,
    "travelDistanceBetweenVisitsMeters": 86460,
    "travelDistanceFromLastVisitToEndLocationMeters": 58633,
    "totalUnassignedVisits": 0,
    "workingTimeFairnessPercentage": 60.6,
    "totalTechnicianCosts": 0
  }
}

modelOutput contains Ann’s, Beth’s, and Carl’s itineraries.

If a customer calls and needs an emergency visit to be added to the schedule, the process for requesting recommendations is the same as above, however, any visits that can’t be moved as part of the recommendation can be pinned.

In this example, Visit D cannot be moved. This visit can be pinned, which prevents the visit from being rescheduled.

Any visit a technician is scheduled to make before a pinned visit, must also be pinned.

Learn more about pinning in the Real-time planning: pinning visits guide.

In this example, Ann is scheduled to complete the following visits: Visit C1, Visit D, and Visit B. Visit C1 is before Visit D and cannot be rescheduled, so it is also pinned, and as Visit C1 is part of a visit group, the other visit in the group (Visit C2) should also be pinned.

To pin visits, add "pinningRequested": true to each visit, and add the "minStartTravelTime" from the previous output dataset to each visit.

{
  "visits": [
    {
      "id": "Visit D",
      "location": [33.90719, -84.28149],
      "serviceDuration": "PT1H",
      "minStartTravelTime": "2027-02-01T00:00:00Z",
      "pinningRequested": true
    }
  ]
}

If a customer calls to schedule a visit group on the same day (Visit Group E), the following recommendations input dataset will provide recommendations for the visit group. Because Visit C1, Visit C2, and Visit D are pinned, the recommendations will not reschedule them.

  • 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/recommendations/visit-group-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitGroupId": "Group E",
  "timeWindows": [
    {
      "startTime": "2027-02-01T09:00:00Z",
      "endTime": "2027-02-01T13:00:00Z"
    },
    {
      "startTime": "2027-02-01T13:00:00Z",
      "endTime": "2027-02-01T17:00:00Z"
    }
  ],
  "modelInput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit C1",
                "kind": "VISIT"
              },
              {
                "id": "Visit D",
                "kind": "VISIT"
              },
              {
                "id": "Visit B",
                "kind": "VISIT"
              }

            ]
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit C2",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H",
        "minStartTravelTime": "2027-02-01T00:00:00Z",
        "pinningRequested": true
      }
    ],
    "visitGroups": [
      {
        "id": "Group C",
        "visits": [
          {
            "id": "Visit C1",
            "name": "visit group C 1/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H30M",
            "minStartTravelTime": "2027-02-01T00:00:00Z",
            "pinningRequested": true,
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          },
          {
            "id": "Visit C2",
            "name": "visit group C 2/2",
            "location": [33.51744, -84.12329],
            "serviceDuration": "PT1H30M",
            "minStartTravelTime": "2027-02-01T00:00:00Z",
            "pinningRequested": true,
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T09:00:00Z",
                "maxEndTime": "2027-02-01T13:00:00Z"
              }
            ]
          }
        ]
      },
      {
        "id": "Group E",
        "visits": [
          {
            "id": "Visit E1",
            "name": "visit group E 1/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT1H"
          },
          {
            "id": "Visit E2",
            "name": "visit group E 2/2",
            "location": [33.67590, -84.11845],
            "serviceDuration": "PT1H"
          }
        ]
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/20000medium/-16989soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/299soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-16194soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-1094soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit C2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:33:20Z",
              "startServiceTime": "2027-02-01T09:33:20Z",
              "departureTime": "2027-02-01T11:03:20Z",
              "effectiveServiceDuration": "PT1H30M",
              "travelTimeFromPreviousStandstill": "PT33M20S",
              "travelDistanceMetersFromPreviousStandstill": 29441,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit E1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:26:42Z",
              "startServiceTime": "2027-02-01T15:05:57Z",
              "departureTime": "2027-02-01T16:05:57Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M22S",
              "travelDistanceMetersFromPreviousStandstill": 21307,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H7M20S",
            "travelTimeFromStartLocationToFirstVisit": "PT33M20S",
            "travelTimeBetweenVisits": "PT23M22S",
            "travelTimeFromLastVisitToEndLocation": "PT10M38S",
            "totalTravelDistanceMeters": 59582,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29441,
            "travelDistanceBetweenVisitsMeters": 21307,
            "travelDistanceFromLastVisitToEndLocationMeters": 8834,
            "endLocationArrivalTime": "2027-02-01T16:16:35Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit C1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:25:22Z",
              "startServiceTime": "2027-02-01T09:33:20Z",
              "departureTime": "2027-02-01T11:03:20Z",
              "effectiveServiceDuration": "PT1H30M",
              "travelTimeFromPreviousStandstill": "PT25M22S",
              "travelDistanceMetersFromPreviousStandstill": 23324,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit D",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T12:00:28Z",
              "startServiceTime": "2027-02-01T12:00:28Z",
              "departureTime": "2027-02-01T13:00:28Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT57M8S",
              "travelDistanceMetersFromPreviousStandstill": 52223,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit B",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T13:32:12Z",
              "startServiceTime": "2027-02-01T13:32:12Z",
              "departureTime": "2027-02-01T14:32:12Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT31M44S",
              "travelDistanceMetersFromPreviousStandstill": 34236,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit E2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T15:05:57Z",
              "startServiceTime": "2027-02-01T15:05:57Z",
              "departureTime": "2027-02-01T16:05:57Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT33M45S",
              "travelDistanceMetersFromPreviousStandstill": 35989,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT2H39M30S",
            "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
            "travelTimeBetweenVisits": "PT2H2M37S",
            "travelTimeFromLastVisitToEndLocation": "PT11M31S",
            "totalTravelDistanceMeters": 154469,
            "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
            "travelDistanceBetweenVisitsMeters": 122448,
            "travelDistanceFromLastVisitToEndLocationMeters": 8697,
            "endLocationArrivalTime": "2027-02-01T16:17:28Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT3H46M50S",
        "travelTimeFromStartLocationToFirstVisit": "PT58M42S",
        "travelTimeBetweenVisits": "PT2H25M59S",
        "travelTimeFromLastVisitToEndLocation": "PT22M9S",
        "totalTravelDistanceMeters": 214051,
        "travelDistanceFromStartLocationToFirstVisitMeters": 52765,
        "travelDistanceBetweenVisitsMeters": 143755,
        "travelDistanceFromLastVisitToEndLocationMeters": 17531,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 6,
        "assignedMandatoryVisits": 6,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 99.89,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-11422hard/20000medium/-38865soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/137soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-36766soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/0medium/-2236soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 2
        },
        {
          "score": "0hard/20000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -2
        },
        {
          "score": "-11422hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 2
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShifts": [
        {
          "id": "Beth-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit C2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:33:20Z",
              "startServiceTime": "2027-02-01T09:33:20Z",
              "departureTime": "2027-02-01T11:03:20Z",
              "effectiveServiceDuration": "PT1H30M",
              "travelTimeFromPreviousStandstill": "PT33M20S",
              "travelDistanceMetersFromPreviousStandstill": 29441,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit E1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T11:26:42Z",
              "startServiceTime": "2027-02-01T13:35:11Z",
              "departureTime": "2027-02-01T14:35:11Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT23M22S",
              "travelDistanceMetersFromPreviousStandstill": 21307,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT1H7M20S",
            "travelTimeFromStartLocationToFirstVisit": "PT33M20S",
            "travelTimeBetweenVisits": "PT23M22S",
            "travelTimeFromLastVisitToEndLocation": "PT10M38S",
            "totalTravelDistanceMeters": 59582,
            "travelDistanceFromStartLocationToFirstVisitMeters": 29441,
            "travelDistanceBetweenVisitsMeters": 21307,
            "travelDistanceFromLastVisitToEndLocationMeters": 8834,
            "endLocationArrivalTime": "2027-02-01T14:45:49Z",
            "technicianCosts": null,
            "overtime": null
          }
        },
        {
          "id": "Ann-2027-02-01",
          "startTime": "2027-02-01T09:00:00Z",
          "itinerary": [
            {
              "id": "Visit C1",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T09:25:22Z",
              "startServiceTime": "2027-02-01T09:33:20Z",
              "departureTime": "2027-02-01T11:03:20Z",
              "effectiveServiceDuration": "PT1H30M",
              "travelTimeFromPreviousStandstill": "PT25M22S",
              "travelDistanceMetersFromPreviousStandstill": 23324,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit D",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T12:00:28Z",
              "startServiceTime": "2027-02-01T12:00:28Z",
              "departureTime": "2027-02-01T13:00:28Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT57M8S",
              "travelDistanceMetersFromPreviousStandstill": 52223,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit E2",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T13:35:11Z",
              "startServiceTime": "2027-02-01T13:35:11Z",
              "departureTime": "2027-02-01T14:35:11Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT34M43S",
              "travelDistanceMetersFromPreviousStandstill": 31571,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            },
            {
              "id": "Visit B",
              "kind": "VISIT",
              "arrivalTime": "2027-02-01T15:08:41Z",
              "startServiceTime": "2027-02-01T15:08:41Z",
              "departureTime": "2027-02-01T16:08:41Z",
              "effectiveServiceDuration": "PT1H",
              "travelTimeFromPreviousStandstill": "PT33M30S",
              "travelDistanceMetersFromPreviousStandstill": 38702,
              "minStartTravelTime": "2027-02-01T00:00:00Z"
            }
          ],
          "metrics": {
            "totalTravelTime": "PT2H58M32S",
            "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
            "travelTimeBetweenVisits": "PT2H5M21S",
            "travelTimeFromLastVisitToEndLocation": "PT27M49S",
            "totalTravelDistanceMeters": 175041,
            "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
            "travelDistanceBetweenVisitsMeters": 122496,
            "travelDistanceFromLastVisitToEndLocationMeters": 29221,
            "endLocationArrivalTime": "2027-02-01T16:36:30Z",
            "technicianCosts": null,
            "overtime": null
          }
        }
      ],
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT4H5M52S",
        "travelTimeFromStartLocationToFirstVisit": "PT58M42S",
        "travelTimeBetweenVisits": "PT2H28M43S",
        "travelTimeFromLastVisitToEndLocation": "PT38M27S",
        "totalTravelDistanceMeters": 234623,
        "travelDistanceFromStartLocationToFirstVisitMeters": 52765,
        "travelDistanceBetweenVisitsMeters": 143803,
        "travelDistanceFromLastVisitToEndLocationMeters": 38055,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 6,
        "assignedMandatoryVisits": 6,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 86.2,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

Only one of the recommendations does not break hard constraints and results in the following schedule with visits C1, C2, and D unaffected by the recommendation.

recommendation with pinning

Learn more about pinning in the Real-time planning: pinning visits guide.

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 Visit time window recommendations for individual visits.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default