Finish#

The finish dates of one evaluation stage: the project finish and the finish of each focus task, each with its distance from the baseline. You read it from Metrics::finish, which appears in EvaluateResult::baseline and in the scheduled and leveled stages of every CandidateResult.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub struct Finish {
    pub project: Option<Shift>,
    pub tasks: Vec<TaskFinish>,
}

With the serde feature, Finish implements Serialize and Deserialize with camelCase field names.

Fields#

Field Type Description
project Option<Shift> Project finish: the latest end over the scheduled tasks of this stage. None when the stage has no tasks.
tasks Vec<TaskFinish> One entry per id in EvaluateOptions::focus, in the same order. Empty when focus is empty.

Shift describes how delta is measured. TaskFinish covers a focus task that the candidate removed.

Example#

Report the project finish and the finish of task 2 for a candidate that moves task 2 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 options = EvaluateOptions {
        focus: vec![2.into()],
        ..Default::default()
    };
    let result = project.evaluate(&candidates, &options)?;

    for candidate in &result.candidates {
        let Some(scheduled) = &candidate.scheduled else { continue };
        let finish = &scheduled.finish;
        if let Some(shift) = &finish.project {
            println!("{}: project ends {} ({:+})", candidate.id, shift.date, shift.delta);
        }
        for task in &finish.tasks {
            match &task.finish {
                Some(shift) => println!("  task {} ends {} ({:+})", task.id, shift.date, shift.delta),
                None => println!("  task {} removed", task.id),
            }
        }
    }
    Ok(())
}