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


# Metrics

The measurements of one evaluated state: finish dates, resource overload, conflict and cycle counts, and a cost that combines them. [`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 it inside an [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>): as `EvaluateResult::baseline`, as [`CandidateResult::scheduled`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate-result.md>), and nested in [`LeveledMetrics::metrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/leveled-metrics.md>) for the leveled stage. It isn't built by hand.

## Declaration

```rust
#[derive(Clone, Debug, PartialEq)]
pub struct Metrics {
    pub finish: Finish,
    pub overload: Overload,
    pub conflicts: Amount,
    pub cycles: Amount,
    pub cost: f64,
}
```

With the `serde` feature (`cargo add plen-core --features serde`), `Metrics` also implements `Serialize` and `Deserialize`, with field names in camelCase.

## Fields

Every [`Amount`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/amount.md>) holds a `value` for this stage and a `delta` against the baseline.

| Field | Type | Description |
|---|---|---|
| `finish` | [`Finish`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/finish.md>) | Project finish and the finish of each focus task. |
| `overload` | [`Overload`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/overload.md>) | Resource overload: total, per resource, and per focus task. |
| `conflicts` | [`Amount`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/amount.md>) | Number of entries in the stage's [`ScheduleResult::conflicts`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/results/schedule-result.md#conflicts>) (see [`Conflict`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/results/conflict.md>)). |
| `cycles` | [`Amount`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/amount.md>) | Number of entries in the stage's [`ScheduleResult::cycles`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/results/schedule-result.md#cycles>) (see [`Cycle`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/results/cycle.md>)). |
| `cost` | `f64` | Score of the stage. Lower is better. Formula below. |

### cost

```
cost = max(0, finish.project.delta)
     + overload.total.value
     + 1000 × (conflicts.value + cycles.value)
```

- A finish earlier than the baseline adds nothing. When `finish.project` is `None`, the finish term is 0.
- The `1000 ×` penalty puts any state with conflicts or cycles behind every state without them.
- Finish delay and overload are on the same scale when the overload basis follows `config.duration_unit`: one day late weighs about as much as one resource-day over capacity.

The formula is fixed. To rank by other criteria, compare the raw fields instead of `cost`.

## Example

Score one candidate that assigns a second task to the same resource, then print the metrics of the baseline and of the candidate.

```rust
use plen_core::{
    AssignmentData, Candidate, DateTime, EvaluateOptions, Metrics, Project, ProjectData,
    ProjectOptions, ResourceData, SchedulingChanges, SolveOptions, TaskData,
};

fn print(label: &str, m: &Metrics) {
    let finish_delta = m.finish.project.as_ref().map(|s| s.delta);
    println!(
        "{label}: finish delta {:?}, overload {}, conflicts {}, cycles {}, cost {}",
        finish_delta, m.overload.total.value, m.conflicts.value, m.cycles.value, m.cost
    );
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;

    let task = |id: i32| TaskData {
        start_date: Some(monday),
        duration: Some(2.0),
        ..TaskData::new(id)
    };
    let assign = |task: i32| AssignmentData {
        id: None,
        task: task.into(),
        resource: "dev1".into(),
        value: 1.0,
        offset: None,
        duration: None,
    };

    let data = ProjectData {
        tasks: vec![task(1), task(2)],
        resources: vec![ResourceData { id: "dev1".into(), capacity: None, calendar_id: None }],
        assignments: vec![assign(1)],
        ..Default::default()
    };

    let mut project = Project::new(ProjectOptions::default());
    project.solve(Some(data), &SolveOptions::default())?;

    let mut changes = SchedulingChanges::default();
    changes.assignments.add.push(assign(2));
    let candidates = [Candidate { id: "dev1-on-2".into(), changes }];

    let result = project.evaluate(&candidates, &EvaluateOptions::default())?;
    print("baseline", &result.baseline);
    for c in &result.candidates {
        if let Some(scheduled) = &c.scheduled {
            print("candidate", scheduled);
        }
    }
    Ok(())
}
```

## Related

- [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>) - holds the baseline metrics and every candidate's
- [`CandidateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate-result.md>) - whose `cost` comes from these metrics
- [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-options.md>) - `focus` and `level` control what is measured
- [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/evaluating-alternatives.md#metrics-and-cost>) - reading metrics and cost to rank candidates
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/suggesting-assignments.md>) - the same metrics scoring proposal steps
