Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
    • Task Scheduling
  • Platform
Try models
  • Timefold Solver SNAPSHOT
  • Responding to change
  • Assignment Recommendation API
  • Edit this Page

Timefold Solver SNAPSHOT

    • Introduction
    • Getting started
      • Overview
      • Build as a service
      • Embed as a library
        • Hello World guide
        • Quarkus guide
        • Spring Boot guide
    • Domain modeling
      • Guide
      • Building blocks
      • Common patterns
    • Constraints and score
      • Overview
      • Score calculation
      • Understanding the score
      • Load balancing and fairness
      • Performance tips and tricks
    • Running the Solver
      • Overview
      • As a service
        • REST API
        • Model Enrichment
        • Constraint weights
        • Demo data
        • Exposing metrics
      • As a library
        • Configuring Timefold Solver
        • Constraint weights
        • Quarkus integration
        • Spring Boot integration
        • JPA/JAXB/JSON integration
    • Diagnosing the Solver
      • Benchmarking
      • Solver diagnostics
    • Optimization algorithms
      • Overview
      • Construction heuristics
      • Local search
      • Exhaustive search
      • Custom moves
        • Neighborhoods API
        • Move Selector reference
    • Responding to change
      • Continuous planning
      • Real-time planning
      • Non-disruptive replanning
      • Assignment Recommendation API
    • Example use cases
      • Vehicle routing (guide)
      • More examples on GitHub
    • FAQ
    • New and noteworthy
    • Upgrading Timefold Solver
      • Upgrading Timefold Solver: Overview
      • Upgrade from Timefold Solver 1.x to 2.x
      • Upgrading from OptaPlanner
      • Backwards compatibility
      • Migration guides
        • Variable Listeners to Custom Shadow Variables
        • Chained planning variable to planning list variable
    • Commercial editions
      • Overview
      • Installation
      • Performance improvements
      • Score analysis
      • Recommendation API
      • Nearby selection
      • Multithreaded solving
      • Partitioned search
      • Constraint profiling
      • Multistage moves
      • Throttling best solution events

Assignment Recommendation API

This feature is exclusive to Timefold Solver Plus and Enterprise Editions.

With real-time planning, we can respond to a continuous stream of external changes. However, it is often necessary to respond to ad hoc changes too, for example when a call center operator needs to arrange an appointment with a customer. In such cases, it is not necessary to use the full power of real-time planning. Instead, immediate response to the customer and a selection of available time windows are more important. This is where Assignment Recommendation API comes in.

The Assignment Recommendation API allows you to quickly respond to ad hoc changes, while providing a selection of the best available options for fitting the change in the existing schedule. It doesn’t use the full local search algorithm. Instead, it uses a simple greedy algorithm together with incremental calculation. This combination allows the API to find the best possible fit within the existing solution in a matter of milliseconds, even for large planning problems.

Once the customer has accepted one of the available options and the change has been reflected in the solution, the full local search algorithm can be used to optimize the entire solution around this change. This would be an example of continuous planning.

1. Using the Assignment Recommendation API

The Assignment Recommendation API requires an entity to be evaluated for assignment:

EmployeeSchedule employeeSchedule = ...; // Our planning solution.
Shift unassignedShift = new Shift(...); // A new shift needs to be assigned.
employeeSchedule.getShifts().add(unassignedShift);

If the entity is unassigned, then it must be the only unassigned entity in the planning solution. The SolutionManager is then used to retrieve the recommended assignments for this entity:

SolutionManager<EmployeeSchedule, HardSoftScore> solutionManager = ...;
List<RecommendedAssignment<Employee, HardSoftScore>> recommendations =
    solutionManager.recommendAssignment(employeeSchedule, unassignedShift, Shift::getEmployee);

Breaking this down, we have:

  • employeeSchedule, the planning solution.

  • unassignedShift, the uninitialized entity, which is part of the planning solution.

  • Shift::getEmployee, a function extracting the planning variable from the entity, also called a "proposition function".

  • List<RecommendedAssignment<Employee, HardSoftScore>>, the list of recommended employees to assign to the shift, in the order of decreasing preference. Each recommendation contains the employee and the difference in score caused by assigning the employee to the shift. This difference has the full explanatory power of score analysis.

This list of recommendations can be used to present the operator with a selection of available options, as it is fully serializable to JSON and can be sent to a web browser or mobile app. The operator can then select the best available recommendation and assign the employee to the shift, represented here by the necessary backend code:

RecommendedAssignment<Employee, HardSoftScore> bestRecommendation = recommendations.get(0);
Employee bestEmployee = bestRecommendation.proposition();
unassignedShift.setEmployee(bestEmployee);

If required, continuous planning can be used to optimize the entire solution afterwards.

Assignment Recommendation API requires the SolutionManager to be configured with a construction heuristic as the first phase, as it uses that construction heuristic to find the best fit. If there are multiple construction heuristics phases in the solver configuration, or if the first phase is not a construction heuristic (perhaps a custom initializer), the API will fail fast.

2. Using mutable types in the proposition function

In the previous example, we used a simple proposition function that extracts the planning variable from the entity. However, it is also possible to use a more complex proposition function that extracts the entire planning entity, or any values that will mutate as the solver tries to find the best fit. In that case, there are some caveats to consider.

The solver will try to find the best fit for the uninitialized entity, and it will start from the solution it received on input. Before trying the next value to assign, it will first return to that original solution. The consequence of this is that if our proposition function returns any values that change during this process, those changes will also affect the previously processed propositions. In other words, if we decide to return the entire entity from the proposition function, we will find that each of the final recommendations is the same. And because the solver will return to the original solution after trying the last value, the final recommendation will be unassigned, defeating the purpose of the API. Consider the following example:

SolutionManager<EmployeeSchedule, HardSoftScore> solutionManager = ...;
List<RecommendedAssignment<Shift, HardSoftScore>> recommendations =
    solutionManager.recommendAssignment(employeeSchedule, unassignedShift, shift -> shift);

The proposition function (shift → shift) returns the entire Shift entity. Because of the behavior described above, every RecommendedAssignment in the recommendations list will point to the same unassignedShift, and its employee variable will be null. This is not what we want, because none of the RecommendedAssignment instances give us the Employee we need to assign to the shift.

To avoid this, the proposition function should preferably return a value that does not change during the process, such as the planning variable instead of the entire entity. If it’s necessary to return a value that could be mutated by the solver, we should make a defensive copy.

  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default