ResourceOverallocation#

The periods in which one resource is booked above its capacity. It isn't built by hand: read it from ScheduleResult.Overallocations, which holds one entry per resource with at least one overallocated window. Results from Project.Level also say why leveling left each window in place.

Declaration#

namespace Plen;

public sealed class ResourceOverallocation
    : IDeepCloneable<ResourceOverallocation>, IEquatable<ResourceOverallocation>
{
    public required Id Resource { get; set; }
    public List<OverallocationWindow> Windows { get; set; } = [];

    public ResourceOverallocation DeepClone();
    public bool Equals(ResourceOverallocation? other);
}

Equality is by value: Resource and Windows element by element. ToString() lists the fields, for debugging.

Properties#

Property Type Description
Resource Id The overallocated resource.
Windows List<OverallocationWindow> Its overallocated windows. Never empty in a reported entry.

Methods#

DeepClone#

public ResourceOverallocation DeepClone();

Returns a copy that shares no list or window with the original. Results are read-only by contract; copy one before changing it.

Returns: a new ResourceOverallocation equal to this one.

OverallocationWindow#

A maximal run of consecutive buckets in which the resource's load exceeds its capacity. Bucket size is the result's basis: derived from SchedulingConfig.DurationUnit, or LevelingOptions.Basis for Level.

namespace Plen;

public sealed class OverallocationWindow
    : IDeepCloneable<OverallocationWindow>, IEquatable<OverallocationWindow>
{
    public Moment From { get; set; }
    public Moment To { get; set; }
    public double Capacity { get; set; }
    public double Peak { get; set; }
    public double Overload { get; set; }
    public List<Id> Tasks { get; set; } = [];
    public string? Reason { get; set; }

    public OverallocationWindow DeepClone();
    public bool Equals(OverallocationWindow? other);
}
Property Type Description
From Moment Window start: the earliest working moment of the contending tasks in the first bucket.
To Moment Window end: the latest working moment of the contending tasks in the last bucket. Both edges are clipped to the booked assignment slices, not to the whole task.
Capacity double The resource's capacity, in assignment Value units (1 = 100 %). See ResourceData.Capacity.
Peak double The highest bucket load in the window.
Overload double Σ (load − capacity) over the window's buckets, in capacity × buckets. Per resource, the windows' Overload adds up to the overload metric at the same basis.
Tasks List<Id> The contending tasks, in input order.
Reason string? Why leveling left the window. Set only in results from Level; null otherwise. One of pinned-constraint, fixed-task, completed, priority-1000, slack-exhausted, out-of-scope. When several apply, the first in this list wins.

DeepClone() returns a copy with its own Tasks list. Equality is by value, with Tasks compared element by element.

The reasons are explained in Leveling resources.

Example#

Two fixed tasks overlap on dev1 on Tuesday. Level can't move either, so the window stays and carries a reason:

using Plen;

static Moment Aug(int day) =>
    Moment.FromYmdHm(2026, 8, day, 8, 0) ?? throw new InvalidOperationException("invalid date");

static AssignmentData Booking(string task) =>
    new() { Task = task, Resource = "dev1", Value = 1.0 };

var data = new ProjectData
{
    Tasks =
    [
        new TaskData("A") { StartDate = Aug(10), Duration = 2.0, AutoScheduling = false },
        new TaskData("B") { StartDate = Aug(11), Duration = 2.0, AutoScheduling = false },
    ],
    Resources = [new ResourceData { Id = "dev1" }],
    Assignments = [Booking("A"), Booking("B")],
};

var project = new Project();
project.Solve(data, new SolveOptions());
var leveled = project.Level(new LevelingOptions());

foreach (var over in leveled.Schedule.Overallocations ?? [])
{
    foreach (var window in over.Windows)
    {
        // dev1: 2026-08-11 08:00 .. 2026-08-11 17:00, peak 2 of 1, overload 1, tasks [A, B], reason fixed-task
        Console.WriteLine(
            $"{over.Resource}: {window.From} .. {window.To}, peak {window.Peak} of {window.Capacity}, " +
            $"overload {window.Overload}, tasks [{string.Join(", ", window.Tasks)}], reason {window.Reason}");
    }
}

The same window from Solve has Reason set to null.