Visit service level agreement (SLA)
Visits can have hard and soft requirements for the time windows when visits can occur. These time windows can represent the business hours of the service location, the customer’s availability, or even the customer’s preferred times.
In addition to these constraints about site availability, there are often requirements (including contractual requirements) regarding the latest time visits can be completed. For instance, if the company promises service within a specific time period of a service being requested.
This requirement is called a service level agreement (SLA).
This is often determined by the urgency of the incident that has been reported, and how quickly the service must be performed.
This guide describes the SLA with the following example:
1. Visit SLAs
Learn how to configure an API Key to run the examples in this guide:
In the examples, replace |
SLAs are defined in visits:
{
"visits": [
{
"id": "Visit A",
"location": [34.31785, -83.82816],
"serviceDuration": "PT1H30M",
"latestSlaEndTime": "2027-02-01T13:00:00Z"
}
]
}
latestSlaEndTime defines the latest time the visit can end to satisfy the SLA.
latestSlaEndTime uses ISO 8601 date and time format with offset to UTC format.
The Latest SLA end time soft constraint is invoked for any visit with a latestSlaEndTime, where the latestSlaEndTime is not met.
To optimize for SLAs, the constraint adds a soft score penalty to the dataset if the visit ends after the time specified by latestSlaEndTime.
The penalty is equivalent to the number of seconds between latestSlaEndTime and when the visit ends.
If a visit with a latestSlaEndTime remains unassigned and the latestSlaEndTime is within the planning window, the soft penalty will also be applied.
In this case, the penalty is derived from the time between latestSlaEndTime and the end of the planning window.
Visits can still be scheduled even if doing so breaks this constraint, but Timefold is incentivized to use the route plan with the best score.
|
Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints. Learn about constraint weights. |
1.1. Visit SLAs example
In this example, Carl’s shift begins at 09:00.
There are two visits, Visit A and Visit B, that need to be scheduled.
Visit B has a latestSlaEndTime of 2027-02-01T13:00:00Z, which means that it must be completed by 13:00.
Visit A does not include a latestSlaEndTime.
Visit B is scheduled first so the visit can be completed by the latestSlaEndTime.
-
Input
-
Output
Try this example in Timefold Platform by saving the JSON into a file called sla-example-1.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 -d@sla-example-1.json
{
"config": {
"run": {
"name": "SLA 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"
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.31785, -83.82816],
"serviceDuration": "PT2H30M"
},
{
"id": "Visit B",
"location": [33.32468, -84.127456],
"serviceDuration": "PT3H",
"latestSlaEndTime": "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>
{
"metadata": {
"id": "ID",
"name": "SLA example",
"submitDateTime": "2025-05-16T12:01:05.974541+02:00",
"startDateTime": "2025-05-16T12:01:05.987945+02:00",
"activeDateTime": "2025-05-16T12:01:05.993695+02:00",
"completeDateTime": "2025-05-16T12:01:36.002054+02:00",
"shutdownDateTime": "2025-05-16T12:01:36.004414+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-130078soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:48:53Z",
"startServiceTime": "2027-02-01T09:48:53Z",
"departureTime": "2027-02-01T12:48:53Z",
"effectiveServiceDuration": "PT3H",
"travelTimeFromPreviousStandstill": "PT48M53S",
"travelDistanceMetersFromPreviousStandstill": 40733,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:22:16Z",
"startServiceTime": "2027-02-01T13:22:16Z",
"departureTime": "2027-02-01T15:52:16Z",
"effectiveServiceDuration": "PT2H30M",
"travelTimeFromPreviousStandstill": "PT33M23S",
"travelDistanceMetersFromPreviousStandstill": 27819,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT2H25M37S",
"travelTimeFromStartLocationToFirstVisit": "PT48M53S",
"travelTimeBetweenVisits": "PT33M23S",
"travelTimeFromLastVisitToEndLocation": "PT1H3M21S",
"totalTravelDistanceMeters": 121341,
"travelDistanceFromStartLocationToFirstVisitMeters": 40733,
"travelDistanceBetweenVisitsMeters": 27819,
"travelDistanceFromLastVisitToEndLocationMeters": 52789,
"endLocationArrivalTime": "2027-02-01T16:55:37Z",
"technicianCosts": null,
"overtime": "PT0S"
}
}
]
}
]
},
"inputMetrics": {
"visits": 2,
"visitGroups": 0,
"vehicles": 1,
"mandatoryVisits": 2,
"optionalVisits": 0,
"vehicleShifts": 1,
"visitsWithSla": 1
},
"kpis": {
"totalTravelTime": "PT2H25M37S",
"travelTimeFromStartLocationToFirstVisit": "PT48M53S",
"travelTimeBetweenVisits": "PT33M23S",
"travelTimeFromLastVisitToEndLocation": "PT1H3M21S",
"totalTravelDistanceMeters": 121341,
"travelDistanceFromStartLocationToFirstVisitMeters": 40733,
"travelDistanceBetweenVisitsMeters": 27819,
"travelDistanceFromLastVisitToEndLocationMeters": 52789,
"totalUnassignedVisits": 0,
"totalAssignedVisits": 2,
"assignedMandatoryVisits": 2,
"assignedOptionalVisits": 0,
"totalActivatedVehicles": 1,
"workingTimeFairnessPercentage": 100.0,
"totalTechnicianCosts": null,
"totalOvertime": "PT0S",
"percentageVisitsInSla": "100.0",
"absoluteVisitsInSla": "1"
}
}
2. Prefer scheduling visits with tight SLAs earlier
The Latest SLA end time constraint penalises visits that miss their SLA deadline, but it does not differentiate between visits that both satisfy their SLA.
When two visits are tied on other criteria, the visit with the tighter deadline may still end up scheduled later which increases the risk of disruption if the plan changes or the job takes longer than expected.
The Prefer scheduling visits with tight SLAs earlier soft constraint addresses this by incentivising the model to schedule visits with a closer SLA deadline earlier in the planning period.
It is disabled by default and can be enabled by setting the preferSchedulingEarlierBasedOnSlaWeight in the model configuration.
{
"config": {
"model": {
"overrides": {
"preferSchedulingEarlierBasedOnSlaWeight": 1
}
}
}
}
The penalty for each visit is proportional to how late in the planning period it is scheduled relative to how much time remains before its SLA deadline:
penalty = secondsFromPlanningPeriodStart / secondsUntilSlaDeadline * weight
A visit scheduled early with plenty of SLA slack receives a low penalty. A visit scheduled late with little SLA slack receives a high penalty, steering the solver to prefer moving it earlier.
|
Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints. Learn about constraint weights. |
Next
-
See the full API spec or try the online API.
-
Learn more about field service routing from our YouTube playlist.