Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Field Service Routing
  • Recommendations
  • Visit 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 time window recommendations

Recommendations are a useful way of populating a draft plan.

This guide explains recommendations with the following examples:

  • Recommendations for unsolved route plans

  • Recommendations for unsolved route plans with existing visits

  • Recommendations for solved 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. 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 the technicians whose vehicle shifts overlap the specified time window and make the number of recommendations specified in maxNumberOfRecommendationsPerTimeWindow.

Each recommendation relates to a technician’s vehicle shift. If there are 5 technicians and the maxNumberOfRecommendationsPerTimeWindow is set to 2, only 2 vehicle shifts will be included in the recommendations per time window.

{
  "maxNumberOfRecommendationsPerTimeWindow": 2
}

1.1.2. Fit visit ID

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

{
  "fitVisitId": "Visit 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 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"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT2H"
      }
    ]
  }
}

1.2. Submit the input dataset

Submit the recommendations input dataset to the API endpoint: /v1/route-plans/recommendations/visit-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-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-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-recommend-time-windows?includeJustifications=true [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitId": "Visit 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"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT2H"
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/10000medium/-117593soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-322soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-111336soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-5935soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:48:57Z",
            "startServiceTime": "2027-02-01T09:48:57Z",
            "departureTime": "2027-02-01T11:48:57Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT48M57S",
            "travelDistanceMetersFromPreviousStandstill": 54141,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H38M55S",
          "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 111336,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T12:38:55Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H38M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT49M58S",
        "totalTravelDistanceMeters": 111336,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 57195,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-117875soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-604soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-111336soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-5935soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:48:57Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT48M57S",
            "travelDistanceMetersFromPreviousStandstill": 54141,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H38M55S",
          "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 111336,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T15:49:58Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H38M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT49M58S",
        "totalTravelDistanceMeters": 111336,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 57195,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-137632soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-343soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-130532soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-6757soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:55:56Z",
            "startServiceTime": "2027-02-01T09:55:56Z",
            "departureTime": "2027-02-01T11:55:56Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M56S",
            "travelDistanceMetersFromPreviousStandstill": 64909,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H52M37S",
          "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT56M41S",
          "totalTravelDistanceMeters": 130532,
          "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 65623,
          "endLocationArrivalTime": "2027-02-01T12:52:37Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H52M37S",
        "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT56M41S",
        "totalTravelDistanceMeters": 130532,
        "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 65623,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-137903soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-614soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-130532soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-6757soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:55:56Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M56S",
            "travelDistanceMetersFromPreviousStandstill": 64909,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H52M37S",
          "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT56M41S",
          "totalTravelDistanceMeters": 130532,
          "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 65623,
          "endLocationArrivalTime": "2027-02-01T15:56:41Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H52M37S",
        "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT56M41S",
        "totalTravelDistanceMeters": 130532,
        "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 65623,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/10000medium/-117593soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-322soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-322soft",
              "justification": {
                "unfairnessScore": 322,
                "description": "Unfairness score for the time utilization of vehicles '322'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-111336soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-111336soft",
              "justification": {
                "visitId": "Visit A",
                "travelDistanceMeters": 111336,
                "description": "Visit (Visit A) has a travel distance of '111336' meters."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-5935soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-5935soft",
              "justification": {
                "visitId": "Visit A",
                "travelTime": "PT1H38M55S",
                "description": "Visit (Visit A) has travel time of 'PT1H38M55S'."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A",
                "description": "Mandatory visit (Visit A) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:48:57Z",
            "startServiceTime": "2027-02-01T09:48:57Z",
            "departureTime": "2027-02-01T11:48:57Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT48M57S",
            "travelDistanceMetersFromPreviousStandstill": 54141,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H38M55S",
          "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 111336,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T12:38:55Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H38M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT49M58S",
        "totalTravelDistanceMeters": 111336,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 57195,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-117875soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-604soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-604soft",
              "justification": {
                "unfairnessScore": 604,
                "description": "Unfairness score for the time utilization of vehicles '604'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-111336soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-111336soft",
              "justification": {
                "visitId": "Visit A",
                "travelDistanceMeters": 111336,
                "description": "Visit (Visit A) has a travel distance of '111336' meters."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-5935soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-5935soft",
              "justification": {
                "visitId": "Visit A",
                "travelTime": "PT1H38M55S",
                "description": "Visit (Visit A) has travel time of 'PT1H38M55S'."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A",
                "description": "Mandatory visit (Visit A) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:48:57Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT48M57S",
            "travelDistanceMetersFromPreviousStandstill": 54141,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H38M55S",
          "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 111336,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T15:49:58Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H38M55S",
        "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT49M58S",
        "totalTravelDistanceMeters": 111336,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 57195,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-137632soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-343soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-343soft",
              "justification": {
                "unfairnessScore": 343,
                "description": "Unfairness score for the time utilization of vehicles '343'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-130532soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-130532soft",
              "justification": {
                "visitId": "Visit A",
                "travelDistanceMeters": 130532,
                "description": "Visit (Visit A) has a travel distance of '130532' meters."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-6757soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-6757soft",
              "justification": {
                "visitId": "Visit A",
                "travelTime": "PT1H52M37S",
                "description": "Visit (Visit A) has travel time of 'PT1H52M37S'."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A",
                "description": "Mandatory visit (Visit A) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:55:56Z",
            "startServiceTime": "2027-02-01T09:55:56Z",
            "departureTime": "2027-02-01T11:55:56Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M56S",
            "travelDistanceMetersFromPreviousStandstill": 64909,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H52M37S",
          "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT56M41S",
          "totalTravelDistanceMeters": 130532,
          "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 65623,
          "endLocationArrivalTime": "2027-02-01T12:52:37Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H52M37S",
        "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT56M41S",
        "totalTravelDistanceMeters": 130532,
        "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 65623,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-137903soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-614soft",
          "constraintName": "Balance time utilization",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-614soft",
              "justification": {
                "unfairnessScore": 614,
                "description": "Unfairness score for the time utilization of vehicles '614'."
              }
            },
            {
              "score": "0hard/0medium/0soft",
              "justification": {
                "unfairnessScore": 0,
                "description": "Unfairness score for the time utilization of vehicles '0'."
              }
            }
          ],
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-130532soft",
          "constraintName": "Minimize travel distance",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-130532soft",
              "justification": {
                "visitId": "Visit A",
                "travelDistanceMeters": 130532,
                "description": "Visit (Visit A) has a travel distance of '130532' meters."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-6757soft",
          "constraintName": "Minimize travel time",
          "matchesDiff": [
            {
              "score": "0hard/0medium/-6757soft",
              "justification": {
                "visitId": "Visit A",
                "travelTime": "PT1H52M37S",
                "description": "Visit (Visit A) has travel time of 'PT1H52M37S'."
              }
            }
          ],
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchesDiff": [
            {
              "score": "0hard/10000medium/0soft",
              "justification": {
                "visitId": "Visit A",
                "description": "Mandatory visit (Visit A) has been left unassigned."
              }
            }
          ],
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:55:56Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M56S",
            "travelDistanceMetersFromPreviousStandstill": 64909,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H52M37S",
          "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
          "travelTimeBetweenVisits": "PT0S",
          "travelTimeFromLastVisitToEndLocation": "PT56M41S",
          "totalTravelDistanceMeters": 130532,
          "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
          "travelDistanceBetweenVisitsMeters": 0,
          "travelDistanceFromLastVisitToEndLocationMeters": 65623,
          "endLocationArrivalTime": "2027-02-01T15:56:41Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H52M37S",
        "travelTimeFromStartLocationToFirstVisit": "PT55M56S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT56M41S",
        "totalTravelDistanceMeters": 130532,
        "travelDistanceFromStartLocationToFirstVisitMeters": 64909,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 65623,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 1,
        "assignedMandatoryVisits": 1,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

1.3. The recommendations

In this example, there are a maximum of 2 recommendations per time window. Each recommendation relates to a technician’s vehicle shift. We have 2 time windows and 2 vehicle shifts for a total of 4 recommendations for the visit.

recommendations visit time window unsolved

Each recommendation includes the following:

1.3.1. ScoreDiff

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

{
  "scoreDiff": "0hard/10000medium/-117593soft"
}

1.3.2. Constraint scores without justifications

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

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

1.3.3. Constraint scores with justifications

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

{
  "score": "0hard/0medium/-111336soft",
  "constraintName": "Minimize travel distance",
  "matchesDiff": [
    {
      "score": "0hard/0medium/-111336soft",
      "justification": {
        "visitId": "Visit A",
        "travelDistanceMeters": 111336,
        "description": "Visit (Visit A) has a travel distance of '111336' meters."
      }
    }
  ],
  "matchCountDiff": 1
}

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 shift

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

{
  "vehicleShift": {
    "id": "Ann-2027-02-01",
    "startTime": "2027-02-01T09:00:00Z",
    "itinerary": [
      {
        "id": "Visit A",
        "kind": "VISIT",
        "arrivalTime": "2027-02-01T09:48:57Z",
        "startServiceTime": "2027-02-01T09:48:57Z",
        "departureTime": "2027-02-01T11:48:57Z",
        "effectiveServiceDuration": "PT2H",
        "travelTimeFromPreviousStandstill": "PT48M57S",
        "travelDistanceMetersFromPreviousStandstill": 54141,
        "minStartTravelTime": "2027-02-01T00:00:00Z"
      }
    ],
    "metrics": {
      "totalTravelTime": "PT1H38M55S",
      "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
      "travelTimeBetweenVisits": "PT0S",
      "travelTimeFromLastVisitToEndLocation": "PT49M58S",
      "totalTravelDistanceMeters": 111336,
      "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
      "travelDistanceBetweenVisitsMeters": 0,
      "travelDistanceFromLastVisitToEndLocationMeters": 57195,
      "endLocationArrivalTime": "2027-02-01T12:38:55Z",
      "technicianCosts": null,
      "overtime": null
    }
  },
  "dependentVehicleShifts": [],
  "kpis": {
    "totalTravelTime": "PT1H38M55S",
    "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT49M58S",
    "totalTravelDistanceMeters": 111336,
    "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 57195,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 1,
    "assignedMandatoryVisits": 1,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 0.0,
    "totalTechnicianCosts": null,
    "totalOvertime": null
  }
}

1.4. Implementing the recommendation

The recommendation output for this example includes four recommendations:

  1. Between 09:00 and 13:00 on February 1st with technician Ann.

  2. Between 13:00 and 17:00 on February 1st with technician Ann.

  3. Between 09:00 and 13:00 on February 1st with technician Beth.

  4. Between 13:00 and 17:00 on February 1st with technician Beth.

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:

  • Between 13:00 and 17:00 on February 1st with technician Ann.

First, add the visit to Ann’s itinerary:

{
  "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 A",
          "kind": "VISIT"
        }
      ]
    }
  ]
}

Next, add the time window to the visit:

{
  "id": "Visit A",
  "location": [33.84475, -84.63649],
  "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. 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 a time window for Visit B:

  • 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-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitId": "Visit 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 A",
                "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"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location":  [33.67590, -84.11845],
        "serviceDuration": "PT2H"
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/10000medium/-18406soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/546soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-17677soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-1275soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:10:37Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00: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-01T15:10:38Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H10S",
        "travelTimeFromStartLocationToFirstVisit": "PT59M34S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT1H36S",
        "totalTravelDistanceMeters": 129013,
        "travelDistanceFromStartLocationToFirstVisitMeters": 62984,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 66029,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 94.96,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-18507soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-17413soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-1094soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit B",
            "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 A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T12:07:11Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M11S",
            "travelDistanceMetersFromPreviousStandstill": 62535,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H57M9S",
          "travelTimeFromStartLocationToFirstVisit": "PT12M",
          "travelTimeBetweenVisits": "PT55M11S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 128749,
          "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
          "travelDistanceBetweenVisitsMeters": 62535,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T15:49:58Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H57M9S",
        "travelTimeFromStartLocationToFirstVisit": "PT12M",
        "travelTimeBetweenVisits": "PT55M11S",
        "travelTimeFromLastVisitToEndLocation": "PT49M58S",
        "totalTravelDistanceMeters": 128749,
        "travelDistanceFromStartLocationToFirstVisitMeters": 9019,
        "travelDistanceBetweenVisitsMeters": 62535,
        "travelDistanceFromLastVisitToEndLocationMeters": 57195,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-18744soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/208soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-17677soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-1275soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:10:37Z",
            "startServiceTime": "2027-02-01T09:10:37Z",
            "departureTime": "2027-02-01T11:10:37Z",
            "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:21:15Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H10S",
        "travelTimeFromStartLocationToFirstVisit": "PT59M34S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT1H36S",
        "totalTravelDistanceMeters": 129013,
        "travelDistanceFromStartLocationToFirstVisitMeters": 62984,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 66029,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 51.25,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-7303hard/10000medium/-33075447soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-201soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "-3997hard/0medium/0soft",
          "constraintName": "Max shift end time (hard)",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-33060000soft",
          "constraintName": "Minimize the visit completion risk",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-14247soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-999soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        },
        {
          "score": "-3306hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:48:57Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T15:00:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT48M57S",
            "travelDistanceMetersFromPreviousStandstill": 54141,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T15:55:06Z",
            "startServiceTime": "2027-02-01T15:55:06Z",
            "departureTime": "2027-02-01T17:55:06Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT55M6S",
            "travelDistanceMetersFromPreviousStandstill": 62745,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT1H55M34S",
          "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
          "travelTimeBetweenVisits": "PT55M6S",
          "travelTimeFromLastVisitToEndLocation": "PT11M31S",
          "totalTravelDistanceMeters": 125583,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
          "travelDistanceBetweenVisitsMeters": 62745,
          "travelDistanceFromLastVisitToEndLocationMeters": 8697,
          "endLocationArrivalTime": "2027-02-01T18:06:37Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT1H55M34S",
        "travelTimeFromStartLocationToFirstVisit": "PT48M57S",
        "travelTimeBetweenVisits": "PT55M6S",
        "travelTimeFromLastVisitToEndLocation": "PT11M31S",
        "totalTravelDistanceMeters": 125583,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
        "travelDistanceBetweenVisitsMeters": 62745,
        "travelDistanceFromLastVisitToEndLocationMeters": 8697,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 2,
        "assignedMandatoryVisits": 2,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 0.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

The recommendation output for this example includes four recommendations:

  1. Between 13:00 and 17:00 on February 1st with technician Beth.

  2. Between 09:00 and 13:00 on February 1st with technician Ann.

  3. Between 09:00 and 13:00 on February 1st with technician Beth.

  4. Between 13:00 and 17:00 on February 1st with technician Ann.

recommendations visit time window unsolved 2

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

  • Max shift end time

  • Require service max end time

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

3. Recommendations for solved 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": "Recommendations 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 D",
                "kind": "VISIT"
              },
              {
                "id": "Visit A",
                "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 B",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.67590, -84.11845],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T13:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.51744, -84.12329],
        "serviceDuration": "PT2H"
      },
      {
        "id": "Visit D",
        "location": [33.84954, -84.52085],
        "serviceDuration": "PT1H",
        "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": "Recommendations sample schedule",
    "submitDateTime": "2025-01-17T06:35:26.852340505Z",
    "startDateTime": "2025-01-17T06:35:33.653699216Z",
    "activeDateTime": "2025-01-17T06:35:33.972843058Z",
    "completeDateTime": "2025-01-17T06:40:34.032295161Z",
    "shutdownDateTime": "2025-01-17T06:40:34.363235655Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-186900soft",
    "tags": [],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:41:09Z",
                "startServiceTime": "2027-02-01T09:41:09Z",
                "departureTime": "2027-02-01T10:41:09Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT41M9S",
                "travelDistanceMetersFromPreviousStandstill": 45639,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:59:06Z",
                "startServiceTime": "2027-02-01T13:00:00Z",
                "departureTime": "2027-02-01T15:00:00Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT17M57S",
                "travelDistanceMetersFromPreviousStandstill": 13866,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H49M4S",
              "travelTimeFromStartLocationToFirstVisit": "PT41M9S",
              "travelTimeBetweenVisits": "PT17M57S",
              "travelTimeFromLastVisitToEndLocation": "PT49M58S",
              "totalTravelDistanceMeters": 116700,
              "travelDistanceFromStartLocationToFirstVisitMeters": 45639,
              "travelDistanceBetweenVisitsMeters": 13866,
              "travelDistanceFromLastVisitToEndLocationMeters": 57195,
              "endLocationArrivalTime": "2027-02-01T15:49:58Z"
            }
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:10:37Z",
                "startServiceTime": "2027-02-01T09:10:37Z",
                "departureTime": "2027-02-01T11:10:37Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT10M37S",
                "travelDistanceMetersFromPreviousStandstill": 8843,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:34:00Z",
                "startServiceTime": "2027-02-01T11:34:00Z",
                "departureTime": "2027-02-01T13:34:00Z",
                "effectiveServiceDuration": "PT2H",
                "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:07:13Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT2H56M17S",
    "travelTimeFromStartLocationToFirstVisit": "PT51M46S",
    "travelTimeBetweenVisits": "PT41M20S",
    "travelTimeFromLastVisitToEndLocation": "PT1H23M11S",
    "totalTravelDistanceMeters": 176172,
    "travelDistanceFromStartLocationToFirstVisitMeters": 54482,
    "travelDistanceBetweenVisitsMeters": 35083,
    "travelDistanceFromLastVisitToEndLocationMeters": 86607,
    "totalUnassignedVisits": 0,
    "workingTimeFairnessPercentage": 85.67
  }
}

