Installing it, and the first thing to run
For somebody who has not run this before. It ends with a report on screen and knowing which document explains it. Every command on this page was run on Windows before it was written down. Where a command can fail, the failure is named rather than left to be discovered — the failures in section 2 are the ordinary ones for a Python install and are not all reproduced here.
The package is not published. pip install construct-engine and
pipx install construct-engine resolve to nothing — the name was checked and
is unregistered, which RELEASING.md (RELEASING.md) §9 records with the date
and the HTTP status. Installing means installing from a clone, and the rest of
this page assumes you have one.
1. What you need
Python 3.11 or later, and nothing else. The engine has no runtime dependency of any kind and that is enforced by an import contract rather than by a convention, so there is no wheel to compile, no virtual-environment lock file to reconcile, and no network access needed after the clone.
That last clause is true of the engine and not of the validation suite, and
the distinction matters enough to state here rather than to let you find it.
Running the tool needs nothing off the network. Re-running the evidence does:
the corpus, the PSPLIB instances, the structural fixtures, MPXJ and the two
other comparison engines are all gitignored and fetched by pinned,
hash-verified scripts, because most of their sources state no licence — which
is a finding and not a permission. On a clean clone with nothing fetched, 118
of the suite's collected items skip, and python check.py still prints
all 6 checks passed. engine/quality/THE-REPOSITORY.md section four is the
table of what runs, what skips and what each skip costs you.
Check what you have:
python --version
If that says 3.10 or earlier, or python is not found, install a current
Python before going further. On Windows the launcher is py -3.11 and on macOS
the system Python is usually too old.
2. Install
From the top of the clone:
cd engine
pip install -e .
That puts a construct command on your PATH. It is the same entry point as
python -m forensic, which is the form most documents here use; neither is a
wrapper around the other, and every command on this page works either way.
If you would rather install nothing, set PYTHONPATH to engine/src and use
python -m forensic.
When it does not work
construct: command not foundafter a successful install. The scripts directory is not on your PATH.python -m forensicalways works and needs no PATH entry; use that and move on.constructruns but does something else entirely. There is a long-establishedconstructlibrary on PyPI for binary parsing. It ships no console script, so the usual collision is with something local rather than with it — but if aconstructon your PATH does not print the usage below, it is not this one.python -m forensicdisambiguates.error: externally-managed-environment. Your system Python refuses installs. Make a virtual environment first:python -m venv .venv, then activate it and repeat the install.- Installed, but
import forensicfails. You are in a different interpreter from the one that ranpip.python -m pip install -e .ties the two together.
Confirm it is there:
construct --help
3. A schedule file to run it on
You need a schedule before any of this means anything, and the constraint on which files this repository may publish is real: most of the corpus comes from repositories that state no licence, and an absent grant is not a permission. Two files are in the clone and both may be shown to anyone.
engine/oracle/cases/p6-23.12-xval.oracle.xer — real Primavera P6 23.12
output, captured from P6's own database, transcribed from an MIT-licensed
source. It is the file the whole of demo/README.md (../demo/README.md) runs
on. It is a cross-validation export holding thirteen projects, XV01 to
XV13, of 27 activities between them — so every command below names one of
them with --project. XV01 is a three-activity chain: it will tell you the
tool runs and nothing about how it behaves on a programme.
A file holding several projects has to be told which one you mean. check
reviews one schedule, so given thirteen it lists them with their activity counts
and asks, rather than reviewing the first and mentioning the other twelve in a
note. index is the command for the other reading: it reviews every project
in the file, one row each, and needs no --project — so construct index
engine/oracle/cases/p6-23.12-xval.oracle.xer works as it stands. Naming a
project narrows index to that one too, if that is what you want.
demo/outputs/synthetic-800.xer — 800 activities, three calendars, a
two-level WBS, progress on the first third. It is generated, by the same
function the performance measurements use, and it says so in its own project
name: P6 would show the project as SYNTHETIC. Use it to see what the output
looks like at a size you recognise. Do not read the counts in its findings as
anything about real schedules — demo/README.md §5 sets out precisely what a
generated file can and cannot demonstrate.
Your own file works too. XER, Primavera XML and Microsoft Project MSPDI are all read, and the format is detected from the contents rather than the extension.
4. The first command
Section 2 left you in engine/; these paths are from the top of the clone, so
go back up first.
cd ..
construct engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01 --jurisdiction us_federal
A conformance report on standard output. --jurisdiction is the argument that
matters most and the one with no safe default: it decides which standards
govern, and therefore which clauses are even looked for. Run the same file
under us_state_or_private and you get a different report from identical bytes.
Two things about what comes back will look wrong and are not:
- The exit code is 1.
0means nothing blocking was found,1means something is wrong or unestablished,2means nothing was analysed because an input was unreadable or refused. A report with findings exits 1 by design. A schedule that could not be scheduled — circular logic, so there is no critical path and no float — also exits 1, on bothcheckandindex, and says so on standard error. The file still produces a report, because the clauses that read the file can still be decided; what it cannot produce is a clean exit, and under a governance electing neither UFGS section there is no clause about circular logic to carry the failure instead. - Most clauses say
NOT_EVALUATED. The tool has been given a schedule and no contract, so it reports the clauses it could not decide and names the input each one wanted. That is the correct output, not a failure to finish.
Add --out report.html for a document to attach to a submittal response and
print to PDF from a browser. The Markdown and the HTML are rendered from one
object, so they cannot disagree.
4a. Turning that list into a worklist
The NOT_EVALUATED list is the longest section of that report — on XV01
under us_federal it is 79 of the 120 clauses that apply — and it is printed
one clause per line, in citation order. Read straight through, it gives the
impression that the tool mostly cannot answer. What it is really telling you is
much shorter, because those 79 lines name far fewer distinct inputs between them
and several of the inputs are wanted by a dozen clauses each.
unanswered is the command that does that arithmetic. It takes the same
arguments as check:
construct unanswered engine/oracle/cases/p6-23.12-xval.oracle.xer \
--project XV01 --jurisdiction us_federal
It groups the abstentions by the input a person would have to supply, largest group first, so the top of the list is the most useful thing you can go and find. On the run above the first line reads that supplying the delay events alone would make twelve clauses decidable. It says decidable, not passing — a clause that can be decided can also fail — and it separates the inputs that settle a clause on their own from the ones that need a second input as well.
This is the page to work from when you assemble the terms file in section 5, rather than working from the 79 lines by hand.
clause CLAUSE answers the same question for a single clause without running
anything: what it asks, what it looks at, and what input would let it be
decided.
health FILE is the other command that needs no contract terms at all. It runs
the DCMA 14-point screening metrics — logic, leads, lags, relationship types,
hard constraints, float, durations, dates, resources, missed tasks, the
critical-path test, CPLI and BEI — and reports which of the fourteen could not
be run and why, rather than scoring them as passes. It is not a conformance
check and never exits 1: GAO-16-89G appendix VII states that DCMA's
thresholds "are not compliance triggers", so a red metric here is a prompt to
look, not a finding. Where a specification does mandate one of these
quantities, check runs it as a cited clause at that specification's own
threshold, which is usually not DCMA's.
construct health engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01
5. The second command, which is where it gets useful
The NOT_EVALUATED list is a worklist. Each entry names the contract term it
wanted. Put those in a JSON file and run again:
construct engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01 \
--jurisdiction us_federal --terms terms.json
The coverage figure on the score line rises and the NOT_EVALUATED list gets
shorter; the clauses that moved are the ones whose terms you supplied, and they
are named in both lists so you can check that they are.
~~The full key list~~ What each key means, the date format, and the difference between an absent key and an empty list are in CONFORMANCE.md §5, which also gives a minimal example to copy. An empty list means none were made; an absent key means nobody said, and the tool treats those differently on purpose. §5 covers three of the thirteen packs and 44 of the 144 keys the tool accepts, so the list is your own no-terms run — the Not evaluated section names every key that clause wanted. Corrected 6 September 2026.
If you have more than one dated snapshot, pass them oldest first and name which
one is the contract baseline with --baseline 0. It is never guessed —
READING-A-REPORT.md §9 measures what the second snapshot
actually buys.
5a. Proving the answer to somebody who does not trust the tool
Two commands, and they are a pair. record writes down what was received, what
was parsed and what was computed; verify re-runs the files against that record
and either reproduces the answer or names the first value that differs.
construct record engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01 \
--json --out record.json
construct verify record.json engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01
--json is not optional here. record prints a Markdown report by default,
which is for a person; the record verify reads is the JSON one. Handing the
report to verify is the ordinary first mistake and it says so rather than
failing on a parse error.
verify takes no --terms, --jurisdiction or --baseline, and that is
deliberate: it re-runs what the record says was run. An option supplied at
verification time would let it answer a different question from the recorded
one, which is the thing it exists to detect. --project must be given again,
because it changes every digest and is written into the record's method.
Exit 0 means the answer reproduced, 1 that it diverged, 2 that the record or a file could not be read. Exit 1 is not an accusation. The commonest cause is a different engine version, which the manifest names, and the second is reproducing against a different file, which the input digests name; both are stated before any date is compared.
6. What to read next, in order
- CONCEPTS.md — the ideas the output is built on.
- READING-A-REPORT.md — the report walked through section by section, on a real one.
- LIMITATIONS.md — before you rely on a number. Every report prints the agreement measurement itself; this is the full list, and it is longer.
demo/README.md (../demo/README.md) is the shortest route to seeing what the
tool does without installing anything: five sections of commands and their real
output, the last of them at 800 activities.
Source: docs/INSTALL.md. Source commit date: 2026-09-06.