AI-based Scheduling


Attention: AI-based optimization and scheduling only works for internal technicians. Crowd is currently not supported.

Looking to optimize your field service according to your specific requirements? Want to minimize driving distances so that your technicians can hit more jobs per day?

SAP Field Service Management offers a flexible framework for automated planning and optimization that caters to the individual requirements and use cases of each customer.

The framework accepts plugins to contain individually tailored target functions that rate how well a technician is suitable for a job. The target functions can be written by the customers themselves.

This enables the customers to programmatically reflect all criteria that are relevant for planning and optimization of the field service activities, such as:

  • Distance between the current location of the technician and the planned job
  • the next available timeslot
  • if a technician is familiar with the customer and their equipment, etc.

It is up to the customer to consider multiple factors and weigh them out against each other (as soft vs. hard constraints) in the target function.

Not sure how to start and want to try out an out-of-the-box solution? Use our standard plugins to optimize the following:

  • by distance to the job (nearest)
  • find the first available technician (fastest)
  • or the one with the most adequate skill set (best).

Note: As of today, plugins shall be deployed individually for every customer.

A Closer Look

Typically, there are three common cases addressed by automation:

  1. Scheduling of a job upon creation
  2. Periodic re-optimization of the entire board
  3. Rescheduling of overlapping jobs and reservations, e.g. urgent jobs, sick leaves, etc.

Within SAP Field Service Management, automatic scheduling can be triggered by a business rule. It can as well be triggered externally, e.g. via an external service or lambda function (additional provisioning shall reside with the customer). To trigger scheduling and optimization externally, refer to Re-Optimization API.

Business Rules

Within the business rule, the following shall be specified:

  • Trigger – specifies when scheduling/optimization shall be executed
  • SQL statement – specifies which data (jobs and technicians) shall be affected
  • Partitioning strategy – specifies the parameters by which jobs and technicians is being divided into logical units to run scheduling and optimization for the unit. Learn more here.
  • Plugin to be used – specifies which of the available plugins shall be used for scheduling.

The business rules shall have to be configured per company:

Configure the trigger of the business rule to schedule either periodic or event-based optimization.

Configure following actions:

  • first, for getting an authorization token
  • second, to define the list of activities to be reoptimized, partitioning strategy, and the plugin to be used

For Action #0,

  • select Action Webhook
  • select Method POST
  • maintain URL according to the following pattern: https://<cluster><api>/oauth2/<v>/token
  • maintain Content type:/x-www-form-urlencoded
  • maintain Body according to the following pattern: grant_type=client_credentials
  • maintain Response Variable Name: accessToken

For Action #1:

  • select Action Webhook
  • select Method POST
  • maintain URL: https://<cluster>
  • maintain Content type: application/json
  • maintain Body according to the following pattern:

{ "activityIds": [<comma separated list of activity IDs, as strings>], "optimizationPlugin": "<name of the default plugin from the ones deployed for the company>", "start": "${moment().add(1, 'hour').toISOString()}", "end": "<end of the time span to be reoptimized - use the pattern for the start>", "partitioningStrategy": { "name": "<parameter for the partitioning strategy if needed>", "<parameter name>": [ "<instance 1 name>", "<instance 2 name>" ] } }

  • maintain Response Variable Name: reOptimizeResponse

Note: for multiple use cases (i.e. automated scheduling upon job creation, periodic rescheduling, rescheduling of overlapping jobs), multiple business rules will need to be configured.


Once there is a new event that triggers a business rule, scheduling and optimization is started according to the parameters specified in the selected plugin. There are three standard plugins that can be utilized by the customers.

Rank by Earliest Start

The difference between a constant and the difference between proposed start and job earliest start is returned. A quotient could also be used, the difference being used for readability. The assignments which start earlier will be returned on top of the list.

Name: Quickest

