FocusOverload#

Resource overload measured only where one focus task runs. Project::evaluate returns one FocusOverload per task in EvaluateOptions::focus, in that order, as Overload::focus. It isn't built by hand.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub struct FocusOverload {
    pub task: Id,
    pub resources: Option<Vec<ResourceOverload>>,
}

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

Fields#

Field Type Description
task Id The focus task, as given in EvaluateOptions::focus.
resources Option<Vec<ResourceOverload>> Overload per resource, counted only over the buckets that intersect the task's [start, end) in this stage. None when the task isn't in this stage, for example when the candidate removes it.

resources#

  • Lists every resource with non-zero overload inside the task's span, in this stage or in the baseline. The order matches Overload::resources.
  • The span is the task's span in this stage, so it moves with the task's dates. A zero-span milestone covers no bucket and has no overload.
  • In the scheduled stage, the values show the overload around the task. In the leveled stage, they show the overload leveling couldn't remove there.

Example#

Score a candidate that gives task 2 to a resource already busy on task 1, and print the overload inside task 2's span.

use plen_core::{
    AssignmentData, Candidate, DateTime, EvaluateOptions, 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 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 options = EvaluateOptions {
        focus: vec![2.into()],
        ..Default::default()
    };

    let result = project.evaluate(&candidates, &options)?;
    for c in &result.candidates {
        let Some(scheduled) = &c.scheduled else { continue };
        for focus in &scheduled.overload.focus {
            match &focus.resources {
                Some(rows) => {
                    for row in rows {
                        println!(
                            "task {}: {} over by {} ({:+})",
                            focus.task, row.resource, row.overload.value, row.overload.delta
                        );
                    }
                }
                None => println!("task {} isn't in this stage", focus.task),
            }
        }
    }
    Ok(())
}