Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Field Service Routing
  • Real-time planning
  • Real-time planning: reassignment

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

Real-time planning: reassignment

There are many situations where Real-time planning is necessary.

When a visit takes longer than expected other visits might need to be rescheduled or even assigned to another technician.

Consider the following shift schedule:

Carl has three visits scheduled for the day: Visit E, Visit B, and Visit D.

Ann has two visits scheduled for the day: Visit C and Visit A.

However, Visit E takes much longer than expected.

As a result, Visit B and Visit D need to be rescheduled with Visit D being reassigned to Ann.

real time planning extended visit3

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. Batch schedule: reassignment

The original schedule was generated from the following input dataset during the regular batch scheduling:

  • 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": "Original shift plan: reassignment example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit E",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit A",
        "location":  [33.67590, -84.11845],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit C",
        "location":  [33.71517, -84.08527],
        "serviceDuration": "PT1H30M"
      }
    ]
  }
}
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": "Original shift plan: reassignment example",
    "submitDateTime": "2024-10-30T08:37:32.691576435Z",
    "startDateTime": "2024-10-30T08:37:38.554385237Z",
    "activeDateTime": "2024-10-30T08:37:38.654385237Z",
    "completeDateTime": "2024-10-30T08:42:39.308136355Z",
    "shutdownDateTime": "2024-10-30T08:42:39.408136355Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-200626soft",
    "tags": null,
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:48:56Z",
                "startServiceTime": "2027-02-01T09:48:56Z",
                "departureTime": "2027-02-01T11:18:56Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT48M56S",
                "travelDistanceMetersFromPreviousStandstill": 54141,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:59:29Z",
                "startServiceTime": "2027-02-01T11:59:29Z",
                "departureTime": "2027-02-01T13:29:29Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT40M33S",
                "travelDistanceMetersFromPreviousStandstill": 42302,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T14:01:19Z",
                "startServiceTime": "2027-02-01T14:01:19Z",
                "departureTime": "2027-02-01T15:31:19Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT31M50S",
                "travelDistanceMetersFromPreviousStandstill": 34289,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H39M10S",
              "travelTimeFromStartLocationToFirstVisit": "PT48M56S",
              "travelTimeBetweenVisits": "PT1H12M23S",
              "travelTimeFromLastVisitToEndLocation": "PT37M51S",
              "totalTravelDistanceMeters": 165337,
              "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
              "travelDistanceBetweenVisitsMeters": 76591,
              "travelDistanceFromLastVisitToEndLocationMeters": 34605,
              "endLocationArrivalTime": "2027-02-01T16:09:10Z"
            }
          }
        ]
      },
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:09:55Z",
                "startServiceTime": "2027-02-01T09:09:55Z",
                "departureTime": "2027-02-01T10:39:55Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT9M55S",
                "travelDistanceMetersFromPreviousStandstill": 6094,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:52:12Z",
                "startServiceTime": "2027-02-01T10:52:12Z",
                "departureTime": "2027-02-01T12:22:12Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT12M17S",
                "travelDistanceMetersFromPreviousStandstill": 8841,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT32M50S",
              "travelTimeFromStartLocationToFirstVisit": "PT9M55S",
              "travelTimeBetweenVisits": "PT12M17S",
              "travelTimeFromLastVisitToEndLocation": "PT10M38S",
              "totalTravelDistanceMeters": 23769,
              "travelDistanceFromStartLocationToFirstVisitMeters": 6094,
              "travelDistanceBetweenVisitsMeters": 8841,
              "travelDistanceFromLastVisitToEndLocationMeters": 8834,
              "endLocationArrivalTime": "2027-02-01T12:32:50Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT3H12M",
    "travelTimeFromStartLocationToFirstVisit": "PT58M51S",
    "travelTimeBetweenVisits": "PT1H24M40S",
    "travelTimeFromLastVisitToEndLocation": "PT48M29S",
    "totalTravelDistanceMeters": 189106,
    "travelDistanceFromStartLocationToFirstVisitMeters": 60235,
    "travelDistanceBetweenVisitsMeters": 85432,
    "travelDistanceFromLastVisitToEndLocationMeters": 43439,
    "totalUnassignedVisits": 0
  }
}

