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


# Project#evaluate

Scores alternative change sets against the solved baseline of a [`Project`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md>). The project isn't modified. Use it to compare options, such as which resource should take a task, before committing one with [`apply`](<https://docs.dhtmlx.com/planning-engine/js/api/project-apply.md>). Needs a solved project.

## Signature

```ts
evaluate(candidates: Candidate[], options?: EvaluateOptions): EvaluateResult
```

Synchronous. Runs on the calling thread, so a long run blocks the event loop.

## Parameters

| Name | Type | Description |
|---|---|---|
| `candidates` | [`Candidate`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md#candidate>)`[]` | The change sets to score |
| `options` | [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md#evaluateoptions>) | Optional. Baseline check, leveling stage, focus tasks and `clearFirst`. Default `{}` |

### Candidate

One alternative to score.

| Field | Type | Default | Description |
|---|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#id>) | required | Echoed in the matching [`CandidateResult`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md#candidateresult>). Uniqueness isn't checked |
| `changes` | [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/js/api/types/scheduling-changes.md>) | required | The edits this alternative makes. A `changes.baselineId` is checked like `options.baselineId` |

### EvaluateOptions

Options for one `evaluate` call. Every field is optional.

| Field | Type | Default | Description |
|---|---|---|---|
| `baselineId` | `string` | no check | Expected baseline token, taken from the latest result's `baselineId`. A mismatch throws `ProjectError` `"stale-baseline"` |
| `level` | [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/js/api/types/leveling-options.md>) `\| false` | `false` | Levels each candidate after scheduling it, with these options unchanged, and fills `leveled` in its result. `null` counts as `false`; `true` is invalid |
| `focus` | [`Id`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#id>) `\| Id[]` | none | Tasks to report finish and overload for, in the metrics' `finish.tasks` and `overload.focus` |
| `clearFirst` | `boolean` | `false` | Scores against a copy of the baseline with `leveling_delay` and `pauses` cleared on the tasks in scope: `level.scope.tasks` when given, otherwise every task. Independent of `level.clearFirst` |

:::note
`focus` takes one task id or an array of ids.
:::

## Behavior

- The reference state is the accepted baseline as it is, or its cleared copy with `clearFirst`. `evaluate` never levels the reference state itself.
- Each candidate runs on its own disposable copy of the reference state: `apply(changes)`, then `solve(null)` and measure the scheduled stage, then, with `level`, `level(options.level)` and measure the leveled stage. No candidate sees another candidate's changes.
- Both stages are compared against the same baseline metrics.
- A candidate whose changes are rejected, or whose solve or leveling fails on bad input, gets an `error` in its result and doesn't fail the call.
- With `clearFirst`, all checks run against the accepted state before anything is cleared, and the result's `baselineId` is still the accepted baseline's token.
- The result holds metrics only: no `updates` and no overallocation windows. To commit a candidate, pass its `changes` to `apply` and follow with `solve(null)`, or with `level` using the same options.

For what the metrics measure and how to pick a winner, see [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/js/resources-and-assignment/evaluating-alternatives.md>).

:::note
Candidates are scored one after another.
:::

## Returns

An [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md#evaluateresult>): a fresh object that shares nothing with the project. With `dates: "date"` on the project, the `date` fields of every finish are local `Date` objects.

### EvaluateResult

| Field | Type | Description |
|---|---|---|
| `baselineId` | `string` | The accepted baseline the candidates were scored against |
| `baseline` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/js/api/types/metrics.md>) | Metrics of the reference state itself. Every `delta` is `0` |
| `candidates` | [`CandidateResult`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md#candidateresult>)`[]` | One entry per candidate, in input order |

### CandidateResult

The score of one candidate. A rejected candidate has `id` and `error` only.

| Field | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#id>) | The candidate's `id` |
| `error` | [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#rejectedoperation>) | Present when the candidate failed: the first rejected operation, or the input error of its solve or leveling. No other field is present then |
| `scheduled` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/js/api/types/metrics.md>) | Present unless `error` is set. Metrics after applying the changes and solving |
| `leveled` | [`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/js/api/types/metrics.md#leveledmetrics>) | Present only with `options.level`, and absent when the candidate has a cycle. Metrics after leveling |
| `cost` | `number` | Present unless `error` is set. The `cost` of the last stage present: `leveled` when it exists, otherwise `scheduled`. Lower is better |

## Errors

| Error | When |
|---|---|
| [`ProjectError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md>) `"no-baseline"` | The project is unsolved, including after `apply` |
| `ProjectError` `"stale-baseline"` | `options.baselineId` or any candidate's `changes.baselineId` doesn't name the current baseline |
| [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md>) | An unknown task in `focus` (path `focus[i]`); `options.level` is invalid for the baseline, including `level: true`; an unknown option key or a malformed candidate; a non-finite number or an invalid `Date` in the input |
| `Error("Project has been disposed")` | The project was disposed |

A failure inside one candidate doesn't throw; it goes into that candidate's `error`.

## Example

Compare leaving the plan as it is with moving task `b` to an idle resource.

```js
import { Project } from "plen-js";

const project = new Project();
const solved = project.solve({
  tasks: [
    { id: "a", start_date: "2026-08-10 08:00", duration: 2 },
    { id: "b", start_date: "2026-08-10 08:00", duration: 2 },
  ],
  resources: [{ id: "dev1" }, { id: "dev2" }],
  assignments: [
    { id: "aA", task: "a", resource: "dev1", value: 1 },
    { id: "aB", task: "b", resource: "dev1", value: 1 },
  ],
});

const result = project.evaluate(
  [
    { id: "stay", changes: {} },
    { id: "dev2", changes: { assignments: { update: [{ id: "aB", resource: "dev2" }] } } },
  ],
  { baselineId: solved.baselineId, focus: "b" },
);

for (const candidate of result.candidates) {
  console.log(`${candidate.id}: cost ${candidate.cost}`);
}
// stay: cost 2
// dev2: cost 0
```

## Related

- [`Project`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md>) - the class and its other methods
- [`Metrics`](<https://docs.dhtmlx.com/planning-engine/js/api/types/metrics.md>) - what each candidate is scored on
- [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/js/api/types/leveling-options.md>) - the `level` option
- [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/js/resources-and-assignment/evaluating-alternatives.md>) - candidates, metrics and committing the winner
- [Run in a worker](<https://docs.dhtmlx.com/planning-engine/js/how-to/run-in-a-worker.md>) - keeping long runs off the UI thread
