Real-time planning: reassignment
There are many situations where Real-time planning is necessary.
When a stop takes longer than expected other stops might need to be rescheduled or even assigned to another driver.
Consider the following shift schedule:
Ann has six stops scheduled for the day: Stop E, Stop C, Stop A, Stop B, Stop D and Stop F.
However, Stop C takes much longer than expected.
As a result, Stop A, Stop B, Stop D and Stop F need to be rescheduled.
This guide explains real-time planning: reassignment with the following:
1. Batch schedule: reassignment
Learn how to configure an API Key to run the examples in this guide:
In the examples, replace |
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/pickup-delivery-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Original shift plan: reassignment example"
}
},
"modelInput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z"
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z"
}
]
}
],
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT30M"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M"
}
]
}
]
}
}
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/pickup-delivery-routing/v1/route-plans/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Original shift plan: reassignment example",
"submitDateTime": "2026-04-09T17:06:00.955838+02:00",
"startDateTime": "2026-04-09T17:06:01.006547+02:00",
"activeDateTime": "2026-04-09T17:06:01.008361+02:00",
"completeDateTime": "2026-04-09T17:06:31.02371+02:00",
"shutdownDateTime": "2026-04-09T17:06:31.023713+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-6136soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop G",
"arrivalTime": "2027-02-01T09:10:21Z",
"startServiceTime": "2027-02-01T09:10:21Z",
"departureTime": "2027-02-01T09:40:21Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT10M21S",
"travelDistanceMetersFromPreviousStandstill": 8629,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop H",
"arrivalTime": "2027-02-01T09:54:26Z",
"startServiceTime": "2027-02-01T09:54:26Z",
"departureTime": "2027-02-01T10:24:26Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT14M5S",
"travelDistanceMetersFromPreviousStandstill": 11743,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT30M25S",
"travelTimeFromStartLocationToFirstStop": "PT10M21S",
"travelTimeBetweenStops": "PT14M5S",
"travelTimeFromLastStopToEndLocation": "PT5M59S",
"totalTravelDistanceMeters": 25355,
"travelDistanceFromStartLocationToFirstStopMeters": 8629,
"travelDistanceBetweenStopsMeters": 11743,
"travelDistanceFromLastStopToEndLocationMeters": 4983,
"endLocationArrivalTime": "2027-02-01T10:30:25Z"
}
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop E",
"arrivalTime": "2027-02-01T09:17:11Z",
"startServiceTime": "2027-02-01T09:17:11Z",
"departureTime": "2027-02-01T09:47:11Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT17M11S",
"travelDistanceMetersFromPreviousStandstill": 14324,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop C",
"arrivalTime": "2027-02-01T09:48:41Z",
"startServiceTime": "2027-02-01T09:48:41Z",
"departureTime": "2027-02-01T10:18:41Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M30S",
"travelDistanceMetersFromPreviousStandstill": 1250,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop A",
"arrivalTime": "2027-02-01T10:24:17Z",
"startServiceTime": "2027-02-01T10:24:17Z",
"departureTime": "2027-02-01T10:54:17Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT5M36S",
"travelDistanceMetersFromPreviousStandstill": 4671,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop B",
"arrivalTime": "2027-02-01T11:06:53Z",
"startServiceTime": "2027-02-01T11:06:53Z",
"departureTime": "2027-02-01T11:36:53Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT12M36S",
"travelDistanceMetersFromPreviousStandstill": 10498,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop D",
"arrivalTime": "2027-02-01T11:37:55Z",
"startServiceTime": "2027-02-01T11:37:55Z",
"departureTime": "2027-02-01T12:07:55Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M2S",
"travelDistanceMetersFromPreviousStandstill": 867,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop F",
"arrivalTime": "2027-02-01T12:26:13Z",
"startServiceTime": "2027-02-01T12:26:13Z",
"departureTime": "2027-02-01T12:56:13Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT18M18S",
"travelDistanceMetersFromPreviousStandstill": 15249,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT1H11M51S",
"travelTimeFromStartLocationToFirstStop": "PT17M11S",
"travelTimeBetweenStops": "PT39M2S",
"travelTimeFromLastStopToEndLocation": "PT15M38S",
"totalTravelDistanceMeters": 59891,
"travelDistanceFromStartLocationToFirstStopMeters": 14324,
"travelDistanceBetweenStopsMeters": 32535,
"travelDistanceFromLastStopToEndLocationMeters": 13032,
"endLocationArrivalTime": "2027-02-01T13:11:51Z"
}
}
]
}
],
"unassignedJobs": []
},
"inputMetrics": {
"jobs": 4,
"stops": 8,
"drivers": 2,
"driverShifts": 2,
"pinnedStops": 0
},
"kpis": {
"totalTravelTime": "PT1H42M16S",
"totalTravelDistanceMeters": 85246,
"totalActivatedDrivers": 2,
"totalUnassignedJobs": 0,
"totalAssignedJobs": 4,
"assignedMandatoryJobs": 4,
"totalUnassignedStops": 0,
"totalAssignedStops": 8,
"assignedMandatoryStops": 8,
"travelTimeFromStartLocationToFirstStop": "PT27M32S",
"travelTimeBetweenStops": "PT53M7S",
"travelTimeFromLastStopToEndLocation": "PT21M37S",
"travelDistanceFromStartLocationToFirstStopMeters": 22953,
"travelDistanceBetweenStopsMeters": 44278,
"travelDistanceFromLastStopToEndLocationMeters": 18015
}
}
modelOutput contains Carl’s shift itinerary.
2. Real-time planning update: reassignment
If the same scenario occurs, and Stop C takes much longer than expected, the input dataset needs to be modified and resubmitted.
Update the duration for Stop C from 30 minutes to 60 minutes.
Add the minStartTravelTime from the most recent planning output dataset (batch or real-time) to each stop.
{
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT60M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
}
]
}
Add the itineraries to Carl’s and Ann’s shifts, including the stop IDs and the stop kind:
{
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T11:00:00Z",
"itinerary":[
{
"id": "Stop G",
"kind": "STOP"
},
{
"id": "Stop H",
"kind": "STOP"
}
]
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop E",
"kind": "STOP"
},
{
"id": "Stop C",
"kind": "STOP"
},
{
"id": "Stop A",
"kind": "STOP"
},
{
"id": "Stop B",
"kind": "STOP"
},
{
"id": "Stop D",
"kind": "STOP"
},
{
"id": "Stop F",
"kind": "STOP"
}
]
}
]
}
]
}
Freeze the departure times for stops that have already occurred and that drivers have begun traveling to by adding freezeTime:
{
"modelInput": {
"freezeTime": "2027-02-01T10:20:00Z"
}
}
Because Ann finished Stop C at 10:18 and is already traveling to Stop A at the freezeTime, this will keep Stop A scheduled after Stop C with a new arrival time.
|
Instead of resubmitting the full updated dataset, you can make this change with the Patch feature, which submits only the change as a JSON Patch document and generates a new revision of the plan. The Patch feature is currently available as a preview feature. If you’d like early access to it, please contact us.
To learn more about the The
Submit the full patch to modify the original input dataset to the following endpoint. Replace <ID> with the dataset ID:
When submitted, solving is triggered to generate a new revision of the plan.
|
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/pickup-delivery-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Original shift plan: reassignment example"
}
},
"modelInput": {
"freezeTime": "2027-02-01T10:20:00Z",
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop G",
"kind": "STOP"
},
{
"id": "Stop H",
"kind": "STOP"
}
]
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop E",
"kind": "STOP"
},
{
"id": "Stop C",
"kind": "STOP"
},
{
"id": "Stop A",
"kind": "STOP"
},
{
"id": "Stop B",
"kind": "STOP"
},
{
"id": "Stop D",
"kind": "STOP"
},
{
"id": "Stop F",
"kind": "STOP"
}
]
}
]
}
],
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT60M"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M"
}
]
}
]
}
}
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/pickup-delivery-routing/v1/route-plans/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Original shift plan: reassignment example",
"submitDateTime": "2026-04-10T17:00:10.026997+02:00",
"startDateTime": "2026-04-10T17:00:10.071052+02:00",
"activeDateTime": "2026-04-10T17:00:10.072466+02:00",
"completeDateTime": "2026-04-10T17:00:40.086433+02:00",
"shutdownDateTime": "2026-04-10T17:00:40.086435+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-7414soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop G",
"arrivalTime": "2027-02-01T09:10:21Z",
"startServiceTime": "2027-02-01T09:10:21Z",
"departureTime": "2027-02-01T09:40:21Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT10M21S",
"travelDistanceMetersFromPreviousStandstill": 8629,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop H",
"arrivalTime": "2027-02-01T09:54:26Z",
"startServiceTime": "2027-02-01T09:54:26Z",
"departureTime": "2027-02-01T10:24:26Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT14M5S",
"travelDistanceMetersFromPreviousStandstill": 11743,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop A",
"arrivalTime": "2027-02-01T10:25:26Z",
"startServiceTime": "2027-02-01T10:25:26Z",
"departureTime": "2027-02-01T10:55:26Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M",
"travelDistanceMetersFromPreviousStandstill": 830,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop B",
"arrivalTime": "2027-02-01T11:08:02Z",
"startServiceTime": "2027-02-01T11:08:02Z",
"departureTime": "2027-02-01T11:38:02Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT12M36S",
"travelDistanceMetersFromPreviousStandstill": 10498,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT54M37S",
"travelTimeFromStartLocationToFirstStop": "PT10M21S",
"travelTimeBetweenStops": "PT27M41S",
"travelTimeFromLastStopToEndLocation": "PT16M35S",
"totalTravelDistanceMeters": 45515,
"travelDistanceFromStartLocationToFirstStopMeters": 8629,
"travelDistanceBetweenStopsMeters": 23071,
"travelDistanceFromLastStopToEndLocationMeters": 13815,
"endLocationArrivalTime": "2027-02-01T11:54:37Z"
}
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop E",
"arrivalTime": "2027-02-01T09:17:11Z",
"startServiceTime": "2027-02-01T09:17:11Z",
"departureTime": "2027-02-01T09:47:11Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT17M11S",
"travelDistanceMetersFromPreviousStandstill": 14324,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop C",
"arrivalTime": "2027-02-01T09:48:41Z",
"startServiceTime": "2027-02-01T09:48:41Z",
"departureTime": "2027-02-01T10:48:41Z",
"effectiveDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT1M30S",
"travelDistanceMetersFromPreviousStandstill": 1250,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop D",
"arrivalTime": "2027-02-01T11:05:01Z",
"startServiceTime": "2027-02-01T11:05:01Z",
"departureTime": "2027-02-01T11:35:01Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT16M20S",
"travelDistanceMetersFromPreviousStandstill": 13610,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop F",
"arrivalTime": "2027-02-01T11:53:19Z",
"startServiceTime": "2027-02-01T11:53:19Z",
"departureTime": "2027-02-01T12:23:19Z",
"effectiveDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT18M18S",
"travelDistanceMetersFromPreviousStandstill": 15249,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT1H8M57S",
"travelTimeFromStartLocationToFirstStop": "PT17M11S",
"travelTimeBetweenStops": "PT36M8S",
"travelTimeFromLastStopToEndLocation": "PT15M38S",
"totalTravelDistanceMeters": 57465,
"travelDistanceFromStartLocationToFirstStopMeters": 14324,
"travelDistanceBetweenStopsMeters": 30109,
"travelDistanceFromLastStopToEndLocationMeters": 13032,
"endLocationArrivalTime": "2027-02-01T12:38:57Z"
}
}
]
}
],
"unassignedJobs": []
},
"inputMetrics": {
"jobs": 4,
"stops": 8,
"drivers": 2,
"driverShifts": 2,
"pinnedStops": 4
},
"kpis": {
"totalTravelTime": "PT2H3M34S",
"totalTravelDistanceMeters": 102980,
"totalActivatedDrivers": 2,
"totalUnassignedJobs": 0,
"totalAssignedJobs": 4,
"assignedMandatoryJobs": 4,
"totalUnassignedStops": 0,
"totalAssignedStops": 8,
"assignedMandatoryStops": 8,
"travelTimeFromStartLocationToFirstStop": "PT27M32S",
"travelTimeBetweenStops": "PT1H3M49S",
"travelTimeFromLastStopToEndLocation": "PT32M13S",
"travelDistanceFromStartLocationToFirstStopMeters": 22953,
"travelDistanceBetweenStopsMeters": 53180,
"travelDistanceFromLastStopToEndLocationMeters": 26847
}
}
modelOutput contains Ann’s and Carl’s updated shift itineraries.
Stop C took much longer than expected, Stop A and Stop B have been reassigned to Carl’s shift. Stop D and Stop F are rescheduled on Ann’s shift.
Next
-
See the full API spec or try the online API.
-
Learn about real-time planning.
-
Real-time planning with pinned stops.
-
Real-time planning with extended stops.