Analytics#

Critical path and slack computed over a schedule's dates. It's the Analytics property of ScheduleResult. That property is set only when the call's AnalyticsOptions set CriticalPath, Slack, or both. With both false, it's null.

Declaration#

namespace Plen;

public sealed class Analytics : IDeepCloneable<Analytics>, IEquatable<Analytics>
{
    public AnalyticsStatus Status { get; set; }
    public Critical? CriticalPath { get; set; }
    public List<TaskSlack>? Slack { get; set; }

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

Properties#

Property Type Description
Status AnalyticsStatus Whether the analytics could be computed.
CriticalPath Critical? Critical tasks and links. Set when AnalyticsOptions.CriticalPath is true and Status is Available; null otherwise.
Slack List<TaskSlack>? Total and free slack per task. Set when AnalyticsOptions.Slack is true and Status is Available; null otherwise.

With Status set to AnalyticsStatus.Invalid, both CriticalPath and Slack are null, whatever the options asked for. Check Status before reading the other properties.

Methods#

DeepClone#

public Analytics DeepClone()

Returns a copy that shares no list or nested object with this one.

Equals#

public bool Equals(Analytics? other)
public override bool Equals(object? obj)

Value equality: Status is equal, CriticalPath is equal or both null, and Slack is equal element by element or both null. GetHashCode is consistent with it.

ToString#

public override string ToString()

Returns a readable dump of all properties, for logging.

AnalyticsStatus#

Whether Analytics could be computed.

public enum AnalyticsStatus
{
    Available,
    Invalid,
}
Value Description
Available Analytics were computed. The requested properties are set.
Invalid The dependency graph has a cycle. CriticalPath and Slack are null.

Critical#

The critical tasks and links of a schedule.

public sealed class Critical : IDeepCloneable<Critical>, IEquatable<Critical>
{
    public List<Id> Tasks { get; set; } = [];
    public List<Id> Links { get; set; } = [];
}
Property Type Description
Tasks List<Id> Ids of the critical tasks. A summary is listed when one of its non-summary descendants is critical. Completed tasks are never listed.
Links List<Id> Ids of the links between critical tasks that leave no margin.

Critical has value equality (both lists compared element by element), DeepClone() and ToString(), like Analytics.

TaskSlack#

Total and free slack of one task. Entries exist for active leaf tasks and milestones; summaries have none.

public sealed class TaskSlack : IDeepCloneable<TaskSlack>, IEquatable<TaskSlack>
{
    public required Id Task { get; set; }
    public double Total { get; set; }
    public double Free { get; set; }
}
Property Type Description
Task Id Id of the task. Required.
Total double Total slack, in the configured SchedulingConfig.DurationUnit. 0 for a completed task.
Free double Free slack, in the same unit. 0 for a completed task.

TaskSlack has value equality (Total and Free compared with ==), DeepClone() and ToString(), like Analytics.

Example#

Requests the critical path and slack for three tasks and prints both:

using Plen;

var monday = Moment.FromYmdHm(2026, 1, 5, 8, 0)
    ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        new TaskData("design") { StartDate = monday, Duration = 2.0 },
        new TaskData("build") { StartDate = monday, Duration = 3.0 },
        new TaskData("docs") { StartDate = monday, Duration = 1.0 },
    ],
    Links =
    [
        new LinkData { Id = "l1", Source = "design", Target = "build" },
    ],
};

var options = new SolveOptions
{
    Analytics = new AnalyticsOptions { CriticalPath = true, Slack = true },
};

var analytics = new Project().Solve(data, options).Analytics
    ?? throw new InvalidOperationException("analytics not requested");

if (analytics.Status == AnalyticsStatus.Invalid)
{
    Console.WriteLine("the dependency graph has a cycle");
    return;
}

if (analytics.CriticalPath is { } critical)
    Console.WriteLine($"critical tasks: {string.Join(", ", critical.Tasks)}");

foreach (var s in analytics.Slack ?? [])
    Console.WriteLine($"{s.Task}: total {s.Total}, free {s.Free}");