[Canonical page](<https://docs.dhtmlx.com/planning-engine/js/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 another tool 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/js/api/project.md>) uses `validation: "strict"` and throws an [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#inputerror>) for the first invalid field. `validation: "repair"` coerces the data into valid input, then schedules the result.

## Steps

1. Create the project with repair validation:

   ```js
   import { Project } from "plen-js";

   const project = new Project({ validation: "repair" });
   ```

   [`new Project(options)`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md#projectoptions>) throws an [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#inputerror>) if an option is unknown or has an invalid value.

2. Pass your data to [`project.solve(data, options)`](<https://docs.dhtmlx.com/planning-engine/js/api/project-solve.md>) as usual. [`project.analyze(data)`](<https://docs.dhtmlx.com/planning-engine/js/api/project-analyze.md>) repairs supplied data the same way. The one-shot [`schedule`](<https://docs.dhtmlx.com/planning-engine/js/api/schedule.md>) function always uses strict validation, so use a `Project` here.

3. Read the entries of `result.notes` with `kind: "repaired"`. Each one has a `path` and a `message`. Show them to the user, or log them.

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:

```js
import { Project } from "plen-js";

const data = {
  tasks: [
    // Start and end without a duration: valid, nothing to repair.
    { id: "design", start_date: "2026-01-01", end_date: "2026-01-04" },
    // "build" appears twice. The later record wins.
    { id: "build", start_date: "2026-01-01", duration: 1 },
    // No dates, no duration, and a dated constraint without constraint_date.
    { id: "review", constraint_type: "snet" },
    { id: "build", start_date: "2026-01-01", duration: 2 },
    // Three days between start and end, but duration says five.
    { id: "deploy", start_date: "2026-01-01", end_date: "2026-01-04", duration: 5 },
  ],
  // 24/7 time keeps the example dates easy to check.
  config: { use_calendars: false },
};

const project = new Project({ validation: "repair" });
const result = project.solve(data, { includeUnchanged: true });

for (const note of result.notes) {
  if (note.kind === "repaired") {
    console.log(`${note.path}: ${note.message}`);
  }
}
for (const update of result.updates) {
  console.log(update.id, `${update.start_date}..${update.end_date}`, update.duration);
}
```

Repair works on a copy. Your `data` object stays as you passed it, duplicates included.

## What repair changes

Repair reports each change below as one `repaired` note in [`result.notes`](<https://docs.dhtmlx.com/planning-engine/js/api/types/schedule-result.md>), with the 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 `start_date` set to `config.project_start`, or to the earliest date of any task if `project_start` 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 `constraint_date` 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.project_start` and no task that has a date, still throws an [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#inputerror>) at `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 an `InputError`. See [Error handling](<https://docs.dhtmlx.com/planning-engine/js/error-handling.md>).
- If repair or validation fails, [`solve`](<https://docs.dhtmlx.com/planning-engine/js/api/project-solve.md>) throws and the project keeps its previous state.

## 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` call throws an [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#inputerror>) at `tasks[2].constraint_date`.
