massingplan

A construction scheduling engine that refuses to guess.

Multi-calendar CPM with all four relationship types and all ten Primavera constraint types; DCMA 14-point assessment; forensic delay analysis to AACE 29R-03; Primavera XER, Primavera P6 XML and MS Project MSPDI interchange. The engine imports nothing but the Python standard library, so it can be copied into another codebase and run there unchanged.

11,784lines in core/, pure stdlib
1,335tests
19CI jobs per push
100%branch coverage, calendar kernel

Why it exists

Most scheduling tools compute a plausible answer when they should refuse. A Primavera importer that drops the relationships still produces a schedule — one where every activity is critical with zero float, and nothing says so. A float figure clamped at zero hides the fact that a date cannot be met. A forecast that quietly disagrees with the activity table below it is read against a contract date anyway.

Negative float is an output, not an error. It is never clamped — not total float, not free float. Every coercion, default or dropped row emits an issue naming what was done and why.

What it does

AreaCapability
SchedulingMulti-calendar CPM on an absolute ordinal time axis; FS/SS/FF/SF with lags in a chosen lag calendar; ten constraint types; data date, actuals, retained logic and progress override
QualityDCMA 14-point with tri-state results — a check that could not run is skipped, not passed, and is excluded from the score's denominator
RiskSeeded Monte Carlo, byte-identical between runs; criticality index and duration sensitivity
ResourcesDemand held level per day, over-allocation per day, serial levelling within float or extending the finish
ForensicsBaseline comparison with delay attribution that sums exactly; contemporaneous windows analysis (MIP 3.3); impacted as-planned and collapsed as-built (MIP 3.6 and 3.9) with concurrency reported
ProductionLine of balance with crew continuity; takt planning; Last Planner with a PPC denominator that cannot shrink after the week
PlanningEarned Schedule; weather allowance modelled in the calendar; schedule compression as priced options; multi-project portfolios with links across the boundary
InterchangePrimavera XER, Primavera P6 XML (including baselines), MS Project MSPDI — all read and written

How it is built

Verified by breaking it

Passing tests prove that code does what its author expected. They do not prove the expectation was right. So the engine is also probed with generated inputs — random networks, random re-baselines, random progressed schedules — checked against the properties the modules claim rather than against remembered answers.

Those runs have found defects a fully green suite was passing over: float overstated behind a Start-to-Finish link, a Monte Carlo forecast a day late and landing on Saturdays, a Primavera constraint read as a one-sided floor so that pinned activities silently gained slack, a levelling pass that delayed an activity without delaying anything downstream of it, and an exporter that let an activity name forge a row in the file a planner opens.

Every fix is verified the same way: break the fix, watch a test go red, put it back. Where a probe found nothing, the existing tests were deliberately broken to confirm the coverage was real rather than absent — and more than once the probe turned out to be wrong before the engine was, which is recorded in the probe rather than quietly corrected.

Using it

As a library

from datetime import date
from massingplan.core.network import Link, RelationType, Task
from massingplan.core.schedule import schedule_network

tasks = [Task("A", "Substructure", 10, "5D"),
         Task("B", "Frame", 15, "5D")]
links = [Link("A", "B", RelationType.FS, 0)]

outcome = schedule_network(tasks, links, calendars, data_date=date(2026, 6, 1))
print(outcome.project_finish, outcome.longest_path)

As an API

Fifteen endpoints under /api/massingplan/v1/, every one taking and returning plain JSON: schedule, analyse, risk, linear, takt, level, compare, windows, earned-schedule, modelled-delay, compress, portfolio, weather, import and capabilities.

curl -X POST https://your-host/api/massingplan/v1/schedule \
  -H "Authorization: Bearer mpln_..." \
  -H "Content-Type: application/json" \
  -d '{"data_date": "2026-06-01",
       "activities": [{"id": "A", "duration_days": 10}]}'

Vendored into another codebase

massingplan/core/ imports nothing outside the standard library, enforced by an import-linter contract and a dedicated CI job. It is copied verbatim into a consumer with a VENDOR.md pinning the upstream commit, and travels with a stdlib-only conformance gate the consumer can run.

Honest about what it is not

Documentation