EvaluateResult#

The result of Project::evaluate and Project::evaluate_with. It holds the metrics of the reference state and one CandidateResult per submitted Candidate, all scored against the same baseline.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub struct EvaluateResult {
    pub baseline_id: String,
    pub baseline: Metrics,
    pub candidates: Vec<CandidateResult>,
}

With the serde feature, EvaluateResult implements Serialize and Deserialize with camelCase field names (baselineId).

Fields#

Field Type Description
baseline_id String Id of the accepted baseline the candidates were scored against. With EvaluateOptions::clear_first, it's still the accepted state's id, not the cleared copy's.
baseline Metrics Metrics of the reference state itself. Every delta in it is 0. The reference state is the accepted state, or its cleared copy when clear_first is set.
candidates Vec<CandidateResult> One entry per candidate, in input order.

Rank candidates by CandidateResult::cost against baseline.cost.

Example#

Score moving task 2 from dev1 to dev2.

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

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 resource = |id: &str| ResourceData {
        id: id.into(),
        capacity: None,
        calendar_id: None,
    };
    let assignment = |id: &str, task: i32| AssignmentData {
        id: Some(id.into()),
        task: task.into(),
        resource: "dev1".into(),
        value: 1.0,
        offset: None,
        duration: None,
    };
    let data = ProjectData {
        tasks: vec![task(1), task(2)],
        resources: vec![resource("dev1"), resource("dev2")],
        assignments: vec![assignment("a1", 1), assignment("a2", 2)],
        ..Default::default()
    };

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

    let mut changes = SchedulingChanges::default();
    let mut to_dev2 = AssignmentUpdate::new("a2");
    to_dev2.resource = Patch::Value("dev2".into());
    changes.assignments.update.push(to_dev2);
    let candidates = [Candidate { id: "dev2".into(), changes }];

    let result = project.evaluate(&candidates, &EvaluateOptions::default())?;
    println!("baseline {}: cost {}", result.baseline_id, result.baseline.cost); // cost 2
    for candidate in &result.candidates {
        match (&candidate.error, candidate.cost) {
            (Some(error), _) => println!("{}: rejected at {}", candidate.id, error.path),
            (None, Some(cost)) => println!("{}: cost {cost}", candidate.id), // dev2: cost 0
            (None, None) => {}
        }
    }
    Ok(())
}