Project Management Digital Twin
Project description
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).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pmdt-1.1.2.tar.gz.
File metadata
- Download URL: pmdt-1.1.2.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a15031d10a8b9a93cdad7623c9d541a67cccfce7a2af81e2e24b72aa7d881e0
|
|
| MD5 |
55cb92f4dbcdf7294325d2d146a75360
|
|
| BLAKE2b-256 |
7310fe317da5530480b2b5b0040bb873176c099c4f6448098c446d956ce879af
|
Provenance
The following attestation bundles were made for pmdt-1.1.2.tar.gz:
Publisher:
python-publish.yml on filippomariaottaviani/pmdt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pmdt-1.1.2.tar.gz -
Subject digest:
9a15031d10a8b9a93cdad7623c9d541a67cccfce7a2af81e2e24b72aa7d881e0 - Sigstore transparency entry: 872451235
- Sigstore integration time:
-
Permalink:
filippomariaottaviani/pmdt@d0be2698c5b89b5b0d4d408556f10bdd662723c3 -
Branch / Tag:
refs/tags/1.1.2 - Owner: https://github.com/filippomariaottaviani
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@d0be2698c5b89b5b0d4d408556f10bdd662723c3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pmdt-1.1.2-py3-none-any.whl.
File metadata
- Download URL: pmdt-1.1.2-py3-none-any.whl
- Upload date:
- Size: 20.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
018008b42e81efb8ca10778a8b5658202f27d10896c174ff3f307451e40eeee6
|
|
| MD5 |
90f66ea8a41fae9c0666ab4702d252bd
|
|
| BLAKE2b-256 |
0516ca8e18cc0d1ae1c003a3570b5166910ee17e5cfe9ae56b90b822d08c07b3
|
Provenance
The following attestation bundles were made for pmdt-1.1.2-py3-none-any.whl:
Publisher:
python-publish.yml on filippomariaottaviani/pmdt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pmdt-1.1.2-py3-none-any.whl -
Subject digest:
018008b42e81efb8ca10778a8b5658202f27d10896c174ff3f307451e40eeee6 - Sigstore transparency entry: 872451238
- Sigstore integration time:
-
Permalink:
filippomariaottaviani/pmdt@d0be2698c5b89b5b0d4d408556f10bdd662723c3 -
Branch / Tag:
refs/tags/1.1.2 - Owner: https://github.com/filippomariaottaviani
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@d0be2698c5b89b5b0d4d408556f10bdd662723c3 -
Trigger Event:
release
-
Statement type: