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 commits its result: it writes LevelingDelay and Pauses into the project and accepts the leveled dates as the new baseline. To get a proposal instead, level a clone. Project.Clone is cheap, because the copies share their data until one of them is edited.
Prerequisites#
- A solved
Project:Solve(data, …)has run. - The data has
ResourcesandAssignments, so there's overallocation to level. - The project is forward-scheduled.
LevelrefusesConfig.ScheduleFromEnd = truewithInputException.
Steps#
-
Clone the solved project:
var draft = project.Clone(); -
Level the clone. The original keeps its dates, its baseline and its overallocation:
var proposal = draft.Level(new LevelingOptions()); -
Show the proposal.
proposal.Schedule.Updateslists the tasks leveling moved, with their new dates,LevelingDelayandPauses. 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.foreach (var update in proposal.Schedule.Updates) { var pauses = update.Pauses is { } list ? string.Join(", ", list.Select(p => $"{p.At}+{p.Length}")) : "-"; Console.WriteLine($"{update.Id}: start {update.StartDate}, delay {update.LevelingDelay}, pauses {pauses}"); } -
Show what leveling couldn't fix.
proposal.Schedule.Overallocationsholds the windows left after leveling, and each window has aReason, such asfixed-taskorpriority-1000:foreach (var over in proposal.Schedule.Overallocations ?? []) { foreach (var window in over.Windows) { Console.WriteLine($"{over.Resource}: {window.From} .. {window.To}, {window.Reason}"); } } -
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.
- Accept: replace the original with the clone,
The full example levels two tasks that book the same developer full time on Monday and Tuesday:
using Plen;
// Monday, August 10, 2026, on the default calendar.
var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");
var data = new ProjectData
{
Tasks =
[
new TaskData("A") { StartDate = monday, Duration = 2.0 },
new TaskData("B") { StartDate = monday, Duration = 2.0 },
],
Resources = [new ResourceData { Id = "dev1" }],
Assignments =
[
new AssignmentData { Task = "A", Resource = "dev1", Value = 1.0 },
new AssignmentData { Task = "B", Resource = "dev1", Value = 1.0 },
],
};
var project = new Project();
project.Solve(data, new SolveOptions());
var draft = project.Clone();
var proposal = draft.Level(new LevelingOptions());
foreach (var update in proposal.Schedule.Updates)
{
Console.WriteLine($"{update.Id}: {update.StartDate} .. {update.EndDate}, delay {update.LevelingDelay}");
}
var left = proposal.Schedule.Overallocations?.Count ?? 0;
Console.WriteLine($"overallocated resources left: {left}");
var userAccepts = true;
if (userAccepts)
{
project = draft;
}
var plan = project.Solve(null, new SolveOptions());
Console.WriteLine($"baseline: {plan.BaselineId}");
Result#
The example prints the one task leveling moved, and nothing is left overallocated:
B: 2026-08-12 08:00 .. 2026-08-14 08:00, delay 2
overallocated resources left: 0
B waits two days for A, so its LevelingDelay is 2.0. Until you replace project with draft, project still reports the Monday-Tuesday window on dev1, and a Solve(null, …) on it returns no updates. After the swap, BaselineId is the token from the leveling result.
The clone is a full solved project, so you can also call draft.LoadProfile on it to draw the leveled load. See Resource load. To compare leveling across several alternative change sets with scores, use Evaluate with EvaluateOptions.Level instead. See Evaluating alternatives. For options, reasons and splits, see Leveling resources.