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.1 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
  • visualization (optional, matplotlib via the [plot] extra), kept separate from the statistics: gantt (schedule), network_diagram (activity network with the critical path), evm_curve (earned value S-curve), criticality (Monte Carlo criticality bars), and mc_distribution (completion histogram)
  • 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")

The same extra gives pm.network_diagram(r), pm.evm_curve(pmb, evm_result), pm.criticality(pert_result), and pm.mc_distribution(pert_result).

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.1.tar.gz (23.1 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.1-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pmcontrols-0.2.1.tar.gz
  • Upload date:
  • Size: 23.1 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.1.tar.gz
Algorithm Hash digest
SHA256 835630376b9507a54216df47fcdb105c9aaa8cc3046d287275c61dcdbfa626f8
MD5 2ee26b43e9c6f331c503ce51244faf7e
BLAKE2b-256 8d73106a62fb00ba1d8ec340693adb670a0acfdc680477eab487178c747f419b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmcontrols-0.2.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: pmcontrols-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 19.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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3596f9f26dc992248264e217fa22dfb1086f95553e1bc565cde45a79f909e96d
MD5 c7d9841e08a18d4a886bb29b5c6cf028
BLAKE2b-256 4589224d9f89fd05a477a36d91b5b0412e3395b0c53ecf663a55bb8a794f3675

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmcontrols-0.2.1-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