What running it involves
There is nothing to download from this site. Nothing is distributed here, under any terms, and this page is not an install route.
It is here because an evaluator has a fair question that a feature list cannot answer — what would it actually take to run this in my environment, and what would it produce? — and the honest way to answer it is to show the real commands and the real output rather than describe them. If it looks like it would fit, a call is how you get to run it.
Everything below is a real command. Every one of them was run on Windows before it was written down.
1. What it would need from your environment
The short answer is the interesting one: 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 lock file to reconcile, no service to stand up, and no network access needed at all once it is in place. For a security review that removes an entire class of supply-chain question before it is asked.
That is true of the engine and not of its validation suite. Re-running the evidence needs the network: the corpus, the PSPLIB instances, the structural fixtures and the comparison engines are all fetched by pinned, hash-verified scripts and never committed, because most of their sources state no licence — which is a finding and not a permission.
On a clean checkout with nothing fetched, 118 of the suite's collected items skip and the gate still passes.
python --version
3.10 or earlier will not do. On Windows the launcher is py -3.11; on macOS the
system Python is usually too old.
2. What the install looks like
cd engine
pip install -e .
construct --help
That puts a construct command on the PATH. python -m forensic is the same
program and takes the same arguments; neither wraps the other, and the second
needs no PATH entry at all — PYTHONPATH=engine/src and python -m forensic is
a working configuration that installs nothing.
Two things worth knowing before you plan a rollout, because they are the ones that cost an afternoon:
- There is a long-established
constructlibrary on PyPI for binary parsing. It ships no console script, so the usual collision is with something local — but if aconstructon a machine does not print the usage above, it is not this one, andpython -m forensicdisambiguates. - A system Python that refuses installs (
error: externally-managed-environment) wants a virtual environment first. That is ordinary and not specific to this.
3. What it would run on
Your own file. XER, Primavera XML, Microsoft Project MSPDI and USACE SDEF are all
read, and the format is detected from the contents rather than the extension, so a
P6 XML export renamed to .mspdi is still read correctly. A file matching none of
the four is refused as unreadable rather than guessed at.
Two files travel with the tool, and both are shown in the worked example:
p6-23.12-xval.oracle.xer — real Primavera P6 23.12
output, transcribed from an MIT-licensed source. It holds thirteen projects,
XV01 to XV13, of 27 activities between them, so every command below names one
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. Given
thirteen, check 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, one row each, and needs
no --project.
Neither file is the evidence base, and it matters that you know that before you judge the output. These two are here so you can run something in a minute. The agreement figure quoted anywhere on this site is measured over a corpus of 82 projects and 86,031 activities of real Primavera exports, which is not distributed with the tool and is not what you are running below. A three-activity chain tells you the command works. It tells you nothing about the population the measurement came from — that is the worked example and does this actually work?.
synthetic-800.xer — 800 activities, three calendars, a two-level WBS,
progress on the first third. It is generated, and it says so in its own
project name. It is there to show what the output looks like at a size you
recognise; the counts in its findings are not evidence about real schedules.
4. The first command
construct p6-23.12-xval.oracle.xer --project XV01 --jurisdiction us_federal
--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; the same bytes under us_state_or_private produce a different
report.
| Value | For |
|---|---|
us_federal |
USACE, NAVFAC, and agencies that adopt UFGS |
us_state_or_private |
State DOT, municipal and private work |
uk_scl |
UK and Commonwealth practice under the SCL Protocol |
neutral |
Not "no standard" but "nobody has told me yet" — 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.
Two things about what comes back will look wrong and are not
The exit code is 1. A report with findings exits 1 by design. 0 means
nothing blocking was found, 1 means something is wrong or unestablished, 2
means nothing was analysed because an input was unreadable or refused.
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.
5. Turning the abstentions into a worklist
The not-evaluated list is the longest section of the report — on XV01 under
us_federal it is 79 of the 120 clauses that apply — and 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.
construct unanswered engine/oracle/cases/p6-23.12-xval.oracle.xer \
--project XV01 --jurisdiction us_federal
This 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 that run the first line reads that supplying the delay events alone would make twelve clauses decidable. Decidable, not passing — a clause that can be decided can also fail.
construct clause CLAUSE answers the same question for a single clause without
running anything.
6. Supplying contract terms
Put the keys the report named into a JSON file — a single object, dates as
YYYY-MM-DD — and run again:
construct engine/oracle/cases/p6-23.12-xval.oracle.xer --project XV01 \
--jurisdiction us_federal --terms terms.json
The coverage figure rises and the not-evaluated list gets shorter; the clauses that moved are named in both lists so you can check that they did. A minimal eleven-key file moves ten clauses out of not-evaluated on the standard sample run, taking the abstentions from 79 to 69 and the decided count from 28 to 37.
Worked files are in examples/ — examples/terms.min.json is two keys and
examples/terms.us-federal.json is what a US federal review typically needs.
Two things to know before writing one, because they each cost an afternoon:
- A key the tool does not recognise produces a warning on standard error and the run continues, with the clause still reporting the term as not supplied. A wrong key does not stop anything.
- Where a term is a list of activities, the ids are the ones the report
prints — for a P6 export those are the
task_idvalues from the database and not the Activity IDs the P6 window shows you.
The authoritative list of keys is your own no-terms run. The tool accepts 144 keys across the thirteen packs; no page names all of them, and the not-evaluated section names every key each clause wanted.
7. "I have a schedule and a rejection letter"
Four commands, in this order.
construct update-11.xer --jurisdiction us_federal --out findings.html
construct validate baseline.xer update-11.xer --baseline 0 --jurisdiction us_federal
construct analyse baseline.xer update-11.xer --method 3.6 --baseline 0 --delays delays.json
construct concurrency update-11.xer --jurisdiction uk_scl --delays periods.json
--baseline 0 says which of the files you listed is the contract baseline,
counting from zero. It is never guessed, because the earliest file is very
often a superseded plan.
--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.
examples/delay-events.json, examples/delay-periods.json and
examples/fragnet.json are worked files, and examples/README.md has the exact
command line above each.
8. If you would rather not use a terminal
construct serve
That prints http://127.0.0.1:8765/ and opens it. Everything above can be done
from that page instead: point it at a file already on your computer, read the
report, read the abstentions grouped by the one thing you would have to go and
find, supply that, 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. Ctrl+C stops it.
It is a shell over the commands above and answers nothing they do not.
Then read
- A worked example — the whole of the above, run, with its real output.
- Reading a report — section by section.
- What this cannot do — before you rely on a number.
And if it looks like it would fit: talk to us. That is the only route to running it, and it is deliberate rather than a gate — see what it costs, and who is liable.
Source: web/pages/start.md. Source commit date: 2026-09-06.