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


# CandidateResult

The outcome of one [`Candidate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate.md>): its metrics at the scheduled stage and, with leveling, at the leveled stage, or the error that rejected it. [`Project::evaluate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#evaluate>) and [`Project::evaluate_with`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#evaluate_with>) return one per candidate in [`EvaluateResult::candidates`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>), in input order.

## Declaration

```rust
#[derive(Clone, Debug, PartialEq)]
pub struct CandidateResult {
    pub id: Id,
    pub error: Option<RejectedOperation>,
    pub scheduled: Option<Metrics>,
    pub leveled: Option<LeveledMetrics>,
    pub cost: Option<f64>,
}
```

## Fields

| Field | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>) | The candidate's `id`, echoed. Uniqueness isn't checked. |
| `error` | `Option<`[`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/rejected-operation.md>)`>` | Why the candidate failed: the first rejected operation of its change set, or an input error from its solve or level run. `None` when the candidate was scored. |
| `scheduled` | `Option<`[`Metrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/metrics.md>)`>` | Metrics after the candidate's changes are applied and solved. `Some` whenever `error` is `None`. |
| `leveled` | `Option<`[`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/leveled-metrics.md>)`>` | Metrics after leveling, plus the leveling summary. `Some` only when [`EvaluateOptions::level`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-options.md>) is set and the scheduled stage has no cycles. |
| `cost` | `Option<f64>` | The `cost` of the last stage present: `leveled`, else `scheduled`. Lower is better. `None` when `error` is set. |

`error` excludes every other optional field: a rejected candidate carries no metrics and no cost. The cost formula is on [`Metrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/metrics.md>).

## Field combinations

| Case | `error` | `scheduled` | `leveled` | `cost` |
|---|---|---|---|---|
| Rejected | `Some` | `None` | `None` | `None` |
| Scored, no `level` | `None` | `Some` | `None` | from `scheduled` |
| Scored with `level` | `None` | `Some` | `Some` | from `leveled` |
| Cyclic, with `level` | `None` | `Some` (`cycles.value > 0`) | `None` | from `scheduled` |

A cyclic candidate isn't rejected: `scheduled` is measured on the dates the cyclic solve returns.

## Serialization

With the `serde` feature, `CandidateResult` implements `Serialize` and `Deserialize`. Field names are camelCase, and `None` fields are left out of the output.

## Example

Score two candidates with leveling. The second one names a resource that doesn't exist, so its result carries only `error`.

```rust
use plen_core::{
    AssignmentData, AssignmentUpdate, Candidate, DateTime, EvaluateOptions, LevelingOptions,
    Patch, Project, ProjectData, ResourceData, SchedulingChanges, SolveOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Two tasks book "ann" at the same time; "bob" is free.
    let monday = DateTime::from_ymd_hm(2026, 3, 2, 8, 0).ok_or("invalid date")?;
    let task = |id: &str| TaskData {
        start_date: Some(monday),
        duration: Some(3.0),
        ..TaskData::new(id)
    };
    let resource = |id: &str| ResourceData { id: id.into(), capacity: None, calendar_id: None };
    let booking = |id: &str, task: &str| AssignmentData {
        id: Some(id.into()),
        task: task.into(),
        resource: "ann".into(),
        value: 1.0,
        offset: None,
        duration: None,
    };

    let mut project = Project::default();
    project.solve(
        Some(ProjectData {
            tasks: vec![task("design"), task("build")],
            resources: vec![resource("ann"), resource("bob")],
            assignments: vec![booking("a1", "design"), booking("a2", "build")],
            ..ProjectData::default()
        }),
        &SolveOptions::default(),
    )?;

    // Candidate "to-bob" is valid; "to-carol" names a resource that doesn't exist.
    let move_build_to = |resource: &str| {
        let mut changes = SchedulingChanges::default();
        changes.assignments.update.push(AssignmentUpdate {
            resource: Patch::Value(resource.into()),
            ..AssignmentUpdate::new("a2")
        });
        Candidate { id: format!("to-{resource}").into(), changes }
    };
    let candidates = [move_build_to("bob"), move_build_to("carol")];

    let options = EvaluateOptions {
        level: Some(LevelingOptions::default()),
        ..EvaluateOptions::default()
    };
    let result = project.evaluate(&candidates, &options)?;

    for scored in &result.candidates {
        if let Some(error) = &scored.error {
            println!("{}: rejected at {}: {}", scored.id, error.path, error.message);
            continue;
        }
        if let Some(scheduled) = &scored.scheduled {
            println!("{}: scheduled cost {}, overload delta {}", scored.id, scheduled.cost, scheduled.overload.total.delta);
        }
        if let Some(leveled) = &scored.leveled {
            println!("{}: leveled in {} rounds", scored.id, leveled.rounds);
        }
        if let Some(cost) = scored.cost {
            println!("{}: cost {cost}", scored.id);
        }
    }
    Ok(())
}
```

Output:

```
to-bob: scheduled cost 0, overload delta -3
to-bob: leveled in 0 rounds
to-bob: cost 0
to-carol: rejected at assignments.update[0]: unknown resource "carol"
```

For how candidates are run and ranked, see [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/evaluating-alternatives.md>).

## Related

- [`Candidate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate.md>) - the input this result answers
- [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>) - the container that lists one result per candidate
- [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/rejected-operation.md>) - the type of `error`
- [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/evaluating-alternatives.md#failed-and-cyclic-candidates>) - how rejected and cyclic candidates are reported
- [Error handling](<https://docs.dhtmlx.com/planning-engine/rust/error-handling.md#results-that-are-not-errors>) - candidate errors among failures that don't fail the call