modelOutput contains Ann’s and Beth’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 (Visit E):

  • 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-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitId": "Visit 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 D",
                "kind": "VISIT"
              },
              {
                "id": "Visit A",
                "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 B",
                "kind": "VISIT"
              },
              {
                "id": "Visit C",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.67590, -84.11845],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T13:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.51744, -84.12329],
        "serviceDuration": "PT2H"
      },
      {
        "id": "Visit D",
        "location": [33.84954, -84.52085],
        "serviceDuration": "PT1H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T13:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.36302, -84.34906],
        "serviceDuration": "PT1H"
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/10000medium/-69563soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/112soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-65510soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-4165soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:10:37Z",
            "startServiceTime": "2027-02-01T09:10:37Z",
            "departureTime": "2027-02-01T11:10:37Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT10M37S",
            "travelDistanceMetersFromPreviousStandstill": 8843,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:34:00Z",
            "startServiceTime": "2027-02-01T11:34:00Z",
            "departureTime": "2027-02-01T13:34:00Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT23M23S",
            "travelDistanceMetersFromPreviousStandstill": 21217,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T14:12:11Z",
            "startServiceTime": "2027-02-01T14:12:11Z",
            "departureTime": "2027-02-01T15:12:11Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT38M11S",
            "travelDistanceMetersFromPreviousStandstill": 34234,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT2H16M38S",
          "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
          "travelTimeBetweenVisits": "PT1H1M34S",
          "travelTimeFromLastVisitToEndLocation": "PT1H4M27S",
          "totalTravelDistanceMeters": 124982,
          "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
          "travelDistanceBetweenVisitsMeters": 55451,
          "travelDistanceFromLastVisitToEndLocationMeters": 60688,
          "endLocationArrivalTime": "2027-02-01T16:16:38Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT4H5M42S",
        "travelTimeFromStartLocationToFirstVisit": "PT51M46S",
        "travelTimeBetweenVisits": "PT1H19M31S",
        "travelTimeFromLastVisitToEndLocation": "PT1H54M25S",
        "totalTravelDistanceMeters": 241682,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54482,
        "travelDistanceBetweenVisitsMeters": 69317,
        "travelDistanceFromLastVisitToEndLocationMeters": 117883,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 5,
        "assignedMandatoryVisits": 5,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 96.85,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-145150soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-97soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-137448soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-7605soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:41:09Z",
            "startServiceTime": "2027-02-01T09:41:09Z",
            "departureTime": "2027-02-01T10:41:09Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT41M9S",
            "travelDistanceMetersFromPreviousStandstill": 45639,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:48:39Z",
            "startServiceTime": "2027-02-01T11:48:39Z",
            "departureTime": "2027-02-01T12:48:39Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT1H7M30S",
            "travelDistanceMetersFromPreviousStandstill": 70970,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T14:05:51Z",
            "startServiceTime": "2027-02-01T14:05:51Z",
            "departureTime": "2027-02-01T16:05:51Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT1H17M12S",
            "travelDistanceMetersFromPreviousStandstill": 80344,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT3H55M49S",
          "travelTimeFromStartLocationToFirstVisit": "PT41M9S",
          "travelTimeBetweenVisits": "PT2H24M42S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 254148,
          "travelDistanceFromStartLocationToFirstVisitMeters": 45639,
          "travelDistanceBetweenVisitsMeters": 151314,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T16:55:49Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT5H3M2S",
        "travelTimeFromStartLocationToFirstVisit": "PT51M46S",
        "travelTimeBetweenVisits": "PT2H48M5S",
        "travelTimeFromLastVisitToEndLocation": "PT1H23M11S",
        "totalTravelDistanceMeters": 313620,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54482,
        "travelDistanceBetweenVisitsMeters": 172531,
        "travelDistanceFromLastVisitToEndLocationMeters": 86607,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 5,
        "assignedMandatoryVisits": 5,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 78.46,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-310hard/10000medium/-85892soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-34soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "0hard/0medium/-80817soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-5041soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        },
        {
          "score": "-310hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:56:46Z",
            "startServiceTime": "2027-02-01T09:56:46Z",
            "departureTime": "2027-02-01T10:56:46Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT56M46S",
            "travelDistanceMetersFromPreviousStandstill": 54627,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T12:05:10Z",
            "startServiceTime": "2027-02-01T12:05:10Z",
            "departureTime": "2027-02-01T13:05:10Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT1H8M24S",
            "travelDistanceMetersFromPreviousStandstill": 71829,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit A",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T13:23:07Z",
            "startServiceTime": "2027-02-01T13:23:07Z",
            "departureTime": "2027-02-01T15:23:07Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT17M57S",
            "travelDistanceMetersFromPreviousStandstill": 13866,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT3H13M5S",
          "travelTimeFromStartLocationToFirstVisit": "PT56M46S",
          "travelTimeBetweenVisits": "PT1H26M21S",
          "travelTimeFromLastVisitToEndLocation": "PT49M58S",
          "totalTravelDistanceMeters": 197517,
          "travelDistanceFromStartLocationToFirstVisitMeters": 54627,
          "travelDistanceBetweenVisitsMeters": 85695,
          "travelDistanceFromLastVisitToEndLocationMeters": 57195,
          "endLocationArrivalTime": "2027-02-01T16:13:05Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT4H20M18S",
        "travelTimeFromStartLocationToFirstVisit": "PT1H7M23S",
        "travelTimeBetweenVisits": "PT1H49M44S",
        "travelTimeFromLastVisitToEndLocation": "PT1H23M11S",
        "totalTravelDistanceMeters": 256989,
        "travelDistanceFromStartLocationToFirstVisitMeters": 63470,
        "travelDistanceBetweenVisitsMeters": 106912,
        "travelDistanceFromLastVisitToEndLocationMeters": 86607,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 5,
        "assignedMandatoryVisits": 5,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 82.99,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-668hard/10000medium/-69640soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/31soft",
          "constraintName": "Balance time utilization",
          "matchCountDiff": 0
        },
        {
          "score": "-668hard/0medium/0soft",
          "constraintName": "Max shift end time (hard)",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-65512soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-4159soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Beth-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:10:37Z",
            "startServiceTime": "2027-02-01T09:10:37Z",
            "departureTime": "2027-02-01T11:10:37Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT10M37S",
            "travelDistanceMetersFromPreviousStandstill": 8843,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T12:05:24Z",
            "startServiceTime": "2027-02-01T13:00:00Z",
            "departureTime": "2027-02-01T14:00:00Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT54M47S",
            "travelDistanceMetersFromPreviousStandstill": 52521,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T14:37:55Z",
            "startServiceTime": "2027-02-01T14:37:55Z",
            "departureTime": "2027-02-01T16:37:55Z",
            "effectiveServiceDuration": "PT2H",
            "travelTimeFromPreviousStandstill": "PT37M55S",
            "travelDistanceMetersFromPreviousStandstill": 34208,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          }
        ],
        "metrics": {
          "totalTravelTime": "PT2H16M32S",
          "travelTimeFromStartLocationToFirstVisit": "PT10M37S",
          "travelTimeBetweenVisits": "PT1H32M42S",
          "travelTimeFromLastVisitToEndLocation": "PT33M13S",
          "totalTravelDistanceMeters": 124984,
          "travelDistanceFromStartLocationToFirstVisitMeters": 8843,
          "travelDistanceBetweenVisitsMeters": 86729,
          "travelDistanceFromLastVisitToEndLocationMeters": 29412,
          "endLocationArrivalTime": "2027-02-01T17:11:08Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT4H5M36S",
        "travelTimeFromStartLocationToFirstVisit": "PT51M46S",
        "travelTimeBetweenVisits": "PT1H50M39S",
        "travelTimeFromLastVisitToEndLocation": "PT1H23M11S",
        "totalTravelDistanceMeters": 241684,
        "travelDistanceFromStartLocationToFirstVisitMeters": 54482,
        "travelDistanceBetweenVisitsMeters": 100595,
        "travelDistanceFromLastVisitToEndLocationMeters": 86607,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 5,
        "assignedMandatoryVisits": 5,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 2,
        "workingTimeFairnessPercentage": 90.99,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

The recommendation output for this example includes four recommendations:

  1. Between 13:00 and 17:00 on February 1st with technician Ann.

  2. Between 13:00 and 17:00 on February 1st with technician Ann.

  3. Between 09:00 and 13:00 on February 1st with technician Ann.

  4. Between 13:00 and 17:00 on February 1st with technician Ann.

The third and fourth recommendations both break hard constraints and are not feasible solutions.

recommend a time window

The customer can accept one of the recommendations, either:

  1. Between 13:00 and 17:00 on February 1st with technician Beth.

  2. Between 09:00 and 13:00 on February 1st with technician Ann.

After the customer has accepted one of the time windows, 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 E 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, in Multi-vehicle visits multiple technicians could be assigned to a multi-vehicle visit group that is changed as a result of the recommendation.

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

{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitId": "C",
  "includeDependencies": "ALL"
}

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

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 to accommodate new visits, however, visits can be pinned to prevent them from being moved.

In the following example there are 3 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"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit C",
        "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-02-17T06:27:58.191135417Z",
    "startDateTime": "2025-02-17T06:28:04.264234147Z",
    "activeDateTime": "2025-02-17T06:28:04.535194182Z",
    "completeDateTime": "2025-02-17T06:33:04.691938165Z",
    "shutdownDateTime": "2025-02-17T06:33:04.906833499Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-8526soft",
    "tags": [
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:25:22Z",
                "startServiceTime": "2027-02-01T09:25:22Z",
                "departureTime": "2027-02-01T10:55:22Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT25M22S",
                "travelDistanceMetersFromPreviousStandstill": 23325,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:52:30Z",
                "startServiceTime": "2027-02-01T11:52:30Z",
                "departureTime": "2027-02-01T12:52:30Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT57M8S",
                "travelDistanceMetersFromPreviousStandstill": 52223,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:24:18Z",
                "startServiceTime": "2027-02-01T13:24:18Z",
                "departureTime": "2027-02-01T14:24:18Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT31M48S",
                "travelDistanceMetersFromPreviousStandstill": 34233,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H22M6S",
              "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
              "travelTimeBetweenVisits": "PT1H28M56S",
              "travelTimeFromLastVisitToEndLocation": "PT27M48S",
              "totalTravelDistanceMeters": 139001,
              "travelDistanceFromStartLocationToFirstVisitMeters": 23325,
              "travelDistanceBetweenVisitsMeters": 86456,
              "travelDistanceFromLastVisitToEndLocationMeters": 29220,
              "endLocationArrivalTime": "2027-02-01T14:52:06Z",
              "technicianCosts": null
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT2H22M6S",
    "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
    "travelTimeBetweenVisits": "PT1H28M56S",
    "travelTimeFromLastVisitToEndLocation": "PT27M48S",
    "totalTravelDistanceMeters": 139001,
    "travelDistanceFromStartLocationToFirstVisitMeters": 23325,
    "travelDistanceBetweenVisitsMeters": 86456,
    "travelDistanceFromLastVisitToEndLocationMeters": 29220,
    "totalUnassignedVisits": 0,
    "workingTimeFairnessPercentage": 100.0,
    "totalTechnicianCosts": null
  }
}

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 C, Visit D, and Visit B. Visit C is before Visit D and cannot be rescheduled, so it is also 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 E), the following recommendations input dataset will provide recommendations for the visit. Because Visit C 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-recommend-time-windows [email protected]
{
  "maxNumberOfRecommendationsPerTimeWindow": 2,
  "fitVisitId": "Visit 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 C",
                "kind": "VISIT"
              },
              {
                "id": "Visit D",
                "kind": "VISIT"
              },
              {
                "id": "Visit B",
                "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
      },
      {
        "id": "Visit C",
        "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-01T12:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.67590, -84.11845],
        "serviceDuration": "PT1H"
      }
    ]
  }
}
{
  "recommendations": [
    {
      "scoreDiff": "0hard/10000medium/-16512soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-15465soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-1047soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:25:22Z",
            "startServiceTime": "2027-02-01T09:25:22Z",
            "departureTime": "2027-02-01T10:55:22Z",
            "effectiveServiceDuration": "PT1H30M",
            "travelTimeFromPreviousStandstill": "PT25M22S",
            "travelDistanceMetersFromPreviousStandstill": 23324,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:52:30Z",
            "startServiceTime": "2027-02-01T11:52:30Z",
            "departureTime": "2027-02-01T12:52:30Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT57M8S",
            "travelDistanceMetersFromPreviousStandstill": 52223,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T13:24:14Z",
            "startServiceTime": "2027-02-01T13:24:14Z",
            "departureTime": "2027-02-01T14:24:14Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT31M44S",
            "travelDistanceMetersFromPreviousStandstill": 34236,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T14:57:59Z",
            "startServiceTime": "2027-02-01T14:57:59Z",
            "departureTime": "2027-02-01T15:57:59Z",
            "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:09:30Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H39M30S",
        "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
        "travelTimeBetweenVisits": "PT2H2M37S",
        "travelTimeFromLastVisitToEndLocation": "PT11M31S",
        "totalTravelDistanceMeters": 154469,
        "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
        "travelDistanceBetweenVisitsMeters": 122448,
        "travelDistanceFromLastVisitToEndLocationMeters": 8697,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 100.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "0hard/10000medium/-38226soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-36037soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-2189soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T13:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T17:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:25:22Z",
            "startServiceTime": "2027-02-01T09:25:22Z",
            "departureTime": "2027-02-01T10:55:22Z",
            "effectiveServiceDuration": "PT1H30M",
            "travelTimeFromPreviousStandstill": "PT25M22S",
            "travelDistanceMetersFromPreviousStandstill": 23324,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:52:30Z",
            "startServiceTime": "2027-02-01T11:52:30Z",
            "departureTime": "2027-02-01T12:52:30Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT57M8S",
            "travelDistanceMetersFromPreviousStandstill": 52223,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T13:27:13Z",
            "startServiceTime": "2027-02-01T13:27:13Z",
            "departureTime": "2027-02-01T14:27:13Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT34M43S",
            "travelDistanceMetersFromPreviousStandstill": 31571,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T15:00:43Z",
            "startServiceTime": "2027-02-01T15:00:43Z",
            "departureTime": "2027-02-01T16:00:43Z",
            "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:28:32Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H58M32S",
        "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
        "travelTimeBetweenVisits": "PT2H5M21S",
        "travelTimeFromLastVisitToEndLocation": "PT27M49S",
        "totalTravelDistanceMeters": 175041,
        "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
        "travelDistanceBetweenVisitsMeters": 122496,
        "travelDistanceFromLastVisitToEndLocationMeters": 29221,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 100.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-5233hard/10000medium/-38226soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-36037soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-2189soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        },
        {
          "score": "-5233hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:25:22Z",
            "startServiceTime": "2027-02-01T09:25:22Z",
            "departureTime": "2027-02-01T10:55:22Z",
            "effectiveServiceDuration": "PT1H30M",
            "travelTimeFromPreviousStandstill": "PT25M22S",
            "travelDistanceMetersFromPreviousStandstill": 23324,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:52:30Z",
            "startServiceTime": "2027-02-01T11:52:30Z",
            "departureTime": "2027-02-01T12:52:30Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT57M8S",
            "travelDistanceMetersFromPreviousStandstill": 52223,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T13:27:13Z",
            "startServiceTime": "2027-02-01T13:27:13Z",
            "departureTime": "2027-02-01T14:27:13Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT34M43S",
            "travelDistanceMetersFromPreviousStandstill": 31571,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T15:00:43Z",
            "startServiceTime": "2027-02-01T15:00:43Z",
            "departureTime": "2027-02-01T16:00:43Z",
            "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:28:32Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H58M32S",
        "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
        "travelTimeBetweenVisits": "PT2H5M21S",
        "travelTimeFromLastVisitToEndLocation": "PT27M49S",
        "totalTravelDistanceMeters": 175041,
        "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
        "travelDistanceBetweenVisitsMeters": 122496,
        "travelDistanceFromLastVisitToEndLocationMeters": 29221,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 100.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    },
    {
      "scoreDiff": "-10679hard/10000medium/-16512soft",
      "constraintDiffs": [
        {
          "score": "0hard/0medium/-15465soft",
          "constraintName": "Minimize travel distance",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/0medium/-1047soft",
          "constraintName": "Minimize travel time",
          "matchCountDiff": 1
        },
        {
          "score": "0hard/10000medium/0soft",
          "constraintName": "Require scheduling mandatory visits",
          "matchCountDiff": -1
        },
        {
          "score": "-10679hard/0medium/0soft",
          "constraintName": "Require service max end time",
          "matchCountDiff": 1
        }
      ],
      "timeWindow": {
        "minStartTime": "2027-02-01T09:00:00Z",
        "maxStartTime": null,
        "maxEndTime": "2027-02-01T13:00:00Z"
      },
      "vehicleShift": {
        "id": "Ann-2027-02-01",
        "startTime": "2027-02-01T09:00:00Z",
        "itinerary": [
          {
            "id": "Visit C",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T09:25:22Z",
            "startServiceTime": "2027-02-01T09:25:22Z",
            "departureTime": "2027-02-01T10:55:22Z",
            "effectiveServiceDuration": "PT1H30M",
            "travelTimeFromPreviousStandstill": "PT25M22S",
            "travelDistanceMetersFromPreviousStandstill": 23324,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit D",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T11:52:30Z",
            "startServiceTime": "2027-02-01T11:52:30Z",
            "departureTime": "2027-02-01T12:52:30Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT57M8S",
            "travelDistanceMetersFromPreviousStandstill": 52223,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit B",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T13:24:14Z",
            "startServiceTime": "2027-02-01T13:24:14Z",
            "departureTime": "2027-02-01T14:24:14Z",
            "effectiveServiceDuration": "PT1H",
            "travelTimeFromPreviousStandstill": "PT31M44S",
            "travelDistanceMetersFromPreviousStandstill": 34236,
            "minStartTravelTime": "2027-02-01T00:00:00Z"
          },
          {
            "id": "Visit E",
            "kind": "VISIT",
            "arrivalTime": "2027-02-01T14:57:59Z",
            "startServiceTime": "2027-02-01T14:57:59Z",
            "departureTime": "2027-02-01T15:57:59Z",
            "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:09:30Z",
          "technicianCosts": null,
          "overtime": null
        }
      },
      "dependentVehicleShifts": [],
      "kpis": {
        "totalTravelTime": "PT2H39M30S",
        "travelTimeFromStartLocationToFirstVisit": "PT25M22S",
        "travelTimeBetweenVisits": "PT2H2M37S",
        "travelTimeFromLastVisitToEndLocation": "PT11M31S",
        "totalTravelDistanceMeters": 154469,
        "travelDistanceFromStartLocationToFirstVisitMeters": 23324,
        "travelDistanceBetweenVisitsMeters": 122448,
        "travelDistanceFromLastVisitToEndLocationMeters": 8697,
        "totalUnassignedVisits": 0,
        "totalAssignedVisits": 4,
        "assignedMandatoryVisits": 4,
        "assignedOptionalVisits": 0,
        "totalActivatedVehicles": 1,
        "workingTimeFairnessPercentage": 100.0,
        "totalTechnicianCosts": null,
        "totalOvertime": null
      }
    }
  ]
}

Two of the recommendations do not break any hard constraints:

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 group time window recommendations.

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