Project::level#

Resolves resource overallocation in a Project. It runs solve(None) with default options first, then writes persistent leveling_delay and pauses edits and accepts the leveled state as the baseline.

Signature#

pub fn level(&mut self, options: &LevelingOptions) -> Result<LevelingResult, Error>

Takes &mut self. Synchronous and CPU-only.

Parameters#

Name Type Description
options &LevelingOptions Scope, order, basis and how leveling may move tasks

Behavior#

  • Forward scheduling only.
  • On a cyclic project, it returns the initial solve's result with rounds: 0.
  • The leveled dates replace the baseline. To see leveling without committing it, call level on a clone.

Returns#

A LevelingResult.

Errors#

Error When
Error::Input An unknown id in options.scope.resources or options.scope.tasks (path scope.resources / scope.tasks); the project has config.schedule_from_end: true (path config.schedule_from_end)
Error::Project(ProjectError::NeedsResync) Returned by the initial solve

Example#

Level two tasks that share one resource, first on a copy, then for real.

use plen_core::{
    AssignmentData, DateTime, LevelingOptions, Project, ProjectData, ResourceData, 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: &str| TaskData {
        start_date: Some(monday),
        duration: Some(2.0),
        ..TaskData::new(id)
    };
    let assign = |task: &str| AssignmentData {
        id: None,
        task: task.into(),
        resource: "dev".into(),
        value: 1.0,
        offset: None,
        duration: None,
    };
    let data = ProjectData {
        tasks: vec![task("a"), task("b")],
        resources: vec![ResourceData { id: "dev".into(), capacity: None, calendar_id: None }],
        assignments: vec![assign("a"), assign("b")],
        ..Default::default()
    };

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

    // A proposal: level a cheap copy, the project keeps its dates.
    let mut draft = project.clone();
    let proposal = draft.level(&LevelingOptions::default())?;
    for update in &proposal.schedule.updates {
        println!("task {} delay {:?}", update.id, update.leveling_delay);
    }

    // Commit: level the project itself.
    let leveled = project.level(&LevelingOptions::default())?;
    println!("{} round(s)", leveled.rounds);
    Ok(())
}