Skip to main content

Critical-path method (CPM) and Monte Carlo schedule-risk engine for project management

Project description

trueppm-scheduler

PyPI version PyPI downloads CI License

Project-schedule math as a library — critical path and delivery-risk forecasting, without a 500 MB desktop app or a SaaS subscription.

Answer the two questions every plan has to answer:

  • "What's the earliest this can finish, and which tasks can't slip?" — a full forward/backward critical-path pass computes early/late dates, total and free float, and flags the tasks on the critical path.
  • "How confident are we in that date?" — Monte Carlo simulation turns three-point estimates into a P50/P80/P95 forecast, so you can commit to a date you'll actually hit instead of the best-case one.

It's pure Python with just networkx and numpy underneath — no Django, no web server, no GUI. Drop it into a backend, a data pipeline, a Jupyter notebook, or a CLI and get the same engine that powers the TruePPM platform.

Why reach for this

  • Real scheduling semantics, not a toy. All four dependency types (finish-to-start, start-to-start, finish-to-finish, start-to-finish) with calendar-aware lag — most lightweight schedulers only do finish-to-start and count raw calendar days, which silently overruns any plan with a weekend in it.
  • Working-time aware. A built-in working-day calendar skips weekends and honors holiday exceptions, so durations resolve to real delivery dates.
  • Risk forecasting built in. PERT-Beta Monte Carlo, numpy-vectorized at ~10k runs/sec — the difference between "due March 3" and "70% likely by March 3, 95% by March 14."
  • Fails loud on bad input. Cycle detection that names the offending task IDs, plus up-front validation of durations, lag, and project span — no silent wrong answers, no spinning on a degenerate graph.
  • Embeds anywhere. Two dependencies, no framework. Serialize a plan to JSON, schedule it, and read back structured results.

Features

  • Forward/backward CPM pass with all four dependency types (FS, SS, FF, SF), total/free float, and critical-path flagging
  • Calendar-aware working-day arithmetic (weekend skip + holiday exceptions)
  • Monte Carlo schedule-risk simulation via PERT-Beta distributions (numpy-vectorized, ~10k runs/sec) → P50/P80/P95 completion dates
  • JSON round-tripping for plans (Project.from_json() / Project.to_json())
  • CLI: trueppm-scheduler schedule / trueppm-scheduler monte-carlo

Install

pip install trueppm-scheduler

Requires Python 3.11+.

Quick start

from datetime import date, timedelta
from trueppm_scheduler import schedule, Calendar, Project, Task, Dependency, DependencyType

calendar = Calendar()  # Mon–Fri, no holidays (whole-day scheduling)
task_a = Task(id="t-1", name="Design", duration=timedelta(days=5))
task_b = Task(id="t-2", name="Build",  duration=timedelta(days=10))
dep = Dependency(predecessor_id="t-1", successor_id="t-2", dep_type=DependencyType.FS)

project = Project(
    id="p-1",
    name="My Project",
    start_date=date(2026, 1, 5),
    tasks=[task_a, task_b],
    dependencies=[dep],
    calendar=calendar,
)

result = schedule(project)
build = next(t for t in result.tasks if t.id == "t-2")
print(build.early_finish)  # 2026-01-23 (15 working days from 2026-01-05, across two weekends)

Scheduling granularity. The engine schedules in whole working-day units. Calendar.hours_per_day and Calendar.timezone round-trip through serialization for API parity but are not consumed by the CPM or Monte Carlo passes — they do not change any computed date. Sub-day scheduling is a future change.

Per-task calendars

By default every task is scheduled on the single Project.calendar. A task can instead opt into its own working week — useful when one schedule spans teams or projects that keep different calendars:

seven_day = Calendar(working_days=0b111_1111)  # every day is a working day
support = Task(id="t-3", name="Hotfix", duration=timedelta(days=3), calendar_id="ops")

project = Project(
    id="p-1",
    name="My Project",
    start_date=date(2026, 1, 5),
    tasks=[task_a, support],
    calendar=Calendar(),                 # pass-level default (Mon–Fri)
    calendars={"ops": seven_day},        # registry tasks opt into by id
)

Conventions:

  • Duration arithmetic uses the task's own calendar (calendar_id → entry in Project.calendars). A calendar_id of None, or one with no matching entry, falls back to the pass-level Project.calendar — never an error.
  • Lag on a dependency edge is counted on the successor's calendar: the constraint lands where the wait is actually consumed.
  • It is fully backward compatible — a project with no calendars registry schedules byte-for-byte as before.
  • Per-task calendars are honored by the CPM schedule() pass (early/late dates, float, criticality). monte_carlo() samples on the pass-level Project.calendar only.

