PMDT — Project Management Digital Twin
A lightweight, pure‑Python toolkit for building and analyzing “digital twins” of projects:
- Calendars (working days, holidays, workday math)
- CPM scheduling (ES/EF/LS/LF, slack, criticality, FS/SS/FF/SF links with lag)
- Cost model (direct + overhead/indirect)
- EVM / Earned Schedule style performance tracking (PV/EV/AC, CPI/SPI, ES, SV(t), SPI(t), time & cost EACs)
- Monte Carlo schedule/cost simulation with percentile summaries
- Portfolio rollups across multiple projects (+ optional “AI” forecasting helper)
Requirements
Core module:
- Python 3.10+
numpypandas
Optional (only for Portfolio.init_aip()):
scikit-learn
Install basics:
pip install numpy pandas
# optional:
pip install scikit-learn
Core concepts
Calendar
Defines working days (0=Mon … 6=Sun), holidays, and “workday/networkdays” utilities.
- Default
working_daysis all 7 days; pass Mon–Fri explicitly if needed.
Resource
Represents labor/material/cost with a unit cost and (currently non-enforced) availability.
resource_typein{"work","material","cost"}
Activity
A task with:
- Dependencies (
predecessors) - Resources & duration (supports effort-driven duration scaling)
- Costs (direct + overhead)
- Tracking records (planned + actual/progress)
- Monte Carlo distribution parameters (duration & cost)
ControlAccount
Auto-created per activity-resource allocation. Holds cost tracking (AC) and EVA metrics.
Project
A collection of activities with:
- CPM schedule (
project.schedule()) - Time-phased baseline (PMB / PV curve)
- Resource usage time series
- EVA rollups + Earned Schedule style metrics
- Monte Carlo simulation (
project.mc())
Portfolio
A set of projects with portfolio-level EVA dataframe, and optional ML-based helper (init_aip).
Quickstart
Create a small project, schedule it, and view dataframes:
from pmdt import Calendar, Resource, Activity, Project
cal = Calendar(
name="Mon-Fri",
working_days=[0, 1, 2, 3, 4],
holidays=[20251225], # YYYYMMDD int, or date/datetime/ISO strings
)
dev = Resource(name="Dev", resource_type="work", unit_cost=120.0) # €/day (example)
mat = Resource(name="Parts", resource_type="material", unit_cost=50.0)
a1 = Activity(
name="Design",
baseline_duration=5,
baseline_resources={"Dev": (dev, 1.0)},
resources={"Dev": (dev, 1.0)},
)
a2 = Activity(
name="Build",
baseline_duration=3,
baseline_resources={"Dev": (dev, 1.0), "Parts": (mat, 10)},
resources={"Dev": (dev, 1.0), "Parts": (mat, 10)},
predecessors={"Design": (a1, "fs", 0)}, # FS + 0 day lag
)
proj = Project(
name="Demo",
activities=[a1, a2],
calendar=cal,
start_date=20260105, # YYYYMMDD
tracking_freq="D", # pandas date_range freq: D/W/M/...
)
proj.schedule()
print(proj.df_project())
print(proj.df_activities())
print(proj.df_resources())
print(proj.df_controlaccounts())
Dependencies (links)
Activities store predecessors as:
predecessors = {
"SomeKey": (predecessor_activity, rel_type, lag_days)
}
Supported rel_type values used by CPM:
"fs": Finish → Start"ff": Finish → Finish"ss": Start → Start"sf": Start → Finish
lag_days is a float; positive pushes successors later, negative pulls earlier.
Earned Value + Earned Schedule tracking
How tracking works
When you create/schedule a project it initializes tracking “records” at tracking_dates.
Keys are YYYYMMDD integers derived from those dates.
Planned values (PV) are computed; actual progress and cost are inputs:
- WP (work performed, 0–1) is stored on each Activity record
- AC (actual cost) is stored on each ControlAccount record
Then proj.df_eva() recalculates all derived fields and updates project rollups.
Minimal example: update progress and cost
# pick a tracking date key that exists in proj.records
date_key = 20260112
# 40% complete on "Build"
proj.activities["Build"].records[date_key]["WP"] = 0.40
# add cost on the Build-Dev control account
ca_name = "Build-Dev" # ActivityName-ResourceName by default
proj.control_accounts[ca_name].records[date_key]["AC"] = 1500.0
df_eva = proj.df_eva()
print(df_eva.tail())
Useful columns you’ll see
Typical EVA columns include:
PV,EV,AC,CV,SV,CPI,SPIEAC_CV,EAC_CPI- time EACs like
EAC(t)_SPI[Days]/EAC(t)_SPI[Date] - Earned Schedule fields at project level:
ES[Days],SV(t),SPI(t), etc.
Performance Measurement Baseline (PMB / PV curve)
Generate time-phased PV:
proj.pmb()
df_daily_pv = proj.df_pmb_project # daily PV
df_cum_pv = proj.df_pmb_project_cuml # cumulative PV
Resource usage
Compute daily resource usage (units per day):
proj.resource_usage()
print(proj.df_resource_usage.head()) # daily usage by resource
print(proj.df_resource_usage_cuml.head()) # cumulative
Monte Carlo simulation
Each activity supports separate distributions for duration and cost.
Supported distributions include:
fixed, uniform, exponential, normal, log-normal,
triangular, pert, beta, gamma, weibull, discrete.
Example: triangular duration + normal cost
for a in proj.activities.values():
a.duration_distribution = "triangular"
a.duration_params = {"left": 3, "mode": 5, "right": 9}
a.cost_distribution = "normal"
a.cost_mean = a.total_cost
a.cost_stdev = 200.0
proj.mc(n_simulations=2000, track_pmb=True)
print(proj.df_mc.describe(percentiles=[0.05, 0.5, 0.95]))
print(proj.df_mc_pmb_project.head()) # PV percentiles per time bucket (if track_pmb=True)
print(proj.df_mc_pmb_project_cuml.head()) # cumulative PV percentiles
Portfolio
from pmdt import Portfolio
pf = Portfolio([proj], name="My Portfolio")
print(pf.df_projects())
print(pf.df_eva().tail())
Optional: AI-powered helper (init_aip)
Portfolio.init_aip() is an experimental helper that prepares EVA data, optionally interpolates by WP steps,
and runs leave-one-project-out modeling using scikit‑learn.
# requires: pip install scikit-learn
df0, df1, df2, df3, df4, df_out, dfr_model, dfr_wp, dfr_project = pf.init_aip(
target="cost", # or "time"
method="direct", # or "indirect"
model="LinearRegression" # or "MLPRegressor"
)
Notes & limitations
- CPM scheduling does not level resources;
Resource.availabilityis currently informational. - Records are easiest to work with if you keep date keys consistent with the project’s
tracking_dates(use the existing YYYYMMDD keys created by the project).
Metadata
Release files for pmdt 1.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pmdt-1.1.2.tar.gz | 20.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pmdt-1.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.3 kB
Release files / pmdt-1.1.2.tar.gz
| Download URL | pmdt-1.1.2.tar.gz |
|---|---|
| Size | 20.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9a15031d10a8b9a93cdad7623c9d541a67cccfce7a2af81e2e24b72aa7d881e0
|
|
BLAKE2b-256 checksum How to use checksums |
7310fe317da5530480b2b5b0040bb873176c099c4f6448098c446d956ce879af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 30, 2026.
Transparency logRelease files / pmdt-1.1.2-py3-none-any.whl
| Download URL | pmdt-1.1.2-py3-none-any.whl |
|---|---|
| Size | 20.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
018008b42e81efb8ca10778a8b5658202f27d10896c174ff3f307451e40eeee6
|
|
BLAKE2b-256 checksum How to use checksums |
0516ca8e18cc0d1ae1c003a3570b5166910ee17e5cfe9ae56b90b822d08c07b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 30, 2026.
Transparency log