Skip to main content

Project scheduling and earned value control for Python: CPM, PERT with Monte Carlo schedule risk, minimum-cost crashing, EVM and earned schedule, validated against published reference values.

Project description

pmcontrols

CI PyPI License: MIT

Project scheduling and earned value control for Python.

Critical path and PERT scheduling, minimum-cost schedule compression, and earned value management with earned schedule. Results are computed from the standard formulations and checked against reference values in the test suite.

Motivation

Every project office computes CPI and SPI, and almost all of it happens in spreadsheets. R and commercial tools (Primavera, @RISK) cover schedule risk and earned value. In Python the picture is uneven: the scheduling basics exist, but the cost and forecasting side is thin, and nothing ties them together under one validated interface.

Method State of the Python ecosystem
Critical path and PERT available (pyCritical, networkx, assorted scripts)
Schedule crashing (time/cost LP) no packaged library; built ad hoc on PuLP or SciPy
Earned value, full indicator set only minimal or scattered implementations
Earned schedule (Lipke SPI(t)) no maintained package
All of the above, validated, under one API none

pmcontrols is an attempt to fill that gap, with a few specific goals:

  • compute every result from the defining formulation (the forward and backward pass, the linear program, Lipke's interpolation), not from spreadsheet shortcuts
  • return one consistent Result with provenance, so a number can be recomputed and audited months later
  • validate every released number against published reference values on each CI run
  • work headless, so a weekly cost and schedule review can run as a cron job
pip install pmcontrols

For Gantt charts, add the optional plotting extra:

pip install "pmcontrols[plot]"

Documentation: https://arikanatakan.github.io/pmcontrols/

Status

Version 0.2.0 is on PyPI. Implemented and tested:

  • cpm: forward and backward pass returning ES, EF, LS, LF, total slack, and the zero-float critical path, with clear errors on cycles and unknown predecessors
  • pert: three-point estimates (te = (a + 4m + b) / 6) along the critical path, plus a Monte Carlo simulation reporting the completion distribution (p50, p80, p95) and a per-activity criticality index
  • crash: minimum-cost schedule compression to a target date, solved as the classical time/cost trade-off linear program (scipy, HiGHS)
  • evm, plan, earned_schedule: cost and schedule variance, CPI and SPI, the estimate-at-completion family, TCPI and VAC, and Lipke's earned schedule (ES, SPI(t), IEAC(t)) evaluated against a frozen baseline
  • gantt: an optional Gantt chart of a cpm or crash schedule (matplotlib, via the [plot] extra), with the critical path highlighted and total float shown, kept separate from the statistics
  • Result: every analysis returns named statistics, a tidy table, structured alerts, r.ok, r.summary(), and a JSON-safe to_dict() with provenance (version, input hash, timestamp)
  • PMB: a Performance Measurement Baseline that validates a non-decreasing planned-value curve and round-trips through JSON
  • a reference-case validation suite (tests/validation_cases.json), with the General Foundry network (full schedule, critical path, and optimal crash costs) and hand-derived EVM and earned-schedule cases reproduced in CI across Python 3.10 to 3.12

Usage

Critical path, slack, and the full schedule:

import pmcontrols as pm

activities = [
    {"id": "A", "predecessors": [],         "duration": 2},
    {"id": "B", "predecessors": [],         "duration": 3},
    {"id": "C", "predecessors": ["A"],      "duration": 2},
    {"id": "D", "predecessors": ["B"],      "duration": 4},
    {"id": "E", "predecessors": ["C"],      "duration": 4},
    {"id": "F", "predecessors": ["C"],      "duration": 3},
    {"id": "G", "predecessors": ["D", "E"], "duration": 5},
    {"id": "H", "predecessors": ["F", "G"], "duration": 2},
]

r = pm.cpm(activities)
r.stats["project_duration"]      # 15.0
r.meta["critical_activities"]    # ['A', 'C', 'E', 'G', 'H']
r.table                          # ES/EF/LS/LF/slack per activity

The numbers above are the standard General Foundry network from Render, Stair & Hanna; the 15-period schedule and the A-C-E-G-H critical path are computed by the forward and backward pass, and reproduced exactly as a validation case.

Draw that schedule as a Gantt chart (needs pip install "pmcontrols[plot]"):

fig, ax = pm.gantt(r)        # critical path in red, total float in light grey
fig.savefig("schedule.png")

Schedule risk from three-point estimates, with the Monte Carlo completion distribution the analytic method cannot give:

r = pm.pert([
    {"id": "X", "predecessors": [],    "a": 1, "m": 2, "b": 9},
    {"id": "Y", "predecessors": ["X"], "a": 2, "m": 3, "b": 10},
])
r.stats["mc_p80"]                # 80th-percentile completion time
r.table["criticality_index"]     # per activity, from the simulation

The cheapest way to hit a deadline, as a linear program rather than by hand:

r = pm.crash(crash_activities, target=13)
r.stats["total_crash_cost"]      # globally optimal, not greedy
r.table["crash_amount"]          # how much to compress each activity

Freeze the planned value curve once, then control against it every period:

import sys

pm.plan(periods, pv).save("pmb.json")   # commit this to version control

r = pm.evm("pmb.json", ev=30_000, ac=35_000, at=4)
r.ok                 # False if CPI or SPI(t) is below threshold
r.summary()          # plain text verdict
r.stats["ieac_t"]    # earned-schedule duration forecast
sys.exit(0 if r.ok else 1)

Every analysis returns the same Result object: named statistics, a tidy table, a tuple of structured alerts, and provenance metadata (library version, input hash, timestamp).

If you are wiring this into an AI agent, read Project control is not a language task first.

Roadmap

Version Scope
0.3 published PMI/Lipke earned-schedule case-study validation; OC plotting; resource leveling and constrained scheduling; EVM from time-phased ledgers
0.4 critical chain buffers; probabilistic crashing; earned-schedule forecasting variants; JOSS paper

Out of scope: Gantt-chart project editors (use dedicated PM tools), process control, acceptance sampling (see lotsampling), general linear programming (use scipy or pyomo directly).

License

MIT. Written and maintained by Atakan Arikan, MSc Student at Tsinghua University and Politecnico di Milano.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pmcontrols-0.2.0.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pmcontrols-0.2.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file pmcontrols-0.2.0.tar.gz.

File metadata

  • Download URL: pmcontrols-0.2.0.tar.gz
  • Upload date:
  • Size: 21.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pmcontrols-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7605ea53fb90486bb8ef0ae8ef987da5ade053a0be4caf399234bb918066d948
MD5 5976a080ad562207bd677d5ae038c5e3
BLAKE2b-256 196a7125bd1fa4daf7b152a03e76bf82cd65cfadfcf2ceb3144c52dc20073f29

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmcontrols-0.2.0.tar.gz:

Publisher: release.yml on arikanatakan/pmcontrols

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pmcontrols-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pmcontrols-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pmcontrols-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98c722916b56a5e920735eb867a26ed58c3396a68e6bcdd9d9c68b1362a5df29
MD5 07ead5a08a5b2cc677102c4ba01d2a88
BLAKE2b-256 6446160b3a0f9c5e13fe615481edbfdcb7a0faeb22219dac6540295bc3833b52

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmcontrols-0.2.0-py3-none-any.whl:

Publisher: release.yml on arikanatakan/pmcontrols

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page