[Canonical page](<https://docs.dhtmlx.com/planning-engine/net/how-to/accept-messy-input/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Accept messy input

Schedule project data that strict validation rejects, such as data exported from a UI with duplicate ids or missing dates, and get a list of every fix the engine made.

By default a [`Project`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md>) uses [`Validation.Strict`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/validation.md>) and throws [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) for the first invalid field. `Validation.Repair` coerces the data instead (deduplicates ids, derives missing dates and durations, fixes inconsistent date triples), then schedules the result.

## Steps

1. Create the project with repair validation:

   ```csharp
   var project = new Project(new ProjectOptions { Validation = Validation.Repair });
   ```

2. Pass your data to [`Solve(data, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) as usual. [`Project.Analyze`](<https://docs.dhtmlx.com/planning-engine/net/api/project-analyze.md>) with non-null `data` repairs supplied data the same way. The one-shot [`Engine.Schedule`](<https://docs.dhtmlx.com/planning-engine/net/api/engine-schedule.md>) always uses strict validation, so use a [`Project`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md>) here.

3. Read the [`Note.Repaired`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md#repaired>) entries in `result.Notes` and show them to the user, or log them. Each one has a `Path` and a `Message`.

The full example below has four kinds of problems: a duplicate id, a task with no dates, a dated constraint with no date, and a task whose start, end and duration disagree. Strict validation rejects this data. Repair schedules it:

```csharp
using Plen;

var jan1 = Moment.FromYmdHm(2026, 1, 1, 0, 0) ?? throw new InvalidOperationException("invalid date");
var jan4 = Moment.FromYmdHm(2026, 1, 4, 0, 0) ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        // Start and end without a duration: valid, nothing to repair.
        new TaskData("design") { StartDate = jan1, EndDate = jan4 },
        // "build" appears twice. The later record wins.
        new TaskData("build") { StartDate = jan1, Duration = 1.0 },
        // No dates, no duration, and a dated constraint without ConstraintDate.
        new TaskData("review") { ConstraintType = ConstraintType.Snet },
        new TaskData("build") { StartDate = jan1, Duration = 2.0 },
        // Three days between start and end, but Duration says five.
        new TaskData("deploy") { StartDate = jan1, EndDate = jan4, Duration = 5.0 },
    ],
};
// 24/7 time keeps the example dates easy to check.
data.Config.UseCalendars = false;

var project = new Project(new ProjectOptions { Validation = Validation.Repair });
var result = project.Solve(data, new SolveOptions { IncludeUnchanged = true });

foreach (var note in result.Notes)
{
    if (note is Note.Repaired repaired)
    {
        Console.WriteLine($"{repaired.Path}: {repaired.Message}");
    }
}
foreach (var m in result.Updates)
{
    Console.WriteLine($"{m.Id} {m.StartDate}..{m.EndDate} {m.Duration}");
}
```

## What repair changes

Repair reports each change below as one [`Note.Repaired`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md#repaired>), with the wire-spelled field path in `Path`:

- Duplicate ids in `Tasks`, `Links`, `Resources`, `Assignments` and `Calendars`: the last record replaces the earlier one and takes its position. The note is at `tasks[i].id`, where `i` is the index of the duplicate in your input.
- A task with no start and no end gets `StartDate` set to [`Config.ProjectStart`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#projectstart>), or to the earliest date of any task if `ProjectStart` isn't set.
- A leaf task (not a milestone) with no `Duration` and only one date gets `Duration = 1`.
- Start, end and duration that disagree: the dates win, and the duration becomes the working time between them. If the end is in non-working time, or the dates were computed on a different calendar, the duration is dropped and the task is resolved from its dates. An end that falls inside a pause moves to the pause start, with a second note at `tasks[i].end_date`.
- A summary with only one date gets the other date computed from that date and its duration.
- A dated constraint (`Snet`, `Snlt`, `Fnet`, `Fnlt`, `Mso`, `Mfo`) without `ConstraintDate` is cleared.
- Invalid `Pauses` on a task: the whole list is cleared, with a note at `tasks[i].pauses`.
- An invalid assignment `Offset` or `Duration` is cleared, so the assignment books the whole task.

A task that already has two of start, end and duration is valid input. Repair doesn't touch it and doesn't report it.

Only the duplicate-id notes use input indexes. Every other note indexes the deduplicated list. In the example, the two `build` records become one, so `deploy` is `tasks[4]` in your input but `tasks[3]` in its note.

## What repair doesn't fix

- An entirely undated project, with no [`Config.ProjectStart`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#projectstart>) and no task that has a date, still throws [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) with `Path` `tasks[i].start_date`. The engine never falls back to today's date.
- Every other invalid input, such as a cycle in the `Parent` hierarchy or a calendar with no working time, still throws `InputException`. See [Error handling](<https://docs.dhtmlx.com/planning-engine/net/error-handling.md>).
- If repair or validation fails, [`Solve`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) throws and the project keeps its previous state and cached result.

## Result

The example prints the fixes, then the scheduled dates (the date lines may come in a different order):

```text
tasks[3].id: duplicate id replaced the earlier record, retaining its position
tasks[2].constraint_type: dated constraint without date cleared
tasks[2].start_date: missing date defaulted to project anchor
tasks[2].duration: missing duration defaulted to one
tasks[3].duration: duration recomputed from supplied start and end
design 2026-01-01 00:00..2026-01-04 00:00 3
build 2026-01-01 00:00..2026-01-03 00:00 2
review 2026-01-01 00:00..2026-01-02 00:00 1
deploy 2026-01-01 00:00..2026-01-04 00:00 3
```

`build` uses the second record's duration of 2, and `deploy` has a duration of 3, taken from its dates. With `new Project()`, the same [`Solve`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) call throws [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) at `tasks[2].constraint_date`.
