Validation#
Selects how a Project treats invalid ProjectData: reject it with an exception, or coerce it into valid data. It's set through the Validation property of ProjectOptions and applies to the data passed to Project.Solve and Project.Analyze.
Declaration#
namespace Plen;
public enum Validation
{
Strict,
Repair,
}
Variants#
| Variant | Description |
|---|---|
Strict |
Default. Invalid input throws InputException with the path of the bad field. The rules are listed on ProjectData. |
Repair |
Fixes the input listed below before scheduling and reports each change as a Note.Repaired in the result's Notes. Input it can't fix still throws InputException. The fixes are made on the project's own copy, so the caller's ProjectData isn't modified. |
What Repair changes#
Each row is one fix. The note's Path uses wire spelling and points at the field that changed. For duplicate ids the index is the record's position in the input. For the other fixes it's the position after duplicates are removed.
| Input | Fix | Note Path |
|---|---|---|
Duplicate Id in Tasks, Links, Resources, Assignments or Calendars |
The last record wins and takes the position of the first one | tasks[i].id, links[i].id, ... |
Invalid Pauses on a task |
The task's pause list is cleared | tasks[i].pauses |
Dated ConstraintType without ConstraintDate |
ConstraintType is set to null |
tasks[i].constraint_type |
Task with neither StartDate nor EndDate |
StartDate is set to Config.ProjectStart, or to the earliest date of any task when ProjectStart is null |
tasks[i].start_date |
Task (not a milestone) with no Duration and only one of its dates |
Duration is set to 1 |
tasks[i].duration |
StartDate, EndDate and Duration that don't agree |
The dates win: Duration becomes the working time between them. When no duration from the start lands on the end (an end in non-working time, or dates computed on other calendars), Duration is set to null and the task is resolved from its dates |
tasks[i].duration |
EndDate inside a work gap of a task with inconsistent dates |
The end moves to the start of the gap | tasks[i].end_date |
| Summary with only one of its dates | The missing date is computed from the other date and the duration | tasks[i].start_date or tasks[i].end_date |
Assignment Offset that isn't finite or is negative |
Offset is set to null |
assignments[i].offset |
Assignment Duration that isn't finite or isn't positive |
Duration is set to null |
assignments[i].duration |
Unscheduled tasks and summaries without dates stay without dates. Repair never falls back to the current date. When an undated task needs a start and the project has neither Config.ProjectStart nor any dated task, the call throws InputException with Path tasks[i].start_date.
Example#
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 },
// Same id as the first task: this record replaces it.
new TaskData("design") { StartDate = monday, Duration = 3.0 },
// No dates: the start defaults to the earliest task date.
new TaskData("build") { Duration = 2.0 },
],
};
var project = new Project(new ProjectOptions { Validation = Validation.Repair });
var result = project.Solve(data, new SolveOptions());
foreach (var note in result.Notes)
{
if (note is Note.Repaired repaired)
Console.WriteLine($"{repaired.Path}: {repaired.Message}");
}
// tasks[1].id: duplicate id replaced the earlier record, retaining its position
// tasks[1].start_date: missing date defaulted to project anchor
Related#
ProjectOptions- where the validation mode is setNote- theRepairednotes that report each fixInputException- thrown for input that is rejected or can't be repairedProjectData- the validation rules for project data- Accept messy input - turning on repair mode and reading the notes