Getting started

This page is for a scheduler or project controls manager with a programme in front of them, not for a programmer. It assumes you have installed the tool by following docs/INSTALL.md and that construct --help prints something.

Everywhere below, construct is the command. If your installation does not put construct on the path, python -m forensic is the same program and takes the same arguments.

What the tool does, in one paragraph

You give it one or more schedule files. It reads them without opening P6, works out the dates and floats itself, and reports what the specification or standard that governs your contract says about what it found. It reports three kinds of answer and keeps them apart on purpose: what it decided, what it will not decide because a person has to, and what it could not evaluate because an input it needs was not supplied. It never says the submission is approved or rejected. That determination belongs to the reviewing authority, and the report says so at the top of every run.

What it reads

Primavera XER, Primavera XML and Microsoft Project MSPDI files. The format is detected from the contents of the file, not from the extension, so a P6 XML export renamed to .mspdi is still read correctly.

It does not read a binary .mpp. Export from Microsoft Project as XML instead.

It never writes a schedule file. Nothing you give it is modified.

If you would rather not use a terminal at all

construct serve

That prints an address — http://127.0.0.1:8765/ — and opens it. Everything below can be done from that page instead: point it at a schedule file already on your computer, read the report, read the list of clauses it could not decide grouped by the one thing you would have to go and find, supply that and run it again, and save the result where you choose. It binds 127.0.0.1, which no other machine on any network can reach; nothing is uploaded and no page fetches anything from the internet. Press Ctrl+C in the terminal window to stop it.

It is a shell over the commands below and answers nothing they do not. engine/quality/THE-INTERFACE.md says what it deliberately does not show.

The first thing to run

construct my-schedule.xer --jurisdiction us_federal

construct my-schedule.xer on its own is the same as construct check my-schedule.xer, so the word check is optional.

--jurisdiction says what governs. The choices are us_federal for USACE, NAVFAC and agencies that adopt UFGS; us_state_or_private for state DOT, municipal and private work; uk_scl for UK and Commonwealth practice under the SCL Protocol; and neutral, which is not "no standard" but "nobody has told me yet" — under neutral the report states in writing that its authority was assumed. If your contract names a governing document by title, name it with --contract instead, repeated in the contract's own order, because incorporation by name is a contract term and beats any default.

You get a Markdown report on screen. To produce something to attach to a submittal response, write it to a file:

construct my-schedule.xer --jurisdiction us_federal --out response.html

A filename ending .html produces a page you can open in a browser and print to PDF. Anything else is Markdown.

What comes back, and in what order

The report opens with a reservation of determination, then the method, the governing documents, any conflicts between them, and the assumptions and limitations of the run. That is roughly the first eighty lines and it is there so the report survives being read by the other side.

After that comes an inventory of the schedules considered, including what the importer could not take literally — a file with no terminator, a calendar with part days, a missing relationship table — and then the findings themselves, grouped by what kind of answer they are:

Every finding names the document and the clause, quotes what the clause requires, and states the condition found. Every finding is also tagged with what it rests on: from your file means it would still stand if this tool's date arithmetic were wrong, and from our arithmetic means it would not.

Exit codes are the same across every command: 0 means nothing blocking was found, 1 means there was a blocking finding or a withheld result, and 2 means the inputs could not be read or were refused.

Reading "not evaluated" and doing something about it

This is the section that turns a thin report into a useful one, and it is the part most first-time users stop at.

A clause like "the schedule shall complete no later than the contract completion date" cannot be checked against the schedule alone, because the contract completion date is not in the schedule. So the tool abstains and names what it wanted:

UFGS-059 §3.3.21 — needs terms['contract_completion_date']

You answer it with a small JSON file of contract terms and a --terms argument:

construct my-schedule.xer --jurisdiction us_federal --terms terms.json

The file is a single JSON object whose keys are the names the report gave you. Dates are written as YYYY-MM-DD. Nothing else about the format is required.

The loop is: run without --terms, read the "not evaluated" list, write the keys it names into a file, run again. Each pass converts abstentions into decided findings. There are worked files to start from in examples/examples/terms.min.json is two keys, and examples/terms.us-federal.json is what a US federal review typically needs. examples/README.md gives the exact command line for each.

Two things to know before you write one. A key the tool does not recognise, and a date in a format other than YYYY-MM-DD, each produce a warning naming the key and suggesting the nearest real one — but the warning goes to the terminal and the run continues, and the clause still reports the term as not supplied. If a clause keeps asking for something you are sure you gave it, the answer is in those warnings. And where a term is a list of activities, the ids are the ones the report prints, which for a P6 export are the task_id values from the database and not the Activity IDs the P6 window shows you.

