SuggestOptions#

Options for Project#suggestAssignments. They pick which slot categories the call works on, the assignments it must leave alone, the cost of bringing in one more person, the stage it scores on and how many rebalance passes it runs. new SuggestOptions() fills empty slots only, with no penalty, on the scheduled stage.

Declaration#

package org.dhtmlx.plen;

public class SuggestOptions extends ValueRecord implements DeepCloneable<SuggestOptions> {
    public String baselineId = null;
    public Vec<AssignTarget> targets = new Vec<AssignTarget>(java.util.Arrays.asList(AssignTarget.EMPTY));
    public Vec<Id> fixed = new Vec<Id>();
    public double newResourcePenalty = 0.0;
    public LevelingOptions level = null;
    public int passes = 1;
    public boolean clearFirst = false;
}

SuggestOptions is a mutable record. equals and hashCode compare the public fields, and toString() prints them as SuggestOptions{baselineId=…, targets=…, …}.

Constructors#

SuggestOptions()#

public SuggestOptions()

Creates options with every field at its default.

Fields#

Field Type Default Description
baselineId String null The baseline the call expects. null skips the check.
targets Vec<AssignTarget> [EMPTY] Slot categories to work on. An empty list changes no assignment.
fixed Vec<Id> empty Assignment ids the call never changes.
newResourcePenalty double 0.0 Cost added per extra person in use. 0 balances load only; a large value uses the fewest people.
level LevelingOptions null Scores every candidate on the leveled stage with these options. null scores the scheduled stage.
passes int 1 Number of rebalance passes over valid slots.
clearFirst boolean false Scores against a copy of the accepted state with levelingDelay and pauses cleared on the tasks in scope.

baselineId#

public String baselineId = null;

A baseline token taken from the latest ScheduleResult.baselineId. When it isn't null and doesn't name the project's current baseline, Project#suggestAssignments throws a ProjectException with code STALE_BASELINE.

targets#

public Vec<AssignTarget> targets = new Vec<AssignTarget>(java.util.Arrays.asList(AssignTarget.EMPTY));

Any combination of the three AssignTarget values, each listed at most once. EMPTY and BROKEN slots are handled in pass 0, and VALID slots in passes 1..passes.

With an empty list the call is a check: it changes no assignment and only reports the slots whose resource is outside allowed in SuggestResult.invalid. A value listed twice makes the call throw an InputException at path targets[i].

fixed#

public Vec<Id> fixed = new Vec<Id>();

Ids of active assignments that stay as they are. A slot bound to one of them is never visited in any pass. Its resource still counts as on the task and as a person in use, and the slot is still reported in SuggestResult.invalid when its resource is outside allowed.

An id bound to no slot is accepted. An unknown or inactive id, or one listed twice, makes the call throw an InputException at path fixed[i].

newResourcePenalty#

public double newResourcePenalty = 0.0;

The cost of one more person in use, added to each state's score. The people counted are those named in any slot's allowed list or holding a filled slot, and only while they have at least one active assignment. Once a person is brought in, later slots use them for free, and a move that frees a person lowers the cost.

With 0, the score is load balance only. A large value trades balance for the fewest workers. Must be finite and >= 0; otherwise the call throws an InputException at path newResourcePenalty.

level#

public LevelingOptions level = null;

When set, each candidate is leveled with these options and scored on the leveled stage. SuggestResult.proposed.leveled is set only in this mode (see SuggestResult). Leveled scoring runs one leveling per candidate, so keep it for small scopes. Invalid leveling options make the call throw an InputException.

With level set, the call doesn't return when an assignment asks for more units than its resource can ever supply. Project#level has the same bug.

Commit a proposal scored this way with apply(result.changes) followed by level with the same options.

passes#

public int passes = 1;

The maximum number of rebalance passes over VALID slots. A pass that moves nothing ends the loop early. It has no effect unless targets contains VALID. Must be >= 1; otherwise the call throws an InputException at path passes.

clearFirst#

public boolean clearFirst = false;

When true, the call works from a solved copy of the accepted state with levelingDelay and pauses cleared on the tasks in scope. Everything the call measures and builds on comes from that copy. SuggestResult.changes then starts with one tasks.update per cleared task, so a commit reproduces the scored state. The clears are returned even when targets is empty or no slot moves.

This field is separate from level.clearFirst and works with level set to null. With nothing to clear, the result is the same as with false.

Methods#

withBaselineId, withTargets, withFixed, withNewResourcePenalty, withLevel, withPasses, withClearFirst#

public SuggestOptions withBaselineId(String value)
public SuggestOptions withTargets(Vec<AssignTarget> value)
public SuggestOptions withTargets(java.util.Collection<? extends AssignTarget> value)
public SuggestOptions withFixed(Vec<Id> value)
public SuggestOptions withFixed(java.util.Collection<? extends Id> value)
public SuggestOptions withNewResourcePenalty(double value)
public SuggestOptions withLevel(LevelingOptions value)
public SuggestOptions withPasses(int value)
public SuggestOptions withClearFirst(boolean value)

Each setter assigns its field and returns this instance, for chaining. The Collection overloads copy the argument into a new Vec, so List.of(...) works.

copy#

public SuggestOptions copy()

Returns a shallow copy. The copy shares the targets and fixed lists and the level object with this instance.

deepClone#

public SuggestOptions deepClone()

Returns a copy with its own targets and fixed lists and its own LevelingOptions.

AssignTarget#

A slot category, judged from the slot's current resource. Every slot falls into exactly one category.

public enum AssignTarget { EMPTY, BROKEN, VALID }
Value Description
EMPTY The slot has no assignment. Targeted empty slots are filled in pass 0.
BROKEN The slot's resource isn't in its allowed list. Targeted broken slots are repaired in pass 0.
VALID The slot's resource is in its allowed list. Targeted valid slots are rebalanced in passes 1..passes, and a slot moves only when the move strictly lowers the cost.

Example#

Fill empty slots, repair broken ones and rebalance valid ones, keeping assignment aLead where it is and charging for each extra person.

import java.util.List;
import org.dhtmlx.plen.*;

public class SuggestOptionsExample {
  static TaskData task(String id) {
    return new TaskData(Id.str(id))
        .withStartDate(Moment.fromYmdHm(2026, 8, 10, 0, 0))
        .withDuration(2.0);
  }

  public static void main(String[] args) throws Exception {
    ProjectData data = new ProjectData()
        .withTasks(List.of(task("A"), task("B")))
        .withResources(List.of(
            new ResourceData().withId(Id.str("dev1")),
            new ResourceData().withId(Id.str("dev2"))))
        .withAssignments(List.of(new AssignmentData()
            .withId(Id.str("aLead"))
            .withTask(Id.str("A"))
            .withResource(Id.str("dev1"))
            .withValue(1.0)));

    Project project = new Project();
    project.solve(data, new SolveOptions());

    List<Id> devs = List.of(Id.str("dev1"), Id.str("dev2"));
    List<Slot> slots = List.of(
        new Slot(Id.str("aLead"), Id.str("A"), devs).withAssignment(Id.str("aLead")),
        new Slot(Id.str("sB"), Id.str("B"), devs));

    SuggestOptions options = new SuggestOptions()
        .withTargets(List.of(AssignTarget.EMPTY, AssignTarget.BROKEN, AssignTarget.VALID))
        .withFixed(List.of(Id.str("aLead")))
        .withNewResourcePenalty(0.5)
        .withPasses(2);

    SuggestResult result = project.suggestAssignments(slots, options);
    for (SuggestStep step : result.steps) {
      System.out.println(step.slot + " pass " + step.pass + " -> " + step.toResource);
    }
  }
}