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


# Clear leveling

Remove the delays and splits that [`Project::level`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>) wrote into a project, so every task goes back to its unleveled dates.

Leveling stores its result in two task fields: `leveling_delay` and `pauses`. Clearing leveling means setting `leveling_delay` to 0 and `pauses` to an empty list on every task that carries either, then rescheduling. For how leveling sets these fields, see [Leveling resources](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/leveling-resources.md>).

## Prerequisites

- A solved [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>) that carries leveling, either from a `level` call or from loaded data with a non-zero `leveling_delay` or non-empty `pauses`.
- The ids of the tasks that carry a delay or pauses. Take them from your own model, or from the `updates` of the [`LevelingResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/resources/leveling-result.md>).

## Steps

1. Build one [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/task-update.md>) per leveled task. Set `leveling_delay` to [`Patch::Value(0.0)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/patch.md>) and `pauses` to `Patch::Value(Vec::new())`, and put all the updates into one [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/scheduling-changes.md>):

   ```rust
   use plen_core::{Id, Patch, SchedulingChanges, TaskUpdate};

   fn clear_leveling(ids: impl IntoIterator<Item = Id>) -> SchedulingChanges {
       let mut changes = SchedulingChanges::default();
       for id in ids {
           let mut update = TaskUpdate::new(id);
           update.leveling_delay = Patch::Value(0.0);
           update.pauses = Patch::Value(Vec::new());
           changes.tasks.update.push(update);
       }
       changes
   }
   ```

   Leave the dates out of the update. With no date in the same update, the `leveling_delay` change moves the stored start back by the delay, and the `pauses` change keeps the start and duration and recomputes the end.

2. Apply the changes with [`Project::apply`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#apply>) and check the [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/apply-report.md>):

   ```rust
   let report = project.apply(&clear_leveling(ids))?;
   if !report.rejected.is_empty() {
       // Resync with project.solve(Some(data), ...) before the next edit.
   }
   ```

3. Reschedule with [`solve(None, ...)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#solve>) and write `result.updates` back to your model:

   ```rust
   let result = project.solve(None, &SolveOptions::default())?;
   ```

   The cleared dates become the accepted baseline. The resource contention that leveling resolved shows up again in `result.overallocations`.

The full example below levels three 2-day tasks that share `dev1`, then clears the leveling. It uses the default calendar (Monday to Friday, 08:00-12:00 and 13:00-17:00) and starts on Monday, August 10, 2026:

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

fn aug(day: u32) -> Result<DateTime, Box<dyn std::error::Error>> {
    Ok(DateTime::from_ymd_hm(2026, 8, day, 8, 0).ok_or("invalid date")?)
}

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) -> AssignmentData {
    AssignmentData {
        id: None,
        task: task.into(),
        resource: "dev1".into(),
        value: 1.0,
        offset: None,
        duration: None,
    }
}

fn clear_leveling(ids: impl IntoIterator<Item = Id>) -> SchedulingChanges {
    let mut changes = SchedulingChanges::default();
    for id in ids {
        let mut update = TaskUpdate::new(id);
        update.leveling_delay = Patch::Value(0.0);
        update.pauses = Patch::Value(Vec::new());
        changes.tasks.update.push(update);
    }
    changes
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = ProjectData {
        tasks: vec![
            task("A", aug(10)?, 2.0),
            task("B", aug(10)?, 2.0),
            task("C", aug(10)?, 2.0),
        ],
        resources: vec![ResourceData {
            id: "dev1".into(),
            capacity: None,
            calendar_id: None,
        }],
        assignments: vec![booking("A"), booking("B"), booking("C")],
        ..Default::default()
    };

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

    // B is delayed to Wednesday, C to Friday.
    let leveled = project.level(&LevelingOptions::default())?;

    // The tasks that leveling delayed or split.
    let ids: Vec<Id> = leveled
        .schedule
        .updates
        .iter()
        .filter(|u| {
            u.leveling_delay.is_some_and(|d| d != 0.0)
                || u.pauses.as_ref().is_some_and(|p| !p.is_empty())
        })
        .map(|u| u.id.clone())
        .collect();

    let report = project.apply(&clear_leveling(ids))?;
    if !report.rejected.is_empty() {
        return Err("clearing was rejected, resync with solve(Some(data))".into());
    }

    let result = project.solve(None, &SolveOptions::default())?;
    for update in &result.updates {
        if let (Some(start), Some(end)) = (update.start_date, update.end_date) {
            println!("{}: {start} .. {end}", update.id);
        }
    }
    for over in result.overallocations.iter().flatten() {
        for window in &over.windows {
            println!(
                "{}: {} .. {}, peak {}",
                over.resource, window.from, window.to, window.peak
            );
        }
    }
    Ok(())
}
```

## Re-level from scratch instead

To replace old leveling with a fresh run, skip the steps above and call `level` again. [`LevelingOptions.clear_first`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/resources/leveling-options.md#clear_first>) is `true` by default, so the run first sets `leveling_delay: 0` and `pauses: []` on every in-scope task that carries either, then levels the clean plan:

```rust
let result = project.level(&LevelingOptions::default())?;
```

The call diffs its `updates` against the state it started from. A task whose stale delay was cleared and not replaced still gets an update entry, so writing `updates` back to your model keeps it in sync.

With a `scope`, the run clears only in-scope tasks. Set `clear_first: false` to keep the existing delays and pauses and level on top of them.

## Things to know

- [`Patch::Null`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/patch.md>) also works: on `leveling_delay` it restores the default of 0, and on `pauses` it clears the list.
- Only leveling produces pauses. Every pause counts as leveling-owned, whether or not its `by` is set, so clearing removes all of them.
- To see what clearing would do before you commit it, pass the same changes to [`Project::preview`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>). The project stays untouched. See [Previewing changes](<https://docs.dhtmlx.com/planning-engine/rust/live-projects/previewing-changes.md>).
- In a live editor, set `changes.baseline_id` to the baseline your model shows. [`apply`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#apply>) then fails with [`ProjectError::StaleBaseline`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/project-error.md>) if the project has moved on. See [Error handling](<https://docs.dhtmlx.com/planning-engine/rust/error-handling.md>).
- If `apply` rejects an operation, `report.needs_resync` is `true`, and [`solve(None)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#solve>) fails with `ProjectError::NeedsResync` until you send the full data with `solve(Some(data), ...)`.

## Result

The example prints `B` and `C` back on Monday, and the overallocation window on `dev1` returns:

```text
B: 2026-08-10 08:00 .. 2026-08-12 08:00
C: 2026-08-10 08:00 .. 2026-08-12 08:00
dev1: 2026-08-10 08:00 .. 2026-08-11 17:00, peak 3
```
