Metadata-Version: 2.4
Name: pmcontrols
Version: 0.2.1
Summary: 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.
Author: Atakan Arikan
License: MIT
Project-URL: Homepage, https://github.com/arikanatakan/pmcontrols
Project-URL: Documentation, https://arikanatakan.github.io/pmcontrols/
Project-URL: Repository, https://github.com/arikanatakan/pmcontrols
Project-URL: Issues, https://github.com/arikanatakan/pmcontrols/issues
Keywords: project-management,critical-path,cpm,pert,earned-value,evm,earned-schedule,schedule-risk,crashing,project-controls,eia-748
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Manufacturing
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Office/Business :: Scheduling
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.10
Provides-Extra: plot
Requires-Dist: matplotlib>=3.5; extra == "plot"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: matplotlib>=3.5; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs-material; extra == "docs"
Dynamic: license-file

# pmcontrols

[![CI](https://github.com/arikanatakan/pmcontrols/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/pmcontrols/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pmcontrols)](https://pypi.org/project/pmcontrols/)
[![License: MIT](https://img.shields.io/github/license/arikanatakan/pmcontrols)](LICENSE)

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:

```python
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]"`):

```python
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:

```python
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:

```python
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:

```python
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](https://arikanatakan.github.io/pmcontrols/agents/)
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](https://github.com/arikanatakan/lotsampling)),
general linear programming (use scipy or pyomo directly).

## License

MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
MSc Student at Tsinghua University and Politecnico di Milano.