"I have a schedule and a rejection letter. What do I run?"

Four commands, in this order.

One. Check the schedule against the specification.

construct update-11.xer --jurisdiction us_federal --out findings.html

This is the list of clause-level defects, and it is the same list the reviewer's comments are drawn from. Read it beside the letter. Where the letter cites a clause, the report will have something to say about that clause or will tell you which contract term it needed to have an opinion.

Two. Validate the schedules the argument will rest on.

construct validate baseline.xer update-11.xer --baseline 0 --jurisdiction us_federal

Source validation asks whether the baseline and the updates are fit to be analysed at all — before any delay analysis is worth doing. --baseline 0 says which of the files you listed is the contract baseline, counting from zero, so 0 is the first file named. The tool never guesses this, because the earliest file is very often a superseded plan.

Three. If time is in dispute, run the analysis the contract or the standard calls for.

construct analyse baseline.xer update-11.xer --method 3.6 --baseline 0 --delays delays.json

--method takes an AACE RP 29R-03 section number, or a common name where the name means exactly one method. "TIA" means either MIP 3.6 or MIP 3.7 depending on a choice the name does not carry, so the tool refuses it and asks the question that separates them rather than picking one.

Methods 3.6 through 3.9 model delay events into or out of the network, so they need a --delays file describing those events: an identifier, a name, a duration in days, and the activity each one bears on. Methods 3.7 and 3.9 also need the date each event started. examples/delay-events.json is a worked file and examples/README.md has the command line.

Four. If the dispute is about whose delay came first, run the concurrency command.

construct concurrency update-11.xer --jurisdiction uk_scl --delays periods.json

This one reads a different kind of file: delay periods, each with the party who bears the risk, when it happened, and — separately — the window over which it actually drove the critical path, which is often not the same thing. The tool reports the answer under all four combinations of the two concurrency theories and the two causation doctrines, states which one the governing document makes binding, and where nothing governs it reports the spread rather than picking one, because the standards genuinely disagree here.

You supply the party. The tool computes the overlap. If you do not know whose delay it was, write undetermined — that is a real answer, and it keeps the period out of every pairing and lists it as an open question instead of filing it against somebody. examples/delay-periods.json shows both.

Looking forward instead of back: a time impact analysis

Where you are pricing an extension of time on a change that has not yet been absorbed, the prospective procedure is AACE RP 52R-06 and the command is tia. You supply a fragnet — the small network of added activities and the relationships tying them into the programme — as a JSON file, and the day the delay began.

construct tia update-11.xer --fragnet fragnet.json --delay-start 2026-09-14 \
    --unit work_days --reviewed 0

The tool walks the eight steps of the recommended practice and records, for each one, what it did, what it assumed, and whether the tool decided it or a person did. Three of the arguments have no default and will stop you if you leave them out. --unit is work days or calendar days, and the two answers differ by roughly forty per cent on a five-day calendar. --reviewed names which of the files you listed the owner actually reviewed, because Step 2 requires the base schedule to be the last owner-reviewed update and that is a fact about the project record. And the fragnet's own accepted flag is Step 1: it defaults to false, which refuses the analysis, because the owner's acceptance of a fragnet is a claim the analyst makes rather than a box this tool can tick.

examples/fragnet.json is a worked fragnet.

Recording what you were given

construct record baseline.xer update-11.xer update-12.xer --json > record.json

This produces a provenance record: a digest of the bytes as received, a separate digest of the parsed network so that a change of engine version cannot make the same schedule look like a different file, and a list of what is observably absent from the set — missing update numbers, unusual intervals, a baseline dated after the updates it supposedly precedes. Those are reported as observations about the documents in hand and never as a finding that anything was withheld.

Keep this beside the reports. It is what lets somebody else re-run your analysis on the same bytes a year later and get the same answer.

Which commands need a JSON file, and why

check, validate and record run on a schedule alone. They will run better with --terms, but they run.

analyse with methods 3.6 to 3.9, concurrency, and tia cannot run without one, and this is deliberate rather than an omission. Which delays happened, when they bit, whose they were, and what a fragnet contains are all facts about the project record. They are not in the schedule file, and a tool that inferred them would be answering the disputed question by itself. So they are read from a file you write, and the report states them as your input rather than as its finding.

That is also why the tool refuses a file it does not fully understand instead of ignoring the part it cannot read. --delays and --fragnet reject an unrecognised key and list the ones they accept, because a key nothing reads is a value nothing applied.

Every one of these files has a worked example in examples/, minimal and realistic, with the exact command line above each.

Where to go next

Source: docs/GETTING-STARTED.md. Source commit date: 2026-09-06.

See it in practice

Follow the evidence, from the schedule to the finding.

Explore the worked example