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


# JsonProject.evaluate

Scores alternative change sets against the solved baseline of a [`JsonProject`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-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/wasm/api/project-apply.md>). Needs a solved project with no edits applied since the last [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) or [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>).

## Signature

```ts
evaluate(candidates_json: string, options_json: string): string
```

Synchronous and read-only. Bad input comes back as an error envelope. The call doesn't throw.

## Request

| Argument | Payload | Required | Description |
|---|---|---|---|
| `candidates_json` | [`Candidate`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#candidate>)`[]` | yes | The change sets to score, as a JSON array. `"[]"` scores the baseline only |
| `options_json` | [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#evaluateoptions>) | yes | `"{}"` for defaults |

### Candidate

One alternative to score. Unknown fields are rejected with a `parse` error.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>) | yes | | Echoed in the matching [`CandidateResult`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#candidateresult>). Uniqueness isn't checked |
| `changes` | [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) | yes | | The edits to try. `{}` scores the plan as it is |

### EvaluateOptions

The options object for `evaluate`. Every field can be omitted, and `null` reads as the default. Unknown fields are rejected with a `parse` error.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `baselineId` | string | no | absent (not checked) | Must name the current baseline, or the call fails with `stale-baseline` |
| `level` | [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/leveling-options.md>) \| `false` | no | `false` | Levels each candidate after scheduling and adds the `leveled` stage. `false` scores the scheduled stage only. `true` is a `validation` error |
| `focus` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>) \| `Id[]` | no | `[]` | Tasks to report finish and overload for. A single id is read as a one-item list |
| `clearFirst` | boolean | no | `false` | Scores against a copy of the baseline with `leveling_delay` and `pauses` cleared on the tasks in scope: `level.scope.tasks` when given, else every task |

## Behavior

Each candidate runs on a disposable copy of the reference state: the accepted baseline, or its cleared copy with `clearFirst`.

1. `apply` the candidate's changes.
2. `solve` without data and measure the scheduled stage.
3. With `level` set, `level` with those options and measure the leveled stage.

- Every candidate starts from the reference state, never from the previous candidate.
- A failure inside one candidate goes into that candidate's `error` and doesn't fail the call. That covers a rejected operation in its `changes` (the first one is reported) and a solve or level input error.
- `baselineId`, each candidate's `changes.baselineId`, `focus` ids and `level` options are checked against the accepted state before any candidate runs.
- The result has scores, not dates: no task updates and no overallocation windows. To see a candidate's dates, pass its `changes` to [`preview`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-preview.md>).

What the metrics measure and how `cost` is computed: [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) and [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/evaluating-alternatives.md>).

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

## Response

The success envelope carries an [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#evaluateresult>). Envelope shape: [Wire format](<https://docs.dhtmlx.com/planning-engine/wasm/wire-format.md#envelope>).

### EvaluateResult

The scores of the baseline and of every candidate.

| Field | Type | Description |
|---|---|---|
| `baselineId` | string | The accepted baseline the candidates were scored against. Always present, also with `clearFirst` |
| `baseline` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) | The reference state itself. All its deltas are 0 |
| `candidates` | [`CandidateResult`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#candidateresult>)`[]` | One entry per candidate, in input order |

### CandidateResult

The outcome of one candidate. A failed candidate has `id` and `error` only.

| Field | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>) | The candidate's `id` |
| `error` | `{ path, message }` | Present when the candidate failed. `path` points into its `changes`, e.g. `tasks.update[0]` |
| `scheduled` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) | The stage after `apply` and `solve`. Present unless the candidate failed |
| `leveled` | [`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md#leveledmetrics>) | The stage after `level`. Present only with `level` set, and absent when the candidate's plan is cyclic |
| `cost` | number | The `cost` of the last stage present. Lower is better. Absent when the candidate failed |

## Errors

| Kind | Code | When |
|---|---|---|
| `project` | `no-baseline` | The project has never been solved, or `apply` ran since the last solve |
| `project` | `stale-baseline` | `options.baselineId` or any candidate's `changes.baselineId` doesn't name the current baseline |
| `validation` | `null` | An unknown task in `focus` (path `focus[i]`), `level` options that are invalid for the baseline, or `level: true` (`path: null`) |
| `parse` | `null` | Malformed JSON, `candidates_json` isn't an array, or an unknown field in a candidate or the options |

Failed candidates aren't errors: they come back in `CandidateResult.error` inside an `ok: true` envelope. See [Error handling](<https://docs.dhtmlx.com/planning-engine/wasm/error-handling.md>).

## Example

Compare leaving the plan as it is with moving task `B` from `dev1` to the idle `dev2`. The third candidate updates a task that doesn't exist.

```js
const { JsonProject } = require("./pkg-node/plen_wasm.js");

const data = {
  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 candidates = [
  { id: "stay", changes: {} },
  { id: "dev2", changes: { assignments: { update: [{ id: "aB", resource: "dev2" }] } } },
  { id: "bad", changes: { tasks: { update: [{ id: "Z", duration: 1 }] } } },
];

const project = new JsonProject("{}");
try {
  const solved = JSON.parse(project.solve(JSON.stringify(data), "{}")).result;

  const options = { baselineId: solved.baselineId };
  const response = JSON.parse(
    project.evaluate(JSON.stringify(candidates), JSON.stringify(options)),
  );

  if (response.ok) {
    for (const candidate of response.result.candidates) {
      console.log(candidate.id, candidate.error ? candidate.error.message : candidate.cost);
    }
    // stay 2
    // dev2 0
    // bad unknown task id
  } else {
    const { kind, code, message, path } = response.error;
    console.error(kind, code, message, path);
  }
} finally {
  project.free();
}
```

`candidates_json`:

```json
[
  { "id": "stay", "changes": {} },
  { "id": "dev2", "changes": { "assignments": { "update": [{ "id": "aB", "resource": "dev2" }] } } },
  { "id": "bad", "changes": { "tasks": { "update": [{ "id": "Z", "duration": 1 }] } } }
]
```

`options_json` (the token comes from the `solve` result):

```json
{ "baselineId": "1-b0605af8cba3d7c9" }
```

Response:

```json
{
  "ok": true,
  "result": {
    "baseline": {
      "conflicts": { "delta": 0, "value": 0 },
      "cost": 2,
      "cycles": { "delta": 0, "value": 0 },
      "finish": { "project": { "date": "2026-08-12 08:00", "delta": 0 }, "tasks": [] },
      "overload": {
        "focus": [],
        "resources": [{ "overload": { "delta": 0, "value": 2 }, "resource": "dev1" }],
        "total": { "delta": 0, "value": 2 }
      }
    },
    "baselineId": "1-b0605af8cba3d7c9",
    "candidates": [
      {
        "cost": 2,
        "id": "stay",
        "scheduled": {
          "conflicts": { "delta": 0, "value": 0 },
          "cost": 2,
          "cycles": { "delta": 0, "value": 0 },
          "finish": { "project": { "date": "2026-08-12 08:00", "delta": 0 }, "tasks": [] },
          "overload": {
            "focus": [],
            "resources": [{ "overload": { "delta": 0, "value": 2 }, "resource": "dev1" }],
            "total": { "delta": 0, "value": 2 }
          }
        }
      },
      {
        "cost": 0,
        "id": "dev2",
        "scheduled": {
          "conflicts": { "delta": 0, "value": 0 },
          "cost": 0,
          "cycles": { "delta": 0, "value": 0 },
          "finish": { "project": { "date": "2026-08-12 08:00", "delta": 0 }, "tasks": [] },
          "overload": {
            "focus": [],
            "resources": [{ "overload": { "delta": -2, "value": 0 }, "resource": "dev1" }],
            "total": { "delta": -2, "value": 0 }
          }
        }
      },
      {
        "error": { "message": "unknown task id", "path": "tasks.update[0]" },
        "id": "bad"
      }
    ]
  }
}
```

## Related

- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) - the change set of each candidate
- [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) - the scores of each stage
- [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/leveling-options.md>) - the `level` option
- [`JsonProject.suggestAssignments`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-suggest-assignments.md>) - assignment proposals scored the same way
- [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/evaluating-alternatives.md>) - comparing candidates and reading the cost
