Project#evaluate#

Scores alternative change sets against the solved baseline of a Project. The project isn't modified. Use it to compare options, such as which resource should take a task, before committing one with apply. Needs a solved project.

Signature#

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[] The change sets to score
options EvaluateOptions Optional. Baseline check, leveling stage, focus tasks and clearFirst. Default {}

Candidate#

One alternative to score.

Field Type Default Description
id Id required Echoed in the matching CandidateResult. Uniqueness isn't checked
changes SchedulingChanges 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 | 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 | 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.

Note

Candidates are scored one after another.

Returns#

An 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 Metrics of the reference state itself. Every delta is 0
candidates 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 The candidate's id
error 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 Present unless error is set. Metrics after applying the changes and solving
leveled 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 "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 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.

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