Technician ratings
Customers often provide feedback on how well technicians completed the service they requested.
Ratings are usually captured on a scale from 1 to 5 or 1 to 10, the higher the rating the better the perceived service.
Companies want to send their best (highest rated) technicians to ensure the quality of service is as high as possible.
Timefold’s Field Service Routing model can accept technician ratings as input data and assign the highest rated technicians to tasks as long as other constraints are also met.
Customer ratings are often highly subjective and often are not equal to the actual quality of the work the technician did. |
Prerequisites
To run the examples in this guide, you need to authenticate with a valid API key for this model:
-
Log in to Timefold Platform: app.timefold.ai.
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
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. Specifying technician ratings
Technician ratings are added in vehicles:
{
"vehicles": [
{
"id": "Beth",
"technicianRating": 4.0
}
]
}
technicianRating
is a number (with or without decimals) that is equal to or greater than 0.
There are no other restrictions on technicianRating
to keep it as flexible as possible for different rating scenarios.
When ratings don’t exist for every technician, you can define a default rating for all vehicles that don’t have a rating defined with the input.
defaultTechnicianRating
property is added to the overrides in the model configuration in the input:
{
"model": {
"overrides": {
"maximizeTechnicianRatingWeight": 1,
"defaultTechnicianRating": 5
}
}
}
By default maximizeTechnicianRatingWeight
is 0
and is not active in solving.
Setting maximizeTechnicianRatingWeight
to 1
will give the constraint the same weight as other constraints with a value 1
.
With a rating of at least 1, the constraint will be invoked during solving.
defaultTechnicianRating
sets the default rating for all technicians.
If no defaultTechnicianRating
is defined in the model configuration and there are vehicles without a rating, the model will use a rating of 0.0
as the default.
maximizeTechnicianRatingWeight
and defaultTechnicianRating
can also be defined in configuration profiles.
To access configuration profiles:
-
Log in to Timefold Platform: app.timefold.ai.
-
Select the Field Service Routing model.
-
Select configuration profiles.
Select the profile you want to change or create a new profile.
Change Maximize technician rating
and defaultTechnicianRating
to the required value. The updated values will be used with any run that uses this configuration profile.
Learn more about configuration profiles.
2. Example
The constraint is deactivated by default and needs to be activated with an override of 1 for the maximizeTechnicianRatingWeight property in the model config.
|
The following example shows how to define the technician ratings in the input. It also activates the constraint to maximize the technician ratings and defines a default value for vehicles that don’t have a rating defined.
In this case Beth has a rating of 4.0
.
Carl has no technician rating defined, but the default rating in the model configuration is 5.0
.
If all else is the same or very similar between Carl and Beth, Carl will be assigned the visits as he has a higher rating.
-
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": "Technician rating example"
},
"model": {
"overrides": {
"maximizeTechnicianRatingWeight": 1,
"defaultTechnicianRating": 5
}
}
},
"modelInput": {
"vehicles": [
{
"id": "Beth",
"technicianRating": 4.0,
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.77301, -84.43838],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"cost": {
"fixedCost": 100.0
}
}
]
},
{
"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",
"cost": {
"fixedCost": 100.0
}
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT2H"
},
{
"id": "Visit B",
"location": [33.74699, -84.02504],
"serviceDuration": "PT2H"
}
]
}
}
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": "Technician rating example",
"submitDateTime": "2025-05-15T14:41:20.255255+02:00",
"startDateTime": "2025-05-15T14:41:20.274576+02:00",
"activeDateTime": "2025-05-15T14:41:20.3483+02:00",
"completeDateTime": "2025-05-15T14:41:50.369955+02:00",
"shutdownDateTime": "2025-05-15T14:41:50.385054+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-1134239soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [],
"metrics": {
"totalTravelTime": "PT0S",
"travelTimeFromStartLocationToFirstVisit": "PT0S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT0S",
"totalTravelDistanceMeters": 0,
"travelDistanceFromStartLocationToFirstVisitMeters": 0,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 0,
"endLocationArrivalTime": null,
"technicianCosts": 100.0,
"overtime": null
}
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:19:25Z",
"startServiceTime": "2027-02-01T09:19:25Z",
"departureTime": "2027-02-01T11:19:25Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT19M25S",
"travelDistanceMetersFromPreviousStandstill": 16179,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T12:05:24Z",
"startServiceTime": "2027-02-01T12:05:24Z",
"departureTime": "2027-02-01T14:05:24Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT45M59S",
"travelDistanceMetersFromPreviousStandstill": 38320,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H35M44S",
"travelTimeFromStartLocationToFirstVisit": "PT19M25S",
"travelTimeBetweenVisits": "PT45M59S",
"travelTimeFromLastVisitToEndLocation": "PT30M20S",
"totalTravelDistanceMeters": 79782,
"travelDistanceFromStartLocationToFirstVisitMeters": 16179,
"travelDistanceBetweenVisitsMeters": 38320,
"travelDistanceFromLastVisitToEndLocationMeters": 25283,
"endLocationArrivalTime": "2027-02-01T14:35:44Z",
"technicianCosts": 100.0,
"overtime": null
}
}
]
}
]
},
"inputMetrics": {
"visits": 2,
"visitGroups": 0,
"vehicles": 2,
"mandatoryVisits": 2,
"optionalVisits": 0,
"vehicleShifts": 2
},
"kpis": {
"totalTravelTime": "PT1H35M44S",
"travelTimeFromStartLocationToFirstVisit": "PT19M25S",
"travelTimeBetweenVisits": "PT45M59S",
"travelTimeFromLastVisitToEndLocation": "PT30M20S",
"totalTravelDistanceMeters": 79782,
"travelDistanceFromStartLocationToFirstVisitMeters": 16179,
"travelDistanceBetweenVisitsMeters": 38320,
"travelDistanceFromLastVisitToEndLocationMeters": 25283,
"totalUnassignedVisits": 0,
"totalAssignedVisits": 2,
"assignedMandatoryVisits": 2,
"assignedOptionalVisits": 0,
"totalActivatedVehicles": 1,
"workingTimeFairnessPercentage": 0.0,
"totalTechnicianCosts": 200.0,
"totalOvertime": null,
"averageTechnicianRating": 5.0
}
}
modelOutput
contains the visits assigned to Carl’s shift itinerary.
The "averageTechnicianRating
of 5.0
is included in the KPIs.
Next
-
See the full API spec or try the online API.
-
Learn more about field service routing from our YouTube playlist.