[Canonical page](<https://docs.dhtmlx.com/planning-engine/rust/how-to/preview-leveling/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Preview leveling

Show the user what leveling would change before it touches the project, then keep the leveled plan or throw it away.

[`Project::level`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>) takes `&mut self` and commits: it writes `leveling_delay` and `pauses` into the project and accepts the leveled dates as the new baseline. To get a proposal instead, level a clone. [`Project::clone`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#clone>) is O(1), because the data stays shared until one copy is edited.

## Prerequisites

- A solved [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>): [`solve(Some(data), ...)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#solve>) has run.
- The data has `resources` and `assignments`, so there's overallocation to level.
- The project is forward-scheduled. `level` refuses `config.schedule_from_end: true` with [`Error::Input`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/error.md>).

## Steps

1. Clone the solved project:

   ```rust
   let mut draft = project.clone();
   ```

2. Level the clone. The original keeps its dates, its baseline and its overallocation:

   ```rust
   let proposal = draft.level(&LevelingOptions::default())?;
   ```

3. Show the proposal. `proposal.schedule.updates` lists the tasks leveling moved, with their new dates, `leveling_delay` and `pauses`. The diff is relative to the state the call started from. That state matches the original, so you can apply the entries to the model you already hold.

   ```rust
   for update in &proposal.schedule.updates {
       println!(
           "{}: start {:?}, delay {:?}, pauses {:?}",
           update.id, update.start_date, update.leveling_delay, update.pauses
       );
   }
   ```

4. Show what leveling couldn't fix. `proposal.schedule.overallocations` holds the windows left after leveling, and each window has a `reason`, such as `fixed-task` or `priority-1000`:

   ```rust
   for over in proposal.schedule.overallocations.iter().flatten() {
       for window in &over.windows {
           println!("{}: {} .. {}, {:?}", over.resource, window.from, window.to, window.reason);
       }
   }
   ```

5. Apply the user's choice:
   - Accept: replace the original with the clone, `project = draft;`. The project now holds the leveled baseline.
   - Reject: drop `draft`. Nothing else needs undoing.

   Replace the original only if it hasn't been edited since you cloned it. Otherwise the proposal was computed from an older state, so level a new clone.

The full example levels two tasks that book the same developer full time on Monday and Tuesday:

```rust
use plen_core::{
    AssignmentData, DateTime, LevelingOptions, Project, ProjectData, ResourceData, SolveOptions,
    TaskData,
};

fn task(id: &str, start: DateTime, duration: f64) -> TaskData {
    let mut task = TaskData::new(id);
    task.start_date = Some(start);
    task.duration = Some(duration);
    task
}

fn booking(task: &str, resource: &str) -> AssignmentData {
    AssignmentData {
        id: None,
        task: task.into(),
        resource: resource.into(),
        value: 1.0,
        offset: None,
        duration: None,
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Monday, August 10, 2026, on the default calendar.
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;
    let data = ProjectData {
        tasks: vec![task("A", monday, 2.0), task("B", monday, 2.0)],
        resources: vec![ResourceData {
            id: "dev1".into(),
            capacity: None,
            calendar_id: None,
        }],
        assignments: vec![booking("A", "dev1"), booking("B", "dev1")],
        ..Default::default()
    };

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

    let mut draft = project.clone();
    let proposal = draft.level(&LevelingOptions::default())?;

    for update in &proposal.schedule.updates {
        if let (Some(start), Some(end)) = (update.start_date, update.end_date) {
            println!("{}: {start} .. {end}, delay {:?}", update.id, update.leveling_delay);
        }
    }
    let left = proposal.schedule.overallocations.iter().flatten().count();
    println!("overallocated resources left: {left}");

    let user_accepts = true;
    if user_accepts {
        project = draft;
    }
    let plan = project.solve(None, &SolveOptions::default())?;
    println!("baseline: {:?}", plan.baseline_id);
    Ok(())
}
```

## Result

The example prints the one task leveling moved, and nothing is left overallocated:

```text
B: 2026-08-12 08:00 .. 2026-08-14 08:00, delay Some(2.0)
overallocated resources left: 0
```

`B` waits two days for `A`, so its `leveling_delay` is `2.0`. Until you replace `project` with `draft`, `project` still reports the Monday-Tuesday window on `dev1`, and a [`solve(None)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#solve>) on it returns no updates. After the swap, `baseline_id` is the token from the leveling result.

The clone is a full solved project, so you can also call `draft.load_profile` on it to draw the leveled load. See [Resource load](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/resource-load.md>). To compare leveling across several alternative change sets with scores, use [`evaluate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#evaluate>) with [`EvaluateOptions.level`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-options.md#fields>) instead. See [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/evaluating-alternatives.md>). For options, reasons and splits, see [Leveling resources](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/leveling-resources.md>).
