SuggestResult#

The staffing proposal from one Project#suggestAssignments call: the net change set, every step that changed the working state, the slots left unfilled or invalid, and the cost of the reference and final states. Only suggestAssignments returns one. The project isn't modified until changes is passed to Project#apply.

Declaration#

package org.dhtmlx.plen;

public class SuggestResult extends ValueRecord implements DeepCloneable<SuggestResult>

Fields#

Field Type Description
baselineId String Token of the accepted state the proposal was scored against.
changes SchedulingChanges The net proposal, ready for apply.
steps Vec<SuggestStep> Decisions that changed the working state, in order.
unfilled Vec<Id> Targeted empty slots left empty.
invalid Vec<Id> Slots whose final resource isn't in their allowed list.
baseline AssignTotal Cost and resource count of the reference state.
proposed SuggestProposal Cost, resource count and metrics of the final working state.

baselineId#

public String baselineId = null;

The accepted baseline token at the time of the call. suggestAssignments always sets it, and changes.baselineId holds the same value. apply rejects the change set when the project changed since the call.

changes#

public SchedulingChanges changes = null;

The net difference between the accepted state and the final working state:

  • assignments.add: one entry per filled slot, with id = the slot id, task, resource and value. A slot filled and later moved in a rebalance pass is one add with its final resource.
  • assignments.update: one entry (id, resource) per repaired or rebalanced assignment, however many passes moved it. An assignment that ends on its original resource gets no entry.
  • tasks.update: the clears made when SuggestOptions.clearFirst is true.

It holds no removals and no leveling delays. With no steps and no clears, it carries only baselineId.

steps#

public Vec<SuggestStep> steps = new Vec<SuggestStep>();

One SuggestStep per decision that changed the working state, in the order the decisions were made. A slot visit that kept its current resource adds no step.

unfilled#

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

Ids of targeted empty slots that the fill pass (pass 0) left empty because no allowed resource could be scored, in visit order. Empty slots outside SuggestOptions.targets aren't listed.

invalid#

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

Ids of every slot whose final resource isn't in its Slot.allowed list, in visit order. Fixed slots are included, and the list is filled whatever targets is set to. suggestAssignments never removes an assignment, so these slots keep their current resource.

baseline#

public AssignTotal baseline = new AssignTotal(0, 0);

The reference state, scored on the same stage as the steps. When SuggestOptions.level is set, this is the reference state leveled as is. With clearFirst, the reference state is the cleared copy. The first step's cost.before equals baseline.cost.

proposed#

public SuggestProposal proposed = null;

The final working state, measured against the reference state. With no steps, it describes the reference state itself.

Methods#

withBaselineId, withChanges, withSteps, withUnfilled, withInvalid, withBaseline, withProposed#

public SuggestResult withBaselineId(String value)
public SuggestResult withChanges(SchedulingChanges value)
public SuggestResult withSteps(Vec<SuggestStep> value)
public SuggestResult withSteps(java.util.Collection<? extends SuggestStep> value)
public SuggestResult withUnfilled(Vec<Id> value)
public SuggestResult withUnfilled(java.util.Collection<? extends Id> value)
public SuggestResult withInvalid(Vec<Id> value)
public SuggestResult withInvalid(java.util.Collection<? extends Id> value)
public SuggestResult withBaseline(AssignTotal value)
public SuggestResult withProposed(SuggestProposal value)

Each setter assigns its field and returns this instance. The Collection overloads copy the elements into a new Vec, and null sets the field to null.

Returns: this SuggestResult.

deepClone#

public SuggestResult deepClone()

Returns a copy that shares no list or nested record with this one.

Returns: a new SuggestResult.

Errors: NullPointerException when changes, steps, unfilled, invalid, proposed or proposed.scheduled is null. A result returned by suggestAssignments has all of them set.

copy#

public SuggestResult copy()

Returns a shallow copy. The new result shares changes, the lists and proposed with this one, but holds its own copy of baseline.

Returns: a new SuggestResult.

equals, hashCode, toString#

public boolean equals(Object other)
public int hashCode()
public String toString()

Inherited from ValueRecord. Two results are equal when all fields are equal, lists element by element. toString() prints all fields, for logging.

SuggestStep#

One decision that changed the working state. The kind of step follows from its fields: fromResource == null is a fill, fromResource != null with pass == 0 is a repair, and pass >= 1 is a rebalance.

public class SuggestStep extends ValueRecord implements DeepCloneable<SuggestStep>
Field Type Description
slot Id The slot this step changed.
task Id The slot's task.
pass int 0 for a fill or repair; 1 and up for the rebalance pass that made the move. Default 0.
fromResource Id The resource the slot held before the step; null for a fill.
toResource Id The resource the slot holds after the step. Never null in a returned result.
cost StepCost Working-state cost before and after the step.

Each field has a with{Field}(value) setter that returns this instance. deepClone() and copy() both return a new SuggestStep with its own StepCost; the Id values are shared.

StepCost#

The working-state cost around one step, penalty included, on the scoring stage. before is the previous step's after, or baseline.cost for the first step.

public class StepCost extends ValueRecord {
  public double before;
  public double after;

  public StepCost(double before, double after)
}
Field Type Description
before double Cost before the step.
after double Cost after the step.

StepCost has no deepClone. copy() returns a new StepCost with the same values.

AssignTotal#

Cost and resource count of a state. SuggestResult.baseline holds one.

public class AssignTotal extends ValueRecord {
  public double cost;
  public int resources;

  public AssignTotal(double cost, int resources)
}
Field Type Description
cost double Cost of the state on the scoring stage.
resources int Number of counted-pool resources with at least one active assignment. The counted pool is every slot's allowed list plus the current resources of filled slots.

AssignTotal has no deepClone. copy() returns a new AssignTotal with the same values.

SuggestProposal#

The final working state, compared with the reference state. SuggestResult.proposed holds one.

public class SuggestProposal extends ValueRecord implements DeepCloneable<SuggestProposal>
Field Type Description
cost double Cost of the final state, newResourcePenalty included. Compare with baseline.cost. Default 0.0.
resources int Number of resources in use in the final state, counted as in AssignTotal.resources. Default 0.
scheduled Metrics Metrics of the final state before leveling, as deltas against the reference state. With no steps, every scheduled delta is 0.
leveled LeveledMetrics Metrics of the final state after leveling. Set only when SuggestOptions.level is set; null otherwise.

Each field has a with{Field}(value) setter that returns this instance. deepClone() throws NullPointerException when scheduled is null; copy() returns a shallow copy.

With GapBehavior.COMPRESS, committing changes (and leveling with the same options) reproduces proposed. With GapBehavior.PRESERVE, proposed is an estimate and the committed plan can finish on a different date. See Suggesting assignments.

Example#

Fill one open slot and read the proposal before committing it:

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

public class SuggestResultExample {
  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("C")))
        .withResources(List.of(
            new ResourceData().withId(Id.str("dev1")),
            new ResourceData().withId(Id.str("dev2"))))
        .withAssignments(List.of(new AssignmentData()
            .withId(Id.str("aA"))
            .withTask(Id.str("A"))
            .withResource(Id.str("dev1"))
            .withValue(1.0)));
    data.config.useCalendars = false;

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

    List<Slot> slots = List.of(
        new Slot(Id.str("sC"), Id.str("C"), List.of(Id.str("dev1"), Id.str("dev2"))));
    SuggestResult result = project.suggestAssignments(slots, new SuggestOptions());

    for (SuggestStep step : result.steps) {
      String kind = step.fromResource == null ? "fill"
          : step.pass == 0 ? "repair" : "rebalance";
      System.out.println(kind + " " + step.slot + " -> " + step.toResource
          + " (cost " + step.cost.before + " -> " + step.cost.after + ")");
    }
    System.out.println("unfilled: " + result.unfilled + ", invalid: " + result.invalid);
    System.out.println("cost " + result.baseline.cost + " -> " + result.proposed.cost);

    ApplyReport report = project.apply(result.changes);
    if (report.rejected.isEmpty()) {
      project.solve(null, new SolveOptions());
    }
  }
}