modelOutput contains Carl’s and Ann’s shift itineraries.

2. Real-time planning update: reassignment

If the same scenario occurs, and Visit E takes much longer than expected, the input dataset needs to be modified and resubmitted.

Update the serviceDuration for Visit E from 1 hour and 30 minutes to 4 hours. Add the minStartTravelTime from the most recent planning output dataset (batch or real-time) to each visit.

{
  "visits": [
    {
      "id": "Visit E",
      "location": [33.84475, -84.63649],
      "serviceDuration": "PT4H",
      "minStartTravelTime": "2027-02-01T00:00:00Z"
    },
    {
      "id": "Visit B",
      "location": [33.90719, -84.28149],
      "serviceDuration": "PT1H30M",
      "minStartTravelTime": "2027-02-01T00:00:00Z"
    },
    {
      "id": "Visit D",
      "location":  [33.89351, -84.00649],
      "serviceDuration": "PT1H30M",
      "minStartTravelTime": "2027-02-01T00:00:00Z"
    },
    {
      "id": "Visit A",
      "location":  [33.67590, -84.11845],
      "serviceDuration": "PT1H30M",
      "minStartTravelTime": "2027-02-01T00:00:00Z"
    },
    {
      "id": "Visit C",
      "location":  [33.71517, -84.08527],
      "serviceDuration": "PT1H30M",
      "minStartTravelTime": "2027-02-01T00:00:00Z"
    }
  ]
}

Add the itinerary to Carl’s and Beth’s shifts, including the visit IDs and the visit kind:

{
  "vehicles": [
    {
      "id": "Carl",
      "shifts": [
        {
          "id": "Carl-2027-02-01",
          "startLocation": [33.68786, -84.18487],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "itinerary": [
            {
              "id": "Visit E",
              "kind": "VISIT"
            },
            {
              "id": "Visit B",
              "kind": "VISIT"
            },
            {
              "id": "Visit D",
              "kind": "VISIT"
            }
          ]
        }
      ]
    },
    {
      "id": "Ann",
      "shifts": [
        {
          "id": "Ann-2027-02-01",
          "startLocation": [33.70474, -84.06508],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "itinerary": [
            {
              "id": "Visit C",
              "kind": "VISIT"
            },
            {
              "id": "Visit A",
              "kind": "VISIT"
            }
          ]
        }
      ]
    }
  ]
}

Freeze the departure times for visits that have already occurred and that technicians have begun traveling to by adding freezeDeparturesBeforeTime:

{
  "modelInput": {
    "freezeDeparturesBeforeTime": "2027-02-01T12:10:00Z"
  }
}

In the original schedule Ann was due to finish Visit A at 12:22. If the delay for Visit E is reported early enough, the freezeDeparturesBeforeTime can be set prior to Ann finishing Visit A. The updated schedule will keep visit A in place in the schedule and reschedule other visits as required.

