Real-time planning: technician ill
There are many situations where Real-time planning is necessary.
Sometimes technicians become ill and must take the day off or, if they have already started work, go home part way through a shift.
Consider the following shift schedule:
Carl has three visits: Visit E, Visit B, and Visit D. Ann has two visits: Visit C and Visit A.
If Carl calls in sick before the shift starts or goes home part way through the day due to illness, his shifts must be reassigned to another technician or rescheduled for another day.
1. Batch schedule: technician ill
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: technician ill 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: technician ill example",
"submitDateTime": "2024-11-04T02:39:17.953231319Z",
"startDateTime": "2024-11-04T02:39:23.336185735Z",
"activeDateTime": "2024-11-04T02:39:23.436185735Z",
"completeDateTime": "2024-11-04T02:44:23.915852927Z",
"shutdownDateTime": "2024-11-04T02:44:24.015852927Z",
"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 itinerary.
2. Real-time planning update: technician ill
When a technician becomes ill before a shift has started, they can be removed from the input dataset and the dataset can be resubmitted.
Timefold will reassign shifts that can be reassigned and leave any remaining shifts unassigned to be rescheduled for another day.
-
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: technician ill example"
}
},
"modelInput": {
"vehicles": [
{
"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": "Real-time planning: technician ill example",
"submitDateTime": "2024-11-04T02:52:47.169421517Z",
"startDateTime": "2024-11-04T02:52:52.533850903Z",
"activeDateTime": "2024-11-04T02:52:52.633850903Z",
"completeDateTime": "2024-11-04T02:57:53.131011861Z",
"shutdownDateTime": "2024-11-04T02:57:53.231011861Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-4medium/-1116914soft",
"tags": null,
"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: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 D",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:09:38Z",
"startServiceTime": "2027-02-01T11:09:38Z",
"departureTime": "2027-02-01T12:39:38Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT29M43S",
"travelDistanceMetersFromPreviousStandstill": 24817,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:11:26Z",
"startServiceTime": "2027-02-01T13:11:26Z",
"departureTime": "2027-02-01T14:41:26Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT31M48S",
"travelDistanceMetersFromPreviousStandstill": 34233,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T15:15:10Z",
"startServiceTime": "2027-02-01T15:15:10Z",
"departureTime": "2027-02-01T16:45:10Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT33M44S",
"travelDistanceMetersFromPreviousStandstill": 35988,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H55M48S",
"travelTimeFromStartLocationToFirstVisit": "PT9M55S",
"travelTimeBetweenVisits": "PT1H35M15S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 109966,
"travelDistanceFromStartLocationToFirstVisitMeters": 6094,
"travelDistanceBetweenVisitsMeters": 95038,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T16:55:48Z"
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT1H55M48S",
"travelTimeFromStartLocationToFirstVisit": "PT9M55S",
"travelTimeBetweenVisits": "PT1H35M15S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 109966,
"travelDistanceFromStartLocationToFirstVisitMeters": 6094,
"travelDistanceBetweenVisitsMeters": 95038,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"totalUnassignedVisits": 1
}
}
modelOutput
contains Ann’s updated shift itinerary.
Carl’s shifts were reassigned to Ann.
3. Real-time planning update: technician becomes ill
In this example, Carl becomes ill after starting work. He can complete his first visit, then needs to go home.
The plan must be updated part way through the day.
Carl will be going home after Visit E.
Visit E ends at 11.18:56.
Update Carl’s shift by replacing "maxEndTime": "2027-02-01T17:00:00Z"
with "maxLastVisitDepartureTime": "2027-02-01T11:18:56Z"
to set the time Carl returns home from his last visit (Visit E).
Learn more about maxLastVisitDepartureTime
in Shift hours and overtime.
Include the itineraries for both Ann and Carl:
{
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxLastVisitDepartureTime": "2027-02-01T11:18:56Z",
"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"
}
]
}
]
}
]
}
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": "PT1H30M",
"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"
}
]
}
Freeze the departure times for visits that have already occurred and that technicians have begun traveling to by adding freezeDeparturesBeforeTime
:
{
"modelInput": {
"freezeDeparturesBeforeTime": "2027-02-01T11:10:00Z"
}
}
Carl knows he will be going home after his visit which ends at 11:18:56 so the freezeDeparturesBeforeTime
can be set to 11:10.
Submit the 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: technician becomes ill example"
}
},
"modelInput": {
"freezeDeparturesBeforeTime": "2027-02-01T11:10:00Z",
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxLastVisitDepartureTime": "2027-02-01T11:18:56Z",
"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": "PT1H30M",
"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: technician becomes ill example",
"submitDateTime": "2024-11-08T05:36:32.111200973Z",
"startDateTime": "2024-11-08T05:36:37.518765197Z",
"activeDateTime": "2024-11-08T05:36:37.618765197Z",
"completeDateTime": "2024-11-08T05:41:38.239807012Z",
"shutdownDateTime": "2024-11-08T05:41:38.339807012Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-237751soft",
"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"
}
],
"metrics": {
"totalTravelTime": "PT1H38M54S",
"travelTimeFromStartLocationToFirstVisit": "PT48M56S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT49M58S",
"totalTravelDistanceMeters": 111336,
"travelDistanceFromStartLocationToFirstVisitMeters": 54141,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 57195,
"endLocationArrivalTime": "2027-02-01T12:08:54Z"
}
}
]
},
{
"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 B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T12:55:43Z",
"startServiceTime": "2027-02-01T12:55:43Z",
"departureTime": "2027-02-01T14:25:43Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT33M31S",
"travelDistanceMetersFromPreviousStandstill": 38703,
"minStartTravelTime": "2027-02-01T11:10:00Z"
},
{
"id": "Visit D",
"kind": "VISIT",
"arrivalTime": "2027-02-01T14:57:33Z",
"startServiceTime": "2027-02-01T14:57:33Z",
"departureTime": "2027-02-01T16:27:33Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT31M50S",
"travelDistanceMetersFromPreviousStandstill": 34289,
"minStartTravelTime": "2027-02-01T11:10:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H57M42S",
"travelTimeFromStartLocationToFirstVisit": "PT9M55S",
"travelTimeBetweenVisits": "PT1H17M38S",
"travelTimeFromLastVisitToEndLocation": "PT30M9S",
"totalTravelDistanceMeters": 113419,
"travelDistanceFromStartLocationToFirstVisitMeters": 6094,
"travelDistanceBetweenVisitsMeters": 81833,
"travelDistanceFromLastVisitToEndLocationMeters": 25492,
"endLocationArrivalTime": "2027-02-01T16:57:42Z"
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT3H36M36S",
"travelTimeFromStartLocationToFirstVisit": "PT58M51S",
"travelTimeBetweenVisits": "PT1H17M38S",
"travelTimeFromLastVisitToEndLocation": "PT1H20M7S",
"totalTravelDistanceMeters": 224755,
"travelDistanceFromStartLocationToFirstVisitMeters": 60235,
"travelDistanceBetweenVisitsMeters": 81833,
"travelDistanceFromLastVisitToEndLocationMeters": 82687,
"totalUnassignedVisits": 0
}
}
modelOutput
contains Carl’s and Ann’s shift itineraries.
Carl completed Visit E, and Visit B and Visit D were 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.