See the full documentation for CPM output fields, Monte Carlo usage, and CLI reference.

Errors and input limits

Every exception the engine raises subclasses ValueError, so one except ValueError catches them all — but each is individually catchable:

Exception Raised when
CyclicDependencyError The dependency graph contains a cycle. .cycle lists the task IDs forming it.
SimulationCapExceeded monte_carlo(runs=…) exceeds max_runs, or the project has more tasks than max_tasks.
InvalidScheduleInput The input is structurally valid but out of range (see limits below).

The engine walks the working calendar one day at a time, so it validates input up front rather than spinning on a degenerate project:

  • Calendarworking_days must set at least one weekday bit (Mon–Sun); a calendar whose exceptions blanket the entire search window is rejected too.
  • Duration — each task duration must be between 0 and MAX_DURATION_DAYS (36_525, ~100 years). Negative durations are rejected.
  • Lag — each dependency lag must be within ±MAX_LAG_DAYS (36_525).
  • Project span — the cumulative span (every task's worst-case duration plus the magnitude of every lag) must stay under MAX_PROJECT_SPAN_DAYS (366_000, ~1000 years), regardless of task count.
  • Monte Carloruns must be >= 1.

Project.from_json() also rejects the non-standard JSON literals NaN, Infinity, and -Infinity.

from trueppm_scheduler import schedule, InvalidScheduleInput

try:
    result = schedule(project)
except InvalidScheduleInput as e:
    print("Bad input:", e)  # "Task 't-1' duration exceeds the maximum of 36525 days (got …)."

API stability and versioning

The public API is the __all__ surface of the top-level trueppm_scheduler package — the names you can import directly from trueppm_scheduler (schedule, monte_carlo, Project, Task, Dependency, DependencyType, Calendar, ScheduleResult, MonteCarloResult, the exception types, etc.). Everything else — the trueppm_scheduler.engine internals, any underscore- prefixed helper, and module layout — is unstable and may move or change without notice.

This package is Development Status :: 3 - Alpha: the public API may change before 1.0. Pin an exact version rather than a range:

trueppm-scheduler==0.2.0a1

Alpha releases are pre-releases — pip install trueppm-scheduler skips them unless you pass --pre. Breaking changes are recorded in CHANGELOG.md, which also ships inside the wheel.

Monte Carlo determinism

Monte Carlo simulation is deterministic for a fixed seed: the same seed always yields the same P50/P80/P95 forecast for the same input. This is a supported, tested property you can rely on for reproducible reports and regression baselines — not an implementation detail.

License

Apache 2.0

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

trueppm_scheduler-0.3.0a2.tar.gz (103.4 kB view details)

Uploaded Source

Built Distribution

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

trueppm_scheduler-0.3.0a2-py3-none-any.whl (56.8 kB view details)

Uploaded Python 3

File details

Details for the file trueppm_scheduler-0.3.0a2.tar.gz.

File metadata

  • Download URL: trueppm_scheduler-0.3.0a2.tar.gz
  • Upload date:
  • Size: 103.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for trueppm_scheduler-0.3.0a2.tar.gz
Algorithm Hash digest
SHA256 e7b53b7be4911a4f51afd20bdf7fb0c8fe6fe7933f454a03632ef4d0e670e903
MD5 db5ad304fd526bf688ee58f2f3dfcb85
BLAKE2b-256 66dcad27c004681bee4e6e3b876f8d16f3da604802abfdd9a9bf30d31c680836

See more details on using hashes here.

Provenance

The following attestation bundles were made for trueppm_scheduler-0.3.0a2.tar.gz:

Publisher: .gitlab-ci.yml on trueppm/trueppm

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

File details

Details for the file trueppm_scheduler-0.3.0a2-py3-none-any.whl.

File metadata

File hashes

Hashes for trueppm_scheduler-0.3.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 659560c2ce3e55a794ae3effaf9aa506e34114f2afec0c6960f592edfc3cb145
MD5 736456ee8e262a6e53226b28363ca981
BLAKE2b-256 659d35e0d75ca0c77da24bf5c068850551dc90dbfde7d76cc66581d3a1abe283

See more details on using hashes here.

Provenance

The following attestation bundles were made for trueppm_scheduler-0.3.0a2-py3-none-any.whl:

Publisher: .gitlab-ci.yml on trueppm/trueppm

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