Forensic scheduling MCP for Primavera P6 (XER): AACE windows, DCMA-14, Monte Carlo, TIA.
io.github.danafitkowski/cpp-cpm-engine — MCP Server
This MCP server provides forensic scheduling capabilities for Primavera P6 using XER inputs. Its description references AACE windows, DCMA-14 analysis, Monte Carlo schedule risk, and TIA-related processing. The project is the cpp-cpm-engine npm package (MIT license) with 13 tools, and includes cross-validation between JS and Python.
🛠️ Key Features
Primavera P6 forensic scheduling via XER (AACE windows)
DCMA-14 delay analysis support
Monte Carlo schedule risk
TIA-related processing
DAUBERT-style independent verification and crossval (JS↔Python)
Run forensic windows analysis (AACE RP 29R-03 §3.3, MIP 3.3
Observational / Dynamic / Contemporaneous As-Is) across
multiple Primavera P6 XER snapshots and return the full analysis
dict.
This is the headline forensic tool — it computes per-window
completion shifts, per-window slip registers (per-activity slip
with critical/non-critical flag), per-window duration growth on
critical-path activities, per-window per-party attribution
(Owner / Contractor / Concurrent / Force Majeure / Unattributed),
and cumulative project drift from baseline. The attribution math
satisfies the CPP conservation check, per the AACE 29R-03
§3.3.E.13 requirement that the summed per-period net impacts
equal the difference between the first schedule update and the
last schedule update used in the evaluation (per-party day
buckets sum to project drift within ±1 day, no cascade-double-
counting).
Use this tool for the full multi-window forensic claim. If you
already have a windows result and only want the per-window ×
per-party grid view, call ``concurrent_delay_matrix`` instead.
Args:
schedules: list of dicts in chronological order. Minimum 2
entries (baseline + at least one update). Each dict
must contain ``label`` (str) and EXACTLY ONE of:
- ``xer_path`` — server-side filesystem path, OR
- ``xer_content`` — full XER text content.
Use ``xer_content`` when calling a hosted MCP server
from a remote client whose XER lives locally.
project_name: optional override; auto-picked from XER if "".
baseline_idx: which entry in ``schedules`` is the contract
baseline (default 0 = first one).
entitlement_milestone: optional task_code (e.g.
"Ready for Takeover") — recorded on the result, not used
for math.
output_dir: optional dir for HTML dashboard / DOCX report.
If "", a tempdir is used and dropped after — the
dashboard / report paths in the response will point to
the temp location (caller responsible for moving them).
Returns:
{
"analysis": full dict from run_windows() with keys:
"windows", "cumulative", "baseline_label", "data_dates",
"attribution_summary", "mcpm_attribution", ...,
"dashboard": path to HTML dashboard (server-side),
"report": path to DOCX executive report (server-side),
"baseline_stability": {"worst_severity", "has_block", ...}
}
On failure: {"error": "..."} with no schedules processed.
Build the per-window x per-party concurrent-delay attribution
matrix from a chronological list of XER snapshots.
Implements the per-window concurrency view per AACE RP 29R-03
§3.3.I (apportionment) and §4.2 (concurrency). Where
``forensic_windows_analysis`` answers "how many days does each
party own across the whole project?", this tool answers "how did
each window distribute its shift across the parties?" — useful
when defending or attacking concurrency findings on a
window-by-window basis.
CPP conservation check, per the AACE 29R-03 §3.3.E.13
requirement that the summed per-period net impacts equal the
difference between the first schedule update and the last
schedule update used in the evaluation: the sum of per-party
column totals equals the sum of per-window completion shifts
within ±1 day of rounding. The column-total definition is this
tool's own bookkeeping, not an AACE rule. The ``conservation_check`` field on
the response reflects this; ``conservation_diff_days`` carries
the exact gap.
IMPORTANT — conservation is NOT attribution. ``conservation_check``
can be True (the columns sum to the grand total) even when 100% of
the shift lands in the Unattributed column, i.e. no party owns any
of the drift. Read ``unattributed_share_pct`` and
``high_unattributed_share_warning`` to know whether a meaningful
apportionment actually occurred. A fully-unattributed matrix
conserves perfectly but attributes nothing — never present its
green conservation check as a validated apportionment.
Use this tool when you only need the matrix view; use
``forensic_windows_analysis`` for the full claim.
Args:
schedules: chronologically ordered list of dicts — the SAME
shape ``forensic_windows_analysis`` accepts. Each dict
carries ``label`` (optional) and EXACTLY ONE of
``xer_content`` (full XER text, hosted/remote use) or
``xer_path`` (server-side path, local use). This is the
preferred input for hosted/remote clients.
xer_paths: legacy chronologically ordered list of server-side
XER file paths (local-server use).
xer_contents: legacy chronologically ordered list of XER text
contents. Each element is the full text of one XER.
Supply EXACTLY ONE of schedules / xer_paths / xer_contents
(lists must have at least 2 entries either way).
Returns:
{
"parties": ["Owner", "Contractor", "Concurrent",
"Force Majeure", "Unattributed"],
# Unit for every shift_* field and the grand totals. Always
# "working_days" — the matrix measures the completion shift
# in working days (Dana default). The *_calendar_days twins
# express the SAME shift in calendar days so an unlabeled
# "11" can never be mistaken for the 15-calendar-day value.
"shift_unit": "working_days",
"rows": [{ "window_label", "period_start", "period_end",
# shift_days == shift_workdays (working days,
# legacy alias). shift_calendar_days is the same
# shift in calendar days; shift_basis names the
# finish driver the shift was measured on.
"shift_days", "shift_unit", "shift_workdays",
"shift_calendar_days", "shift_basis",
"parties": {party: days},
"cascade_inferred": bool }, ...],
"column_totals": {par
Worked-vs-On-time Execution Timeline (WOET) per-activity
day-by-day classification of as-built execution against baseline.
For each pairable activity (matched by ``task_code``), classifies
execution into 4 day-states:
- PROGRESS: work performed during the baseline-planned window
- GAIN: work performed BEFORE the baseline window opened
- EXTENDED: work performed AFTER the baseline window closed
- VOID: baseline-window day where activity was NOT active
This is a CPP-disclosed enhancement layered on top of AACE
29R-03 §3.3 Windows Analysis — a per-day execution classifier
(Progress/Gain/Extended/Void) NOT itself AACE-defined. It is
not a substitute for fragnet-based AACE 29R-03 §3.7 (TIA)
modeling. It gives the trier-of-fact a calendar picture of
how the project executed versus how it was supposed to
execute, which is otherwise buried in finish-date deltas.
Use this tool when you want a per-activity execution-quality
picture (on-time %, count of activities with VOID days, etc.).
Args:
baseline_xer_path: server-side path to baseline XER (target dates).
actual_xer_path: server-side path to as-built XER (act dates).
baseline_xer_content: full text of baseline XER (alternative).
actual_xer_content: full text of as-built XER (alternative).
Supply EXACTLY ONE of path/content per pair.
today: optional ISO date (YYYY-MM-DD) reference
for in-progress activities. Defaults to actual XER's
last_recalc_date if available, else today's date.
Returns:
{
"method": "WOET",
"standard": "AACE 29R-03 §3.3 Windows Analysis — per-day execution classification overlay (CPP-disclosed enhancement, not AACE-defined)",
"today": "YYYY-MM-DD",
"project_totals": {progress, gain, extended, void},
"per_activity": [{code, name, baseline_start, ...,
"dominant": str ('progress'|'gain'|'extended'|'void'
or 'mixed' on a tie),
"dominant_tie": bool (True when 2+ states share the top
day count — do NOT assert one characterization),
"dominant_states": [tied top states, never truncated]},
...],
"on_time_pct": float (0-100)
}
Per-window slip velocity & acceleration trend across XER snapshots.
Computes three signed metrics per window from the underlying
forensic windows analysis:
- slip_velocity_days_per_day: completion shift / window
duration (positive = slipping, negative = recovering).
Numerator is the WORKING-day completion shift. The
denominator is WORKING days between the prior and later
data dates on the same calendar
(``window_duration_workdays``), making this a same-day-type
working-day/working-day rate. It falls back to CALENDAR days
only for legacy window dicts that predate that field, and
such a row is flagged ``velocity_basis="wd/cd"``. Read
``velocity_basis`` to know which denominator produced the
figure.
Each velocity field name states the ratio it holds:
``slip_velocity_workdays_per_workday`` (populated only on
the wd/wd path), ``slip_velocity_workdays_per_calendar_day``
(working-days of slip per CALENDAR day elapsed, computed
against ``window_duration_days``), and
``slip_velocity_days_per_day`` as the retained back-compat
name for whichever basis was selected. Quote ``basis`` in
any expert report.
NOTE (2026-09-03): the two named fields are no longer equal.
``slip_velocity_workdays_per_calendar_day`` used to be a
blind copy of the headline velocity, which made its name
wrong once the denominator moved to working days — it read
5/10 = 0.500 while its name promised 5/14 = 0.357. It now
holds the calendar-day rate it is named for.
- slip_acceleration: velocity[n] - velocity[n-1] (positive
= slip rate increasing, negative = decelerating/recovery)
- half_period_estimated_slip_days: shift / 2 (forensic
"where were we at the midpoint" centroid estimate), in
WORKING days
Cumulative aggregates ``mean_velocity_days_per_day`` plus a
mean per basis — ``mean_velocity_workdays_per_workday`` and
``mean_velocity_workdays_per_calendar_day`` — each computed
only from the rows that actually carry that denominator, so a
mean is never labelled with a basis it did not use (None when
no window carried it). ``velocity_basis_set`` lists the bases
present and ``velocity_units`` describes them, including an
explicit MIXED string when a run spans both.
Also ``max_velocity_window`` and accelerating / decelerating /
recovery window counts.
Honest caveats embedded in the response (mandatory for expert
reports): midpoint estimates are probabilistic centroids, not
observed events; velocity is per-window average, not
instantaneous; acceleration is a finite difference, not a true
second derivative.
Built on top of AACE RP 29R-03 §3.3 windows analysis. Use this
tool when you want a slip-rate trend line on top of the same
per-window math ``forensic_windows_analysis`` already computes.
Args:
schedules: chronologically ordered list of dicts — the SAME
shape ``forensic_windows_analysis`` accepts. Each dict
carries ``label`` (optional) and EXACTLY ONE of
``xer_content`` or ``xer_path``. Preferred input for
hosted/remote clients.
xer_paths: legacy chronologically ordered list of server-side
XER paths.
xer_contents: legacy chronologically ordered list of XER text
contents (alternative for hosted/remote use).
Supply EXACTLY ONE of schedules / xer_paths / xer_contents
Collapsed As-Built / But-For analysis on a post-impact XER.
Implements AACE RP 29R-03 §3.8 Modeled / Subtractive / Single
Base method (paired with MIP 3.3 Windows for the dual-method
gap report per SCL §11.5). Validates a forensic windows
analysis (MIP 3.3) by independently computing the same
project drift via subtractive removal of delays from the
as-built schedule.
For each delay event, the as-built duration of every
``affected_activity`` is shortened by ``impact_days`` (or
removed entirely if ``removal_method="remove"``), then CPM
re-runs and the resulting "but-for" finish date is compared
to the as-built finish. Cumulative pass removes ALL events at
once for a project-level but-for finish.
Use this tool when opposing counsel demands a but-for analysis
or you need a dual-method validation pairing §3.3 (windows) with
§3.8 (collapsed-as-built). For prospective fragnet insertion
(MIP 3.7), use ``time_impact_analysis_fragnet`` instead.
Args:
as_built_xer_path: server-side post-impact XER (after delays incurred).
as_built_xer_content: full text of post-impact XER (alternative for hosted/remote use).
Supply EXACTLY ONE of path/content.
delay_events: list of event dicts. Each must have
``event_id``, ``affected_activities`` (list of
task_codes), and ``impact_days`` (number). Optional:
``removal_method`` ('shorten'|'remove'),
``responsible_party``, ``name``, ``description``.
output_dir: optional output dir for HTML/CSV (tempdir if "").
project_name: optional override.
removal_method: global default 'shorten' or 'remove'.
contractor_filter: when True, exclude contractor-caused
events from the cumulative pass (owner audit mode).
Returns:
{
"as_built_finish": "YYYY-MM-DD",
"per_event_results": [{event_id, but_for_finish,
impact_days_collapsed,
duration_removal_basis,
finish_driver_after_removal, ...}, ...],
# duration_removal_basis discloses WHAT duration was removed
# and on what basis; finish_driver_after_removal discloses
# WHAT drives the but-for finish (incl. whether it is bound by
# the data-date floor) so a reader sees WHY the finish did or
# did not move across data dates.
"cumulative_but_for_finish": "YYYY-MM-DD",
"cumulative_impact_days": int,
"dual_method_gap": dict | None,
"output_files": {...},
"warnings": [...],
"method": "AACE 29R-03 §3.8 (Modeled/Subtractive/Single Simulation)"
}
Time Impact Analysis (TIA) — prospective fragnet insertion
into a pre-impact baseline schedule. Supports two modes.
**Single-base mode** (legacy): supply ``baseline_xer_path`` or
``baseline_xer_content``. All fragnets are inserted into the
same shared baseline XER and impact is measured against that
shared baseline. The result carries a
``single_base_disclosure`` warning explaining this is an AACE
29R-03 §3.7 simplification — acceptable when all events share
a single baseline window, but not strict MIP 3.7 Multiple
Base.
**Multi-base mode** (AACE 29R-03 MIP 3.7 Multiple Base):
supply ``per_event_bases`` — a dict keyed by each fragnet's
``id``, with each value a dict containing EITHER
``xer_path`` OR ``xer_content`` for that event's
pre-event contemporaneous baseline. Each fragnet is inserted
into its OWN base, impact is measured against THAT base's
pre-event finish, and the result carries
``per_event_methodology``, ``per_event_base_count``, and
``per_event_bases_used`` (sha256-truncated content hashes for
audit reproducibility). The cumulative-impact figure carries
``cumulative_caveat`` because the sum of events measured
against different bases is NOT a valid joint impact.
Exactly ONE of {baseline_xer_path, baseline_xer_content,
per_event_bases} must be supplied. Multi-base mode errors out
(returning ``{"error": ...}``) if any fragnet id is missing
from ``per_event_bases``.
Use this tool when modeling delay impact prospectively (e.g.
quantifying RFI / change-order delay before settlement). For
retrospective windows analysis after the fact, use
``forensic_windows_analysis`` (MIP 3.3 windows).
Args:
baseline_xer_path: server-side pre-impact baseline XER
(single-base mode).
baseline_xer_content: full text of pre-impact baseline XER
(single-base mode, hosted/remote use).
per_event_bases: dict {fragnet_id: {"xer_path": "..."}
OR {"xer_content": "<full XER text>"}}
for AACE MIP 3.7 Multiple Base mode.
Example::
{
"F1": {"xer_path": "/tmp/bl_pre_F1.xer"},
"F2": {"xer_content": "<XER text>"},
}
fragnets: list of fragnet dicts. Each must have:
- 'id', 'name', 'liability' (responsible party)
- 'activities': list of {code, name, duration_days,
calendar_id?}
- 'ties': list of {pred, succ, type, lag_days?}
Optional: 'description'.
output_dir: output dir for TIA_Report.txt + CSV (tempdir if "").
project_name: optional override.
Returns:
{
"report": path to TIA_Report.txt,
"impacts_csv": path to TIA_Impact_Details.csv,
"baseline": {"project_finish", "critical_count", ...},
"per_fragnet": [{fragnet_id, name, liability,
completion_before, completion_after,
impact_days, impact_working_days,
affected_activities, status, error}, ...],
"cumulative_days": int (sum of per-fragnet impacts),
"cumulative_basis": str (BOTH modes — states the cumulative
figure is the sum of independent
p
Critical-path validation, logic health, and DCMA-14
assessment of a Primavera P6 schedule.
Runs the CPP critical-path validator: checks for false
criticality, constraint-driven CP segments, open ends, broken
logic, and surfaces a DCMA-14 block with the 14 metrics
(logic, leads, lags, FS%, hard constraints, high float, high
duration, invalid dates, resources, missed tasks, critical
tasks, CPLI, BEI, etc.) at the chosen profile threshold
(commercial / nuclear / mining). When ``baseline_xer_path``
is supplied, BEI (Baseline Execution Index) is computed.
Use this tool to grade a schedule's logic health and find what
should be fixed before forensic analysis. For the full HTML
health-dashboard PDF render, use ``dcma14_health_check``.
Args:
xer_path: server-side path to the schedule XER.
xer_content: full text of the schedule XER (alternative for
hosted/remote use). Supply EXACTLY ONE of path/content.
project_index: which project to analyze in a multi-project
XER (0 = first/primary; default).
profile: DCMA threshold profile -
'commercial' (default), 'nuclear', 'mining'.
baseline_xer_path: optional server-side baseline XER for DCMA BEI.
baseline_xer_content: optional baseline XER text content (alternative).
Returns:
Full validator result dict including:
- 'project_name', 'data_date', 'analysis_timestamp'
- 'total_activities', 'complete', activity counts
- 'critical_path_findings': list of issues
- 'logic_findings', 'constraint_findings'
- 'overall_rating' / 'overall_score' / 'overall_confidence':
LOGIC-HEALTH verdict only (open ends, logic continuity,
critical-path correctness, constraints, lags). NOT a full
schedule-health verdict.
- 'overall_rating_scope': always 'logic_health';
'overall_rating_label': 'Logic Health'. Use these so the
headline cannot be read as full DCMA schedule-health.
- 'dcma_worst_severity': the embedded DCMA-14 worst severity
(BLOCK/RED/WARN/INFO/PASS) surfaced at the top level so a
DCMA hard stop is visible next to the logic-health rating
rather than buried in dcma_14.report.summary.
- 'dcma_blocks_despite_logic_rating': True when DCMA-14 says
BLOCK/RED even if the logic-health headline reads GREEN/AMBER.
- 'dcma_14': dict of 14 DCMA metric results
- 'recommendations': list of remediation suggestions
Full Schedule Health Dashboard HTML report — DCMA-14 + CPLI
+ BEI + variance/slip register against the baseline.
Wraps the CPP Schedule Health Review skill, which produces a
self-contained ~1.3 MB HTML dashboard. The dashboard renders
DCMA metrics, charts, baseline-vs-current variance, slip
register, GAO/AACE compliance bands, and a reproducibility
manifest.
Baseline XER is OPTIONAL as of Round 7 (Fix MCP-8). When
omitted, the tool runs in "degraded mode": the current XER
is used as its own baseline for a synthetic 0-variance run.
The result carries ``degraded_mode: true`` and
``degraded_mode_reason`` explaining that BEI / variance /
slip register KPIs are NOT meaningful in this mode. Supply
baseline_xer_path or baseline_xer_content to get the real
two-XER variance dashboard.
REQUIRES Node + Playwright on the server (the dashboard renders
via headless Chromium). The tool returns a clear error if
either prerequisite is missing.
Use this tool when you need the formal HTML deliverable.
Do NOT treat ``critical_path_validator`` as a JSON view of this
tool. It runs a SECOND, independent DCMA-14 implementation
(``critical-path-validator/scripts/dcma14.py``) with its own
criterion numbering, its own activity-eligibility rules and its
own CPLI definition. Measured across the real-export corpus on
2026-08-25, the two engines return different verdicts on
individual criteria for the same XER, and on some criteria they
differ by construction on every file. Two separate DCMA-14
implementations, neither derived from the other. Cite one
engine per matter and name which. If what you wanted was the
JSON shape of THESE numbers, it is already in this tool's own
return: ``dcma_14``, ``metrics`` and ``headline`` are extracted
verbatim from the HTML this call produced, so they cannot
disagree with the deliverable the client is reading.
=== HOW TO PASS THE XER FILES ===
For each XER (current, baseline) you supply EXACTLY ONE of:
- ``*_xer_path`` — filesystem path on the server. Use this
when the MCP server runs locally and the
file is already accessible to it.
- ``*_xer_content`` — full text of the XER file as a string.
Use this when calling a HOSTED MCP server
from your local Claude — the server has no
access to your local filesystem, so you
must send the content over the wire. The
server writes it to a tempfile, runs the
pipeline, and cleans up afterward.
If both are supplied for the same XER, content wins (the path
is ignored). If neither is supplied, the call returns an error.
Args:
current_xer_path: server-side path to the current XER.
baseline_xer_path: server-side path to the baseline XER.
current_xer_content: full text of the current XER (alternative).
baseline_xer_content: full text of the baseline XER (alternative).
output_path: optional output HTML path. Ignored when content
is supplied (output goes to a tempdir alongside).
timeout_seconds: per-step Playwright timeout (default 120s).
A floor: for the parse and download waits the server
applies a deadline grown from the activity count of the
XERs supplied, so large schedules need no larger value.
debug: pipe Playwright stderr / browser console
Logic-trace driver-chain explorer — answers "WHY is this
activity critical?" and "WHAT does it drive?".
Traces driving predecessors backward from a target activity to
project start (the "why critical" chain) and/or driving
successors forward to project finish (the "what it drives"
chain). Detects constraint-driven artificial criticality and
cites AACE RP 49R-06 when found. Supports multiple parallel
critical paths (MCPM) and near-critical paths.
Use this tool when investigating a single activity's logic
chain. For a project-wide CP / logic health audit, use
``critical_path_validator``.
Args:
xer_path: server-side path to the schedule XER.
xer_content: full text of the schedule XER (alternative for
hosted/remote use). Supply EXACTLY ONE of path/content.
target_activity_codes: list of task_codes to trace; if
empty, all CP / near-critical endpoints are traced.
direction: 'backward' (predecessors), 'forward'
(successors), or 'both' (default).
include_near_critical: also trace near-critical endpoints
(within float band).
output_dir: optional dir for HTML / CSV / JSON outputs.
Returns:
{
"paths": [{chain dicts ...}],
"output_files": {dashboard, csv, json},
"project_finish": "YYYY-MM-DD",
"project_name": ...,
"data_date": ...
}
Monte Carlo Schedule Risk Analysis — P10/P50/P80/P90
completion-date forecast for a Primavera P6 schedule.
Implements an AACE-style quantitative SRA (the same math as
CPP's browser Tool_11 Portfolio Risk Engine, scripted Python
counterpart). For each iteration, every activity duration is
sampled from the chosen distribution (Triangular, BetaPERT,
Uniform, Lognormal, etc.) parameterized by % of baseline
duration; CPM re-runs and the project finish date is recorded.
After all iterations, P10/P50/P80/P90 completion dates and a
sensitivity tornado (per-activity correlation to project
finish) are reported.
Use this tool when you need probabilistic completion forecasts
or a tornado/sensitivity ranking. For the QRAMM-aligned
five-level maturity badge (AACE 122R-22) on the result,
pipe the response into
``qramm_maturity``.
Args:
xer_path: server-side path to the schedule XER.
xer_content: full text of the schedule XER (alternative for
hosted/remote use). Supply EXACTLY ONE of path/content.
iterations: number of MC iterations (default 5000).
distribution: 'Triangular', 'BetaPERT', 'Uniform',
'Lognormal' (case-insensitive — passed through).
optimistic_pct, most_likely_pct, pessimistic_pct: %
of baseline duration for the distribution params
(defaults: 85 / 100 / 120).
seed: optional fixed seed for reproducibility (0 = system
entropy = non-reproducible).
output_dir: optional output dir; tempdir if "".
Returns:
Full SRA result dict, key paths:
- 'baseline.percentiles': lowercase p-keys
{'p10','p25','p50','p75','p80','p85','p90','p95'},
each {'day', 'date'}. NOTE: keys are lowercase — read
result['baseline']['percentiles']['p80'], not 'P80'.
- 'baseline.config': sim params used
- 'baseline.sensitivity': per-activity tornado rows
- 'risk_register_simulation.percentiles' (only when a
risk_register is supplied): SAME lowercase convention,
{'p10','p50','p80','p90'} each {'day', 'date'}.
- 'project_name', 'data_date', ...
- HTML / DOCX paths if outputs emitted
QRAMM-aligned maturity reading for an SRA result.
Places a Schedule Risk Analysis run (from
``monte_carlo_p50_p80`` or any equivalent dict) on the five
named maturity levels of AACE RP 122R-22 (Quantitative Risk
Analysis Maturity Model), section 3: level 1 Reactive,
level 2 Ad-hoc, level 3 Centralized, level 4 Dynamic,
level 5 Adaptive.
Inputs the SRA inspects (defensively, all keys optional):
- baseline.percentiles (lowercase p50 / p80 presence)
- baseline.config (iterations, opt/ml/pes %, distribution)
- baseline.sensitivity (per-activity tornado rows, on_cp)
- mitigated (scenario comparison evidence)
- risk_register_simulation + risk_register_used
(Hulett quantified risk register evidence)
- convergence (MC diagnostics, required for level 5)
Use this tool any time you have an SRA result and want a
maturity reading for a forensic-claim methodology section.
Note the scope limit the badge carries on every render: AACE
122R-22 assesses the quantitative-risk capability of an
ORGANIZATION and states it is guidelines rather than a
standard, so a reading on one simulation run is CPP's
placement on the RP's scale, not a QRAMM score. Report it as
"QRAMM-aligned", never as "per AACE 122R-22".
Args:
sra_result: dict from ``monte_carlo_p50_p80``. May be {} -
the badge degrades to level 1 with the missing
evidence listed.
Returns:
{
"rp_citation": "AACE RP 122R-22 ...",
"scale_max": 5,
"scale_note": "... names five levels ...",
"level": int (1-5),
"level_name": "Reactive" | "Ad-hoc" | "Centralized"
| "Dynamic" | "Adaptive",
"level_label": "Level 3: Centralized",
"level_description": what CPP requires of a run there,
"level_color": "#xxxxxx",
"evidence": ["..." what the SRA had / lacked],
"gaps_to_next_level": ["..." concrete advance steps],
"caveat": scope-limit string
}
The keys "tier", "tier_label", "tier_description",
"tier_color" and "gaps_to_next_tier" are retained as
aliases carrying the same five-level values.
Forensic claim workbench — analyzes a folder of mixed
evidence (XER chain + MSG/PDF/DOCX/XLSX correspondence) and
produces a unified workbench dashboard.
Built from the real-world workflow where forensic delay
analysis starts from a folder containing schedule updates,
owner correspondence, RFIs, change orders, and meeting
minutes — all mixed together. The workbench produces:
- Evidence ledger (chronological): all artifacts dated and
summarized
- Schedule chain-diff: 14-category manipulation log
(TASKPRED add/remove, constraint flips, retroactive
baseline edits, completion reversals)
- Rolling baseline: per-activity baseline-at-introduction
across the entire XER chain
- Trust score: statistical impossibilities flagged
(zero-duration-variance schedules, no-new-activities,
every-activity-hits-baseline, etc.)
- Slip-to-evidence cross-reference: each forensic slip
auto-paired with documents in its window mentioning
affected activity codes
- Unified HTML dashboard with all of the above
Use this tool when starting forensic delay analysis from raw
evidence. For single-XER-pair forensic with hand-prepared
events, use ``forensic_windows_analysis`` instead.
Two input modes (supply exactly one):
* ``folder_path`` — a server-side evidence folder that already
resolves UNDER the server temp directory (the path guard).
Hosted callers cannot reach a desktop path this way.
* ``evidence_files`` — a CONTENT MANIFEST: a list of
``{"name": str, "content_b64": str}`` entries carrying
base64-encoded file BYTES (handles binary PDF/XLSX/MSG as
well as text). The tool decodes each blob, sanitizes the
filename to a bare basename (rejecting path separators,
``..``, absolute/drive paths, control chars, dot-only
traversal), writes it into a FRESH per-call tempdir under
the allowed server-tempdir root, runs the analysis on that
staged folder, then cleans the staged dir up. Caps: at most
500 files and 60 MB total decoded bytes — an over-cap
manifest returns a clear ``tool_error`` naming the cap and
the actual size (NEVER silently truncated).
Args:
folder_path: path to the evidence folder (mode 1; must
exist and resolve under the server tempdir).
evidence_files: content manifest (mode 2); list of
``{"name": str, "content_b64": str}``.
output_dir: optional dir for outputs (tempdir if "").
project_name: optional override.
original_baseline_xer_filename: optional filename in the
folder identifying the baseline XER.
contract_form: contract template tag (default 'CCDC2').
run_forensic: when True (default), also runs
forensic_windows_analysis on the discovered XER chain.
Returns:
{
"evidence_ledger": {...},
"chain_diff": {...} | None,
"rolling_baseline": {...} | None,
"trust_score": {...} | None,
"cross_reference": {...} | None,
"forensic_result": {...} | None,
"output_files": {...},
"errors": {...} (per-step failure log)
}
Parse a Primavera P6 XER file and return a TABLE SUMMARY
(not the full row-level data — XER row dumps explode the MCP
context window).
For each table in the XER, returns the table name, field list,
and record count. Per-row data is intentionally omitted — for
forensic / DCMA / windows analysis use the dedicated tools
(``forensic_windows_analysis``, ``critical_path_validator``, etc.)
which consume the parsed XER internally and return analytical
summaries, not raw rows.
Use this tool to confirm an XER is parseable, list its tables, see
the data date / project name from PROJECT, or count activities in
TASK before deciding which deeper tool to run.
Args:
xer_path: server-side filesystem path to the XER file.
xer_content: full text of the XER file (alternative for
hosted/remote use). Supply EXACTLY ONE of path/content.
Returns:
{
"filepath": absolute path,
"encoding_used": "utf-8" | "cp1252" | ...,
"ermhdr": file header dict (P6 version, export user, etc.),
"tables": [{"name", "fields", "record_count"}, ...],
"table_count": int,
"total_records": int,
"project_summary": {
"proj_id", "proj_short_name", "proj_long_name",
"data_date", "plan_end_date"
} (from first PROJECT row, if any)
}
An open-source CPM engine with AACE-aligned methodology and a published Daubert disclosure.
Open source. AACE-aligned. Daubert-disclosed. 1,345 JS unit tests, plus JS/Python parity on 2465 of 2539 enumerated cross-validation comparisons; the remaining 74 are not compared because neither engine emits the field on a completed activity (see DAUBERT.md §3.1).
The npm package lags this repository. npm latest is 2.9.19, published 2026-05-17 — that is the number the npm badge above renders, and nothing has been published to npm since, while every later release has shipped here as a git tag. npm install cpp-cpm-engine therefore does not give you the engine described by DAUBERT.md, by the release-evidence/ packets, or by any version reference in these docs. Install from the git tag:
bash
git clone https://github.com/danafitkowski/cpp-cpm-engine.git
cd cpp-cpm-engine
git checkout v2.9.49
The engine has zero runtime dependencies, so a clone is all it needs. This is the same install path VERIFY_RELEASE.md gives an opposing expert.
That's it. Forward pass, backward pass, total float, free float, calendar arithmetic, P6-conventional date math, multi-jurisdiction holidays — all done.
Bayesian update with hierarchical pooling (pre-publication, JS-only)
yes
66 default holiday rule sets (multi-jurisdiction; framework-aligned defaults, not legally certified — see docs/jurisdictions.md)
yes
MIT licensed
yes
(Vendor comparison removed in the v2.9.33 audit cycle. Comparisons against specific commercial CPM tools should be supplied by an independent reviewer, not authored by the engine's maintainer.)
The engine math is a commodity. What carries a forensic schedule analysis is the workflow, the methodology discipline, and the Daubert disclosure posture — not the forward pass itself. Critical Path Partners open-sources the engine so any academic, any solo forensic scheduler, any contractor's internal scheduler can build on a transparent, citable foundation.
What you can build
Forensic delay analysis primitives — CPM forward/backward pass that supports analyses under AACE 29R-03 MIPs 3.3 (windows), 3.6/3.7 (prospective TIA single-base / multi-base), and 3.8 (collapsed as-built). The engine provides the CPM math; full method implementations (period selection, fragnet integration, as-built reconstruction) live in the CPP forensic skill suite — this OSS engine is the math core they build on, not the full method.
Schedule risk primitives — Bayesian posterior estimation (computeBayesianUpdate); per-iteration CPM (runCPM) suitable as an inner loop for Monte Carlo wrappers built on top of this engine. Full Monte Carlo / QRAMM scoring lives in the CPP forensic skill suite (schedule-risk-analysis), built atop this primitive.
Schedule health — DCMA-14 assessment, A-F auto-grade, baseline-vs-current diff
Multi-jurisdiction calendars — 66 default holiday rule sets (CA-FED + 13 provinces/territories, US-FED + 50 states + DC). These are framework-aligned defaults sufficient for general-purpose date math — see docs/jurisdictions.md for the per-jurisdiction reference table and forensic-use verification guidance. They are not legally certified calendars; for forensic use, override with the project's contract calendar via opts.calendar.
AACE alignment
The engine implements the math behind these AACE Recommended Practices:
RP
Title
Method labels emitted
29R-03
Forensic Schedule Analysis
MIP 3.3 / 3.6 / 3.7 / 3.8
49R-06
Identifying the Critical Path
LPM, TFM, MFP
52R-06
Prospective Time Impact Analysis
MIP 3.6 (Single Base) / MIP 3.7 (Multiple Base)
122R-22
Quantitative Risk Analysis Maturity Model (QRAMM)
(badge surface)
PPG #20 (2nd Ed 2024)
Forensic Schedule Analysis Practice Guide
(general acceptance)
Method labels are emitted in result.manifest.methodology — exactly the strings AACE peer-reviewers and opposing experts expect.
Plus, for forensic provenance, every input carries a SHA-256 topology hash:
js
const hash = E.computeTopologyHash(activities, relationships);
console.log(hash.topology_hash); // 64-char hex over canonical (code, duration, sorted preds + types + lags)// Two XERs with identical hashes have IDENTICAL CANONICALIZED TOPOLOGY under the hashed-field// set (activity codes, durations, predecessor links + types + lags). NOT a forensic-equivalence// statement — different calendars, resources, WBS, names, or constraints can still produce// different schedules under the same hash. The hash is a signal, not a schedule-equivalence proof.
This is the engine's network-topology fingerprint. Bid-collusion signal, retroactive-manipulation signal, and copy-detection signal across XERs all rely on it. It is also the foundation that lets opposing counsel verify topology-level integrity of a CPP analysis post-hoc — they can recompute the hash from the same XER and confirm the activity/relationship network was not altered between submission and review.
JavaScript - Python parity
The engine has a Python sibling (_cpp_common/scripts/cpm.py) used by every CPP forensic skill. The two implementations are kept bit-identical via cross-validation:
bash
npm run crossval
# 99 fixtures, 2465 checks executed, 0 deviations, measured 2026-09-27.# A further 74 comparisons on the enumerated surface (2539 total) are skipped rather than failed by the harness field guards, all on ff_signed / ff_signed_working_days on completed activities, where NEITHER engine emits the field.
Plus a 282-activity real-XER stress test reports 0 mismatches. That XER is a single non-public reference file, is not committed, and the run is not independently reproducible from this repo (see DAUBERT.md §2).
This means a forensic analysis run in JavaScript (browser, Node) produces the same numbers as one run in Python (claims-preparation skill, MCP server, batch pipeline). Every CPP deliverable carries the same manifest regardless of which surface produced it.
Independent verification
The same-author crossval is honest about its limit: both JS and Python implementations are maintained here. To close the Daubert "no independent testing" objection, the engine ships with a one-command third-party reproduction harness:
bash
git clone https://github.com/danafitkowski/cpp-cpm-engine
cd cpp-cpm-engine
git checkout <commit-sha> # the SHA cited in the disclosure
npm run verify # runs unit + crossval + citation tests# → attestations/latest.json ← machine-readable witness file
Engine has zero npm dependencies, so reproduction requires only Node 18+ and Python 3.10+. The witness file contains:
Engine SHA-256 + Python-reference SHA-256
Commit SHA + git ref + workflow URL (in CI)
Test counts: unit-tests passed/failed, crossval fixtures + checks, citation regression status
Timestamp + Node version + platform
Verdict (PASS/FAIL)
Compare your locally-generated witness against the CI-signed witness (published on every push as a workflow artifact + Sigstore-signed via actions/attest-build-provenance). Bit-identical SHA-256s + matching pass counts on a clean clone = third-party reproduction confirmed.
Client setup for the hosted MCP server (Cline, Cursor, Claude Code, Claude Desktop) is in llms-install.md. The engine runs live at mcp.criticalpathpartners.ca — try it in your browser. The same cpm-engine.js file is served over the wire and embedded inline in every report CPP produces.
The CPP forensic suite (forensic-delay-analysis, claims-preparation, claim-workbench, time-impact-analysis, schedule-risk-analysis, collapsed-as-built, counter-claim-analysis) all consume this engine — the JS port for browser/MCP, the Python sibling for batch pipelines.
Citation
If you use this engine in academic work or expert-witness reports, please cite:
Fitkowski, D. (2026). cpm-engine: An open-source critical-path-method engine with AACE-canonical method labels and a published Daubert disclosure. Critical Path Partners. Version 2.9.49. https://github.com/danafitkowski/cpp-cpm-engine
Algorithm citations are in docs/citations.md. All citations have been verified against primary sources.
You can use this engine in commercial forensic consulting, in academic research, in your own scheduling product, in court-filed expert reports. Just keep the copyright notice. No support is implied; no warranty is provided. You are responsible for the conclusions you draw with the engine. A Daubert disclosure is built in (DAUBERT.md) — you may use it as a starting point for your own FRCP 26(a)(2)(B) report.
Release notes
v2.9.12 (2026-05-16) — Round 9 engine math fix wave. ~30 substantive math defects closed across four buckets: T1 constraint handling (MS_Start backward LF clamp, actual_start precedence over ES-side constraints per P6 forward-pass semantics, Section D Monte Carlo actual_start pinning, INFO task-dropped alerts, constraint-unrecognized / incomplete WARNs, CS_MANSTART/CS_MANFINISH aliases, Section D SNLT/FNLT/MS_Start violated+applied alerts); T2 calendar/lag arithmetic (calendar-aware Free Float on binding link, signed _countWorkDaysBetween, negative-FF preserved, dateToNum rollover guard, non-finite lag rejection, invalid-calendar-falling-back WARN, SUB_DAY_LAG_ROUNDED direction-bias disclosure); T3 in-progress + actuals (remaining_duration P6 retained-logic, backward LS=ES pin for in-progress, Section C EF>=ES guard, OoS enumerates every pred, hammock-orphan ALERT, hammock duration_working_days, unrecognized-task-type WARN); T4 Python parity (R8A-1 backport, ALAP secondary slot, forward ES gate). 792 unit tests / 416 crossval checks / verify PASS. See CHANGELOG.md for the full T1-T4 fix index.
v2.9.11 (2026-05-16) — Round 8 R8A engine math fix wave. Four T1 silent-wrong-answer paths closed: actual_finish without actual_start no longer collapses ES to EF; sub-day fractional lags emit SUB_DAY_LAG_ROUNDED ALERT; FF / SF Free Float uses the successor's calendar; Section D constraint clamps emit constraint-skipped WARN when opts.projectStart is missing.
v2.9.10 (2026-05-16) — Round 7-8 hardening. Independent-verification infrastructure (public CI on 9 OS × Node combos, Sigstore-signed witness JSONs, one-command local reproduction via npm run verify) ships as a tagged release. Engine math byte-identical to v2.9.9; that is a docs + infra release. See DAUBERT.md §3.1 and the new §10 Roadmap.
See CHANGELOG.md for the full release history through v2.9.49.
Contributing
See CONTRIBUTING.md. Forensic correctness is enforced — every commit must pass 1,345 unit tests and the cross-validation harness, in which 2465 of 2539 defined comparisons execute and are bit-identical with 0 failures across 99 fixtures (the remaining 74 are skipped rather than compared — 37 ff_signed, 37 ff_signed_working_days, all on completed activities where neither engine emits the field), plus the citation regression, truncation regression, and version-drift regression gates (all wired into npm run test:all and npm run verify). New citations require WebSearch-verified URLs. No fabricated case names. No LLM-generated narratives in core engine paths.
Companion repositories
Two companion repositories are public:
cpp-xer-parser — the canonical Primavera P6 XER parser. The engine consumes its parse output as the canonical XER → JS-object layer; cpp-xer-parser has no dependency on this engine.
cpp-critical-path-validator — critical path validation and DCMA-14 assessment. Optionally consumes this engine for the LPM cross-check; degrades gracefully when absent.
Additional CPP skills (forensic-delay-analysis, claims-preparation, claim-workbench, time-impact-analysis, collapsed-as-built, counter-claim-analysis, schedule-risk-analysis) are private; contact Critical Path Partners for access.
Strategic note
CPP is a forensic-scheduling consultancy. The engine is open-source as a deliberate posture choice: the CPM math is a decades-old peer-reviewed commodity (Kelley & Walker 1959, Kahn 1962, Tarjan 1972); the workflow, methodology discipline, and Daubert-disclosure posture are where forensic value lives. Open-sourcing the math layer means any academic, solo forensic, contractor's internal scheduler, or independent reviewer has a transparent, citable foundation to inspect, modify, or build on.
If you ship something built on this engine, we'd love to hear about it: danafitkowski@gmail.com.