Skills
Technicians have different skills and levels of experience which have an impact on which visits they should be assigned. It wouldn’t make any sense to send a plumber to a visit that requires an electrician.
Similarly, even within a skill, it might be necessary to ensure the electrician with the highest skill level is assigned to the most complex visits, while more junior technicians are assigned to less complex visits.
In addition to skill level, technicians have varying levels of effectiveness and complete visits at different rates.
This guide describes how to specify the skill and skill level of the technicians and the required skills for visits with the following examples:
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. Matching skills with visits
Visits require technicians with specific skills to complete the work. For instance, if Visit A is a task for an electrician, and both Beth and Carl could be assigned to the visit, but Beth is an electrician and Carl is a plumber, it’s necessary to match Beth’s skill as an electrician with the visit.
Technician’s skills are added to the shifts they’re available to work.
{
"vehicles": [
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"skills": [
{
"name": "electrician"
}
]
}
]
},
{
"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",
"skills": [
{
"name": "plumber"
}
]
}
]
}
]
}
The skills
arrays in the sample, show that Beth is an "electrician"
and Carl is a "plumber"
.
requiredSkills
for visits are declared as part of the visit:
{
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M",
"requiredSkills": [
{
"name": "electrician"
}
]
}
]
}
The requiredSkill
for Visit A is "electrician"
.
In addition to adding the technicians' skills to their shifts and the requiredSkills
to the visits,
skills
must also be declared as part of the modelInput
.
{
"skills": [ "electrician", "plumber" ]
}
See the following example. Failure to declare skills will result in a validation error.
Declaring skills ensures only declared skills are considered in the solution and prevents similar, but different skills from being included,
for instance, plumber
and pulmber
.
Timefold will match Beth’s skill "electrician" to the requiredSkill
for Visit A and assign Beth to the visit.
-
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": "Matching skills with visits example"
}
},
"modelInput": {
"vehicles": [
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"skills": [
{
"name": "electrician"
}
]
}
]
},
{
"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",
"skills": [
{
"name": "plumber"
}
]
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M",
"requiredSkills": [
{
"name": "electrician"
}
]
}
],
"skills": [ "electrician", "plumber" ]
}
}
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": "Matching skills with visits example",
"submitDateTime": "2024-08-20T05:10:01.30884112Z",
"startDateTime": "2024-08-20T05:10:06.930012466Z",
"activeDateTime": "2024-08-20T05:10:07.030012466Z",
"completeDateTime": "2024-08-20T05:15:07.698377551Z",
"shutdownDateTime": "2024-08-20T05:15:07.798377551Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-3569soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:29:57Z",
"startServiceTime": "2027-02-01T09:29:57Z",
"departureTime": "2027-02-01T10:59:57Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT29M57S",
"travelDistanceMetersFromPreviousStandstill": 31492,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"endLocationArrivalTime": "2027-02-01T11:29:29Z"
}
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-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
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"totalUnassignedVisits": 0
}
}
modelOutput
contains the solution for the dataset with Beth assigned to Visit A.
2. Skill level
Some jobs are more complicated than others and require experienced technicians. For instance, a newly qualified plumber is unlikely to have the same level of experience as a veteran of twenty years.
Skill level
is defined as a positive integer. 1 is the lowest skill level.
The higher the number, the more skilled the technician.
1 is the default value if no level is provided.
{
"id": "Carl-2027-02-01",
"skills": [
{
"name": "plumber",
"level": 3
}
]
}
Visits can include a minLevel
for a requiredSkill
.
If minLevel
is not defined, the default value null
will be used. This means any technician with any skill level could be assigned to the visit.
{
"id": "Visit A",
"requiredSkills": [
{
"name": "plumber",
"minLevel": 2
}
]
}
Timefold will assign a visit to a technician with the correct skill,
for instance "plumber" at or above the minLevel
specified for the visit.
In the following example, Timefold will assign Visit A to Carl because he has a skill level
3, and Beth only has a skill level
1.
-
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": "Matching skill levels with visits example"
}
},
"modelInput": {
"vehicles": [
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"skills": [
{
"name": "plumber",
"level": 1
}
]
}
]
},
{
"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",
"skills": [
{
"name": "plumber",
"level": 3
}
]
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M",
"requiredSkills": [
{
"name": "plumber",
"minLevel": 2
}
]
}
],
"skills": [ "plumber" ]
}
}
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": "Matching skill levels with visits example",
"submitDateTime": "2024-08-20T05:07:30.782484459Z",
"startDateTime": "2024-08-20T05:07:36.233619326Z",
"activeDateTime": "2024-08-20T05:07:36.333619326Z",
"completeDateTime": "2024-08-20T05:08:37.165206321Z",
"shutdownDateTime": "2024-08-20T05:08:37.265206321Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-8969soft",
"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
}
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:29:57Z",
"startServiceTime": "2027-02-01T09:29:57Z",
"departureTime": "2027-02-01T10:59:57Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT29M57S",
"travelDistanceMetersFromPreviousStandstill": 31492,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"endLocationArrivalTime": "2027-02-01T11:29:29Z"
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"totalUnassignedVisits": 0
}
}
modelOutput
contains the solution with Carl assigned to Visit A.
If a visit specifies a |
3. Skill multiplier
Some technicians, even at the same skill level, are more efficient than others and regularly complete visits quicker than other technicians.
The multiplier
is added to the skills definition.
multiplier
is a float number, for instance, 0.5
.
The visit serviceDuration
is multiplied by the multiplier
to determine the effectiveServiceDuration
of the visit.
With a multiplier
of 0.5
and a serviceDuration
of 1 hour, the effectiveServiceDuration
becomes 30 minutes.
{
"id": "Carl-2027-02-01",
"skills": [
{
"name": "plumber",
"multiplier": 0.5
}
]
}
-
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": "Skill multiplier example"
}
},
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"skills": [
{
"name": "plumber",
"multiplier": 0.5
}
]
}
]
},
{
"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",
"skills": [
{
"name": "plumber",
"multiplier": null
}
]
}
]
}
],
"visits": [
{
"id": "Visit A",
"location": [33.77301, -84.43838],
"serviceDuration": "PT1H30M",
"requiredSkills": [
{
"name": "plumber"
}
]
}
],
"skills": [ "plumber" ]
}
}
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": "Skill multiplier example",
"submitDateTime": "2024-08-20T07:43:15.458890029Z",
"startDateTime": "2024-08-20T07:43:21.032131885Z",
"activeDateTime": "2024-08-20T07:43:21.132131885Z",
"completeDateTime": "2024-08-20T07:43:37.791116599Z",
"shutdownDateTime": "2024-08-20T07:43:37.891116599Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-3569soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:29:57Z",
"startServiceTime": "2027-02-01T09:29:57Z",
"departureTime": "2027-02-01T10:14:57Z",
"effectiveServiceDuration": "PT45M",
"travelTimeFromPreviousStandstill": "PT29M57S",
"travelDistanceMetersFromPreviousStandstill": 31492,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"endLocationArrivalTime": "2027-02-01T10:44:29Z"
}
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-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
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT59M29S",
"travelTimeFromStartLocationToFirstVisit": "PT29M57S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M32S",
"totalTravelDistanceMeters": 65476,
"travelDistanceFromStartLocationToFirstVisitMeters": 31492,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 33984,
"totalUnassignedVisits": 0
}
}
modelOutput
contains the solution.
Ann has been assigned Visit A which has an effectiveServiceDuration
of 45 minutes, which is half the expected serviceDuration
.
Skill level
and multiplier
can be used together:
However, they have no effect on each other.
-
level
determines which technicians can be assigned to a visit. -
multiplier
determines theeffectiveServiceDuration
of visits.
If
|
3.1. Skill multipliers with multiple skills
Some visits require multiple skills. For instance, a visit might need a technician who is a plumber and an electrician, another visit might need a plumber who also speaks Spanish.
For a technician who is both a plumber and an electrician, these skills and their multipliers are added to the employee:
{
"id": "Carl-2027-02-01",
"skills": [
{
"name": "plumber",
"multiplier": 0.5
},
{
"name": "electrician",
"multiplier": 1.0
}
]
}
For a visit with a service duration of 1 hour that requires a plumber and an electrician, the multipliers are averaged and then multiplied by the service duration to give an effective service duration. In this case the effective service duration would be 45 minutes.
The multiplier isn’t always required.
For instance, when a visit requires a plumber who speaks Spanish,
because the skill spanish
, while essential, will not effect the speed of the work,
the multiplier can be omitted and the default value null
will be used.
{
"id": "Carl-2027-02-01",
"skills": [
{
"name": "plumber",
"multiplier": 0.5
},
{
"name": "spanish"
}
]
}
In this case, where only one multiplier has been explicitly added,
the average of the multipliers is 0.5
and a service duration of 1 hour would result in an effective service duration of 30 minutes.
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 Shift hours and overtime
-
Use time windows to specify visit availability and limit when visits can be scheduled.