Submit the updated input dataset to generate the new real-time plan:

  • 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": "Real-time planning: reassignment example"
    }
  },
  "modelInput": {
    "freezeDeparturesBeforeTime": "2027-02-01T12:10:00Z",
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit E",
                "kind": "VISIT"
              },
              {
                "id": "Visit B",
                "kind": "VISIT"
              },
              {
                "id": "Visit D",
                "kind": "VISIT"
              }
            ]
          }
        ]
      },
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startLocation": [33.70474, -84.06508],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "Visit C",
                "kind": "VISIT"
              },
              {
                "id": "Visit A",
                "kind": "VISIT"
              }
            ]
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit E",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT4H",
        "minStartTravelTime": "2027-02-01T00:00:00Z"
      },
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H30M",
        "minStartTravelTime": "2027-02-01T00:00:00Z"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H30M",
        "minStartTravelTime": "2027-02-01T00:00:00Z"
      },
      {
        "id": "Visit A",
        "location":  [33.67590, -84.11845],
        "serviceDuration": "PT1H30M",
        "minStartTravelTime": "2027-02-01T00:00:00Z"
      },
      {
        "id": "Visit C",
        "location":  [33.71517, -84.08527],
        "serviceDuration": "PT1H30M",
        "minStartTravelTime": "2027-02-01T00: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": "Real-time planning: reassignment example",
    "submitDateTime": "2024-10-30T08:47:33.505112416Z",
    "startDateTime": "2024-10-30T08:47:39.397533431Z",
    "activeDateTime": "2024-10-30T08:47:39.497533431Z",
    "completeDateTime": "2024-10-30T08:52:39.782389568Z",
    "shutdownDateTime": "2024-10-30T08:52:39.882389568Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-209989soft",
    "tags": null,
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:48:56Z",
                "startServiceTime": "2027-02-01T09:48:56Z",
                "departureTime": "2027-02-01T13:48:56Z",
                "effectiveServiceDuration": "PT4H",
                "travelTimeFromPreviousStandstill": "PT48M56S",
                "travelDistanceMetersFromPreviousStandstill": 54141,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T14:29:29Z",
                "startServiceTime": "2027-02-01T14:29:29Z",
                "departureTime": "2027-02-01T15:59:29Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT40M33S",
                "travelDistanceMetersFromPreviousStandstill": 42302,
                "minStartTravelTime": "2027-02-01T12:10:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H57M18S",
              "travelTimeFromStartLocationToFirstVisit": "PT48M56S",
              "travelTimeBetweenVisits": "PT40M33S",
              "travelTimeFromLastVisitToEndLocation": "PT27M49S",
              "totalTravelDistanceMeters": 125663,
              "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
              "travelDistanceBetweenVisitsMeters": 42302,
              "travelDistanceFromLastVisitToEndLocationMeters": 29220,
              "endLocationArrivalTime": "2027-02-01T16:27:18Z"
            }
          }
        ]
      },
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:09:55Z",
                "startServiceTime": "2027-02-01T09:09:55Z",
                "departureTime": "2027-02-01T10:39:55Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT9M55S",
                "travelDistanceMetersFromPreviousStandstill": 6094,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:52:12Z",
                "startServiceTime": "2027-02-01T10:52:12Z",
                "departureTime": "2027-02-01T12:22:12Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT12M17S",
                "travelDistanceMetersFromPreviousStandstill": 8841,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T12:56:46Z",
                "startServiceTime": "2027-02-01T12:56:46Z",
                "departureTime": "2027-02-01T14:26:46Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT34M34S",
                "travelDistanceMetersFromPreviousStandstill": 31646,
                "minStartTravelTime": "2027-02-01T12:10:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H26M55S",
              "travelTimeFromStartLocationToFirstVisit": "PT9M55S",
              "travelTimeBetweenVisits": "PT46M51S",
              "travelTimeFromLastVisitToEndLocation": "PT30M9S",
              "totalTravelDistanceMeters": 72073,
              "travelDistanceFromStartLocationToFirstVisitMeters": 6094,
              "travelDistanceBetweenVisitsMeters": 40487,
              "travelDistanceFromLastVisitToEndLocationMeters": 25492,
              "endLocationArrivalTime": "2027-02-01T14:56:55Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT3H24M13S",
    "travelTimeFromStartLocationToFirstVisit": "PT58M51S",
    "travelTimeBetweenVisits": "PT1H27M24S",
    "travelTimeFromLastVisitToEndLocation": "PT57M58S",
    "totalTravelDistanceMeters": 197736,
    "travelDistanceFromStartLocationToFirstVisitMeters": 60235,
    "travelDistanceBetweenVisitsMeters": 82789,
    "travelDistanceFromLastVisitToEndLocationMeters": 54712,
    "totalUnassignedVisits": 0
  }
}

modelOutput contains Ann’s and Carl’s updated shift itineraries.

Visit E took longer than expected. Visit B is rescheduled, and Visit D is rescheduled and reassigned to Ann.

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 real-time planning.

  • Real-time planning with pinned visits.

  • Real-time planning with extended visits.

  • Real-time planning and emergency visits.

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