[Canonical page](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# SuggestResult

The staffing proposal from one [`Project#suggestAssignments`](<https://docs.dhtmlx.com/planning-engine/java/api/project-suggest-assignments.md>) 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`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>).

## Declaration

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/scheduling-changes.md>) | The net proposal, ready for `apply`. |
| `steps` | `Vec<`[`SuggestStep`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#suggeststep>)`>` | Decisions that changed the working state, in order. |
| `unfilled` | `Vec<`[`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>)`>` | Targeted empty slots left empty. |
| `invalid` | `Vec<`[`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>)`>` | Slots whose final resource isn't in their `allowed` list. |
| `baseline` | [`AssignTotal`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#assigntotal>) | Cost and resource count of the reference state. |
| `proposed` | [`SuggestProposal`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#suggestproposal>) | Cost, resource count and metrics of the final working state. |

### baselineId

```java
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

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-options.md#clearfirst>) is `true`.

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

### steps

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

One [`SuggestStep`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#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

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-options.md#targets>) aren't listed.

### invalid

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

Ids of every slot whose final resource isn't in its [`Slot.allowed`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/slot.md>) 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

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

The reference state, scored on the same stage as the steps. When [`SuggestOptions.level`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-options.md#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

```java
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

```java
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

```java
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

```java
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

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

Inherited from [`ValueRecord`](<https://docs.dhtmlx.com/planning-engine/java/api/support/value-record.md>). 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.

```java
public class SuggestStep extends ValueRecord implements DeepCloneable<SuggestStep>
```

| Field | Type | Description |
|---|---|---|
| `slot` | [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | The slot this step changed. |
| `task` | [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | 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`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | The resource the slot held before the step; `null` for a fill. |
| `toResource` | [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | The resource the slot holds after the step. Never `null` in a returned result. |
| `cost` | [`StepCost`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#baseline>) for the first step.

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#baseline>) holds one.

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#proposed>) holds one.

```java
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`](<https://docs.dhtmlx.com/planning-engine/java/api/suggest/suggest-result.md#assigntotal>). Default `0`. |
| `scheduled` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/java/api/evaluate/metrics.md>) | Metrics of the final state before leveling, as deltas against the reference state. With no steps, every scheduled delta is `0`. |
| `leveled` | [`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/java/api/evaluate/leveled-metrics.md>) | 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](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/suggesting-assignments.md>).

## Example

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

```java
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());
    }
  }
}
```

## Related

- [`Project#suggestAssignments`](<https://docs.dhtmlx.com/planning-engine/java/api/project-suggest-assignments.md>) - the call that returns it
- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/scheduling-changes.md>) - the type of `changes`
- [`Project#apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>) - commits the proposal
- [`Metrics`](<https://docs.dhtmlx.com/planning-engine/java/api/evaluate/metrics.md>) - the scheduled-stage metrics of the proposal
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/suggesting-assignments.md#reading-the-proposal>) - reading and committing a proposal
