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 uses validation: "strict" and throws an InputError for the first invalid field. validation: "repair" coerces the data into valid input, then schedules the result.
Steps#
-
Create the project with repair validation:
const = new;new Project(options)throws anInputErrorif an option is unknown or has an invalid value. -
Pass your data to
project.solve(data, options)as usual.project.analyze(data)repairs supplied data the same way. The one-shotschedulefunction always uses strict validation, so use aProjecthere. -
Read the entries of
result.noteswithkind: "repaired". Each one has apathand amessage. 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:
const = ;
const = new;
const = ;
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, with the field path in path:
- Duplicate ids in
tasks,links,resources,assignmentsandcalendars: the last record replaces the earlier one and takes its position. The note is attasks[i].id, whereiis the index of the duplicate in your input. - A task with no start and no end gets
start_dateset toconfig.project_start, or to the earliest date of any task ifproject_startisn't set. - A leaf task (not a milestone) with no
durationand only one date getsduration: 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") withoutconstraint_dateis cleared. - Invalid
pauseson a task: the whole list is cleared, with a note attasks[i].pauses. - An invalid assignment
offsetordurationis 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_startand no task that has a date, still throws anInputErrorattasks[i].start_date. The engine never falls back to today's date. - Every other invalid input, such as a cycle in the
parenthierarchy or a calendar with no working time, still throws anInputError. See Error handling. - If repair or validation fails,
solvethrows 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):
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 at tasks[2].constraint_date.