public Integer score(IResource resource, IJob job, IAssignment assignment, ILocation location) {
     return 1000 - (int)(
         (assignment.getStartDate().getTime() - job.getEarliestStartDate().getTime()) / (1000 * 60));

Rank by Distance to the Next Assignment

The difference between a constant and the distance is returned, as a measure of proximity. A quotient could also be used, the difference being used for readability.

Name: Nearest

public Integer score(IResource resource, IJob job, IAssignment assignment, ILocation location) {
    return location != null && job.getLocation() != null ? (600 - OptimizerLibrary.travelTimeInMinutes(job.getLocation(), location)) : -1;

Rank by Overlapping Skills

Counts the sizeof the set containing the skills both in the job and resource.

Name: Best

public Integer score(IResource resource, IJob job, IAssignment assignment, ILocation location) {
    double requiredSkillsOnActivity = job.getRequirements().size();
    double optionalSkillsOnActivity = job.getOptionalRequirements().size();
    if (requiredSkillsOnActivity + optionalSkillsOnActivity == 0) {
        return 100;
    List<String> resourceSkills = resource.getSkills();
    List<String> matchingSkills = job.getRequirements().stream().filter(elem -> resourceSkills.contains(elem)).collect(Collectors.toList());
    List<String> matchingOptionalSkills =  job.getOptionalRequirements().stream().filter(elem -> resourceSkills.contains(elem)).collect(Collectors.toList());
    double requiredSkillsMatched = matchingSkills.size();
    double optionalSkillsMatched = matchingOptionalSkills.size();
    if (optionalSkillsOnActivity == 0) {
        return (int) Math.ceil((requiredSkillsMatched / requiredSkillsOnActivity) * 100);
    } else if (requiredSkillsOnActivity == 0) {
        return (int) Math.ceil((optionalSkillsMatched / optionalSkillsOnActivity) * 100);
    } else {
        return (int) Math.ceil((((requiredSkillsMatched / requiredSkillsOnActivity)
                + ((optionalSkillsMatched / optionalSkillsOnActivity) / (requiredSkillsMatched + 1)))
                * ((requiredSkillsMatched + 1) / (requiredSkillsOnActivity + 2))) * 100);

Rank by Overlapping Skills and Distance

Filters out technicians with missing skills or the ones that have refused the job (hence, are blacklisted for the job). Does not consider technicians or jobs which do not have a location. Sorts the remaining technicians by the shortest travel time.

Name: SkillsAndDistance

public Integer score(IResource resource, IJob job, IAssignment assignment, ILocation location) {
    List<String> resourceSkills = resource.getSkills();
    List<String> jobRequirements = job.getRequirements();
    List<String> missingSkills = -> !resourceSkills.contains(elem)).collect(Collectors.toList());
    if(missingSkills != null && missingSkills.size() > 0) {
      OptimizerLibrary.logScore(job, resource, -1, "Technician does not have the following mandatory skills: " + missingSkills);
      return -1;
    else if (job.getResourceBlackList() != null && job.getResourceBlackList().contains(resource.getFSMId())) {
       OptimizerLibrary.logScore(job, resource, -1, "Technician is blacklisted for this job.");
       return -1;
    else if(job.getLocation() == null) {
        OptimizerLibrary.logScore(job, resource, -1, "Job does not have location set.");
        return -1;
    else if(location == null) {
        OptimizerLibrary.logScore(job, resource, -1, "Resource or previous job does not have location set.");
        return -1;
    else {
        return (600 - OptimizerLibrary.travelTimeInMinutes(job.getLocation(), location));

Customers are free to define their own plugins to address their specific use cases (multiple parameters can be considered within the plugin).

Note: customers shall be guided through definition of their custom plugins. The plugins shall be deployed for every customer individually.


For a lot of businesses, field service activities are executed according to divisions within a company, e.g. based on work centers, regions, etc. Thus, optimizing the planning board on a company level is insufficient. To address those requirements, partitioning strategy can be defined (for detailed instructions, see <configuration of the BR - link>).

When partitioning strategy is maintained, optimization process shall be triggered per partition. That means, jobs belonging to the same partition shall be optimized together. Different partitions shall be optimized in parallel (currently, only sequential optimization is supported).

As of 1911, partitioning can be done based on skills. In order for partitioning to work, relevant skills have to be maintained on jobs and technicians as well as specified in the body of the business rule in the partitioning strategy section:

	"activityIds": ["${var x=[]; activities.forEach(function(a) { x.push(; }); x.join('\",\"')}"],
	"optimizationPlugin": "Nearest",
	"start": "${moment().add(1, 'hour').toISOString()}",
	"end":  "<end of the time span to be reoptimized - use the pattern for the start>",
	"partitioningStrategy": {
		"name": "Skill",
		"skills": [
			"skill name 2",
			"skill name 1"


In Business-to-Business line of work, field service technicians often deal with several machines that are located at the same geographical address. Thus, equipment data is relevant for scheduling of field service activities. Equipment data can now be exposed to custom plugins to be utilized for planning purposes. This way, if needed, jobs can be bundled together not only based on the geographical address, but also based on equipment located at this address. So that a technician would first perform all jobs on one piece of equipment before moving to the next one.

Job Status and Due Date

Oftentimes, automated scheduling of jobs happens in a bulk right after creation or replication of jobs into FSM. Only after having been scheduled the jobs are released to the field technicians. However, sometimes, there are events - like receiving of an urgent job - that compromise the existing schedule and require rescheduling of already scheduled and released jobs. Now, autoscheduler can reschedule also jobs that already have a status “Released”. Furthermore, after the autoscheduler has assigned jobs to technicians the assignments shall be automatically released so that there is no need for the dispatcher to manually release the jobs to field technicians. This configuration option can be switched off so that there is no automatic release of the jobs when autoscheduler has assigned technicians to all jobs.

Additionally, the autoscheduler considers jobs that are due in the future as well as jobs on which the deadline has been missed. The due date no longer has to be in the future - jobs with the missed deadline shall be scheduled along with the ones that are due in the future. However, there is no special treatment of the jobs with a missed deadline. Special handling of the jobs that were due in the past should be considered in a custom plugin.

UDFs in plugins

When writing a custom plugin for optimization of planning board, you can consider User Defined Fields introduced to your master data. As of 2002, User Defined Fields are supported for ServiceCall, Activity, Person, Skill, end Equipment entities.

This way, optimization can be run based on ANY parameter that is relevant for your specific use case. For example, to consider technicians’ ratings, a dedicated UDF will have to be added to the technician. The field would have to be addressed in the plugin, for example as follows:

import java.util.List;
import java.util.ArrayList;
import net.coresystems.autoscheduler.domain.interfaces.*;
import net.coresystems.autoscheduler.service.spatial.EuclideanDistanceService;
import net.coresystems.autoscheduler.service.optimize.OptimizerLibrary;

public class Plugin implements IOptimizationPlugin {
    public net.coresystems.autoscheduler.domain.Algorithm getAlgorithm() {
        return net.coresystems.autoscheduler.domain.Algorithm.GREEDY_RESOURCES_FIRST;

    public List<IResource> filterResources(List<IResource> resources) {
        return new ArrayList();

    public Integer score(IResource resource, IJob job, IAssignment assignment, ILocation location) {

        Integer resourceRating = resource.getUDFValue("RATING") != null ? Integer.valueOf(resource.getUDFValue("RATING")): null;
        if (resourceRating == null) {
           return 0;

        return resourceRating;

Sample Business Rules

For guidance on creating business rules for automated scheduling, please refer to the following guide:

Business Rules for Optimization

Additional Information

For additional information regarding the Re-Optimization API documentation. please refer to the following:

Re-Optimization API