Read Microsoft Project and Primavera P6 files without either program. mpptool is a small, fast command-line tool written in Rust. It can:
- open
.mppfiles from Project 98 through Project 365, plus.mpxand Microsoft Project XML, - open Primavera P6
.xerexports and P6 XML files, including files that hold several projects, - print summaries, tasks, resources, assignments and calendars as tables, JSON or CSV,
- convert any of those formats to Microsoft Project XML (MSPDI), which Microsoft Project opens directly (P6 → MS Project included),
- build a new, scheduled project plan from a simple CSV, with stable activity IDs, holiday and shift calendars (10-hour days, Saturday overtime), total/free float and the critical path,
- recompute CPM (
cpm) and run a Monte Carlo schedule-risk analysis (risk: P50/P80/P90, criticality, sensitivity, threats and opportunities with optional cost, joint cost/schedule confidence) on any schedule it can read, - support lean and agile practice: Last Planner PPC and constraint log (
lps), takt plans and flowline charts (takt), WSJF portfolio ranking (wsjf) and throughput forecasting (forecast), - check a schedule (DCMA-style
check), compare versions, report variance, resource loading and boards, all on the schedule's own calendar.
It works on Windows, macOS and Linux, and it is designed so both people and AI agents can script it and improve it.
mpptool cannot write native
.mppfiles. Microsoft hasn't published the format. Instead it writes Microsoft Project XML, which Project opens with File → Open and can then save as.mpp.
- Go to the latest release and download
mpptool-vX.Y.Z-windows-x86_64.zip. - Right-click the zip → Extract All…, for example into
C:\Tools\mpptool. - Open a terminal in that folder: open the folder in File Explorer, click the address bar, type
cmd, and press Enter. Alternatively, Shift + right-click inside the folder and choose Open in Terminal. - Run it, using the full path to your file:
mpptool.exe summary "C:\Users\me\Documents\My Plan.mpp"
mpptool.exe tasks "C:\Users\me\Documents\My Plan.mpp"
mpptool.exe tasks --csv "C:\Users\me\Documents\My Plan.mpp" > tasks.csv
mpptool.exe to-xml "C:\Users\me\Documents\My Plan.mpp" "My Plan.xml"
mpptool.exe summary "C:\Users\me\Documents\Schedule.xer"
mpptool.exe to-xml "C:\Users\me\Documents\Schedule.xer" "Schedule for MS Project.xml"tasks.csv opens in Excel. My Plan.xml opens in Microsoft Project (File → Open, then choose XML Format as the file type).
The binary is not code-signed yet, so Windows SmartScreen may warn you the first time. Click More info → Run anyway. To check the download, compare certutil -hashfile mpptool-...zip SHA256 with SHA256SUMS.txt on the release page.
macOS and Linux builds are on the same release page. Extract the archive and run ./mpptool. On macOS you may need to run xattr -d com.apple.quarantine mpptool first.
# Requires Rust 1.88+ (https://rustup.rs)
cargo install --git https://github.com/cruiseliner/mpptool
# or, from a clone:
cargo build --release # binary: target/release/mpptoolmpptool summary <file> [--json] Properties, counts, start/finish, cost, % complete, critical tasks
mpptool tasks <file> [--json|--csv] Every task: ID, UID, outline level, dates, duration, %, flags,
predecessors (by visible ID and by UID), slack, constraint,
deadline, actuals, cost, baseline, notes
mpptool resources <file> [--json|--csv] Resources: type, max units, rate, email, group, calendar
mpptool assignments <file> [--json] Who is assigned to which task: units, work, cost
mpptool calendars <file> [--json] Working week and exceptions (holidays) of each calendar
mpptool projects <file> Projects in the file (Primavera files can hold several)
mpptool to-xml <file> <out.xml> [--pin-dates] Convert .mpp/.mpx/.xml to Microsoft Project XML
mpptool from-csv <in.csv> <out.xml> Schedule a new plan from CSV (calendar options: see below)
mpptool risk <in.csv> [--json] Monte Carlo finish dates (P10–P90) and criticality index
mpptool holidays --country US --years 2026-2028 List public holidays
mpptool gantt <file> <out.html|out.svg> [--critical-only] [--from D] [--to D] [--title T]
Gantt chart: offline HTML page or plain SVG
mpptool check <file> [--json] [--status-date D] [--fail-on-error]
DCMA 14-point schedule assessment
mpptool compare <old> <new> [--format text|json|html] [-o out] [--match auto|uid|activity-id|name]
Diff two versions of a plan
mpptool variance <file> [--json|--csv] [--status-date D]
Baseline vs current/actual slip per task, earned value
mpptool resources-load <file> <out.html|out.csv> Weekly hours per resource, over-allocation flagged
mpptool to-xlsx <file> <out.xlsx> Excel workbook for Excel / Smartsheet
mpptool board <file> <out.html> [--json] [--status-date D] [--title T]
Kanban board by status with RAG flags
mpptool portfolio <folder> <out.html|out.json> Dashboard of every readable plan in a folder
mpptool lps <workplan.csv> [--constraints c.csv] [--plan plan] [--json]
Last Planner PPC, reasons for variance, constraint log, make-ready
mpptool takt <trades.csv> --start D [--takt N] [--mode takt|flow] [--html out] [--plan-csv out]
Takt plan / flowline schedule and chart
mpptool wsjf <portfolio.csv> [--budget B] [--by-cd3] [--json] WSJF / CD3 ranking with funding line
mpptool forecast <history.csv> --remaining N [--by D] [--json] Throughput Monte Carlo forecast
Global options:
--locale de|fr|... Language of .mpx files (en, de, fr, it, pt, sv, zh, es, ru)
--project <id|name> Project to read from a multi-project Primavera file (default: the first one)
The file format is detected from the file's content. The .xer extension is used only as a fallback.
| Input | Reader |
|---|---|
.mpp (Project 98–365), .mpx, Microsoft Project XML |
mpxj-rs |
Primavera P6 .xer, P6 XML (APIBusinessObjects/PMXML) |
mpptool's own reader (src/read_p6.rs) |
$ mpptool tasks plan.xml
ID UID LVL Name Start Finish Dur(d) % F Predecessors (IDs)
3 3 3 Wireframes 2026-10-19 08:00 2026-10-21 17:00 3.00 0 C
4 4 3 Visual design 2026-10-22 08:00 2026-10-27 17:00 4.00 0 C 3
7 7 3 Backend 2026-10-20 08:00 2026-10-27 17:00 6.00 0 3SS+1dF shows flags: M = milestone, C = critical. Predecessors use the IDs you see in Project. The CSV and JSON output also include the stable UIDs.
mpptool summary --json plan.mpp | jq .critical_path # scripting / AI agents
mpptool tasks --csv plan.mpp > tasks.csv # open in Excel / pandas
mpptool to-xml plan.mpp plan.xml # hand to Microsoft Project, ProjectLibre, ...mpptool gantt plan.mpp plan.html # self-contained HTML (inline SVG, no external assets, works offline)
mpptool gantt plan.xer plan.svg --critical-only # plain SVG with only the critical path (and its summary rows)
mpptool gantt plan.mpp q3.html --from 2026-07-01 --to 2026-09-30 --title "Q3 look-ahead"The chart shows WBS/outline rows, task bars with % complete fill, summary brackets, milestones as diamonds,
link arrows (FS/SS/FF/SF), the critical path in red, a dashed "today" line (--today D overrides the date),
and a grey baseline ghost bar under each task that has a baseline. The time scale adapts to the span:
days (≤ 45 days), weeks (≤ 200 days), months (≤ 900 days) or quarters. Hover a bar for its dates. Very long
plans are scaled down to keep the chart under 24,000 px wide; thousands of rows render in well under a second.
mpptool check plan.mpp # table
mpptool check plan.xer --json # machine-readable: status, threshold, count, %, offending task IDs
mpptool check plan.mpp --status-date 2026-06-30 --fail-on-error # for CI: exit 1 if any check fails| # | Check | Threshold | Needs |
|---|---|---|---|
| 1 | Logic: incomplete tasks missing a predecessor or successor | ≤ 5% | – |
| 2 | Leads (negative lag) | 0 | – |
| 3 | Lags (positive lag) | ≤ 5% of relationships | – |
| 4 | Relationship types | ≥ 90% FS | – |
| 5 | Hard constraints (MSO, MFO, SNLT, FNLT) | ≤ 5% | – |
| 6 | High float (> 44 working days) | ≤ 5% | total float |
| 7 | Negative float | 0 | total float |
| 8 | High duration (> 44 working days) | ≤ 5% | – |
| 9 | Invalid dates: actuals after, or unstarted/unfinished forecasts before, the status date | 0 | status date |
| 10 | Resources: tasks with duration and no assignment | 0% | resources in the plan |
| 11 | Missed tasks: baselined to finish by the status date but finished (or forecast) late | ≤ 5% | baseline + status date |
| 12 | Critical path test (continuity, see below) | continuous | critical flags |
| 13 | CPLI = (remaining critical path length + finish float) / length | ≥ 0.95 | total float |
| 14 | BEI = completed tasks / tasks baselined to finish by the status date | ≥ 0.95 | baseline + status date |
Population: non-summary tasks; checks 1–8 and 10 use incomplete tasks, relationship checks use links into
incomplete tasks. The status date comes from the file (MS Project status date, P6 data date) or --status-date.
Checks whose data is missing are reported as skipped with the reason, never as failures. The exit code is
always 0 unless --fail-on-error is given. Notes: check 12 is a static approximation — DCMA's real test
delays a critical task and re-schedules, which needs a scheduling engine; mpptool instead verifies that the
project's finish activity is critical and traces the critical chain back from it. CPLI estimates working days
as 5/7 of calendar days.
mpptool compare plan-v1.mpp plan-v2.mpp # text: added/removed tasks, changed fields
mpptool compare old.xer new.xer --format html -o diff.html # self-contained HTML report
mpptool compare a.mpp b.mpp --format json --match name # match tasks by name instead of ID
mpptool variance plan.mpp # slip per task vs baseline + earned value
mpptool variance plan.mpp --csv --status-date 2026-06-30 > variance.csvcompare matches tasks by unique ID (Microsoft Project UIDs are stable across saves), or by Activity ID when
both files have them (Primavera, or plans from from-csv with an activity_id column). Activity IDs are read from
any MS Project custom text field whose alias is "Activity ID" (mpptool writes Text1), so they survive an MS Project
re-save even if UIDs are renumbered or the field moves; use --match activity-id to insist on it. It reports tasks added and removed, and for matched tasks changes to name,
start, finish (with the shift in working days), duration, % complete, milestone flag and predecessor links
(type and lag), plus the movement of the project finish. Every report also says how many matched tasks were keyed by
activity ID versus UID (matched 41 by activity_id, 2 by uid (fallback); JSON coverage) and warns when tasks without
an activity ID fell back to UID matching, which is how a deleted alias shows up (as a task removed and re-added).
It also flags UID-matched pairs whose names differ (case and surrounding spaces ignored), the usual sign of mis-pairing
after a re-save renumbered UIDs (JSON coverage.name_mismatches). --strict makes either finding exit with code 1
after the report is written; otherwise they are warnings and the exit code is 0.
variance lists every task with a baseline: baseline vs current dates (actual dates when the task has
them), start and finish slip in working days of the project calendar, and duration variance. When the file has baseline costs
it adds earned value at the status date (from the file or --status-date):
| Metric | Meaning |
|---|---|
| BAC | baseline cost (budget at completion) |
| PV (BCWS) | baseline cost × planned fraction at the status date (linear over the baseline's working days) |
| EV (BCWP) | baseline cost × % complete |
| AC (ACWP) | actual cost |
| SV / CV | EV − PV / EV − AC |
| SPI / CPI | EV / PV / EV / AC |
| EAC | BAC / CPI (typical) |
| EAC (atypical) | AC + (BAC − EV) |
| EAC (CPI×SPI) | AC + (BAC − EV) / (CPI × SPI) |
| TCPI | (BAC − EV) / (BAC − AC): efficiency needed to finish on budget |
| ES, SV(t), SPI(t) | earned schedule: the working day at which baseline PV equals EV; ES − AT; ES / AT |
| IEAC(t) | planned duration / SPI(t), in working days, with a forecast finish date |
Totals are summed over non-summary tasks. Primavera files carry no baselines (P6 keeps baselines as separate
projects), so variance reports "no baseline" for them; compare an older export with the current one instead.
mpptool resources-load plan.mpp load.html # heat map: resources × weeks, red = above weekly capacity
mpptool resources-load plan.xer load.csv # resource_uid,resource,week_start,hours,capacity_hours,over_allocatedEach assignment's work is spread over the working hours of the project calendar and summed per week (weeks start on Monday). Weekly capacity is the resource's max units × that week's calendar hours (40 h on the Standard calendar, less in holiday weeks). Material and cost resources are left out. Over-allocation is judged per week, not per day.
mpptool to-xlsx plan.mpp plan.xlsxThe workbook has a Tasks sheet and a Resources sheet. Tasks columns: Row, Task Name (indented by
outline level, summary rows in bold), Outline Level, WBS, Activity ID, Start, Finish,
Duration (days), % Complete, Predecessors, Assigned To, Milestone, Critical, Summary,
Baseline Start, Baseline Finish, ID, Notes. Predecessors refer to row numbers of the sheet
(row 1 = first task), Smartsheet-style: 3, 5SS, 7FS +2d, 4FF -1d. In Excel, summary rows are also
collapsible row groups, the header row is frozen and has filters.
Importing into Smartsheet
- In Smartsheet choose Import → Microsoft Excel, pick
plan.xlsx, sheet Tasks, and use row 1 as column headers. MakeTask Namethe primary column. - Open Project Settings, turn on dependencies and map Start Date →
Start, End Date →Finish, Duration →Duration (days), Predecessors →Predecessors, % Complete →% Complete. Because rows keep their order, the row-number predecessors line up with Smartsheet's row numbers. - Smartsheet's Excel import does not rebuild parent/child rows from indentation. Re-create the hierarchy
by selecting the rows of each phase and choosing Indent (sort or filter by
Outline Levelto find them), or, if you need the hierarchy and links rebuilt automatically, import Microsoft Project XML instead:mpptool to-xml plan.mpp plan.xml, then Import → Microsoft Project in Smartsheet. - With dependencies on, Smartsheet re-calculates dates from durations and predecessors using its own calendar; set the working days and holidays in Project Settings first so dates match.
mpptool board plan.mpp board.html # Not started / In progress / Done
mpptool board plan.xer --json --status-date 2026-06-30 # the cards as JSONEach non-summary task becomes a card in Not started (0%), In progress or Done (100% or an actual
finish), with its parent summary, dates, % complete, resources and a RAG flag judged at the status date
(from the file, --status-date, or today):
- Red: unfinished and past its finish date, finishing more than 5 working days later than its baseline, or negative float.
- Amber: not started although its start date has passed, 1–5 working days late against the baseline, or a critical task in progress.
- Green: everything else, including all done tasks.
mpptool portfolio ./plans portfolio.html # every .mpp/.mpx/.xml/.xer below ./plans
mpptool portfolio ./plans portfolio.jsonOne row per plan (per project for multi-project Primavera files): start, finish, % complete, finish slip
against the baseline (working days), critical task count, task count and a health score (the % of the
applicable DCMA 14-point checks that pass, as in check). A plan is red when it slips more than 10 working
days or scores below 50, amber when it slips at all or scores below 75. Files that are not readable plans
(other XML, password-protected files) are listed separately rather than stopping the run.
mpptool projects schedule.xer # list projects in the file
mpptool --project BRIDGE tasks schedule.xer # pick one by id, short name or name
mpptool --project BRIDGE to-xml schedule.xer bridge.xml # open bridge.xml in Microsoft ProjectHow P6 data is mapped:
| P6 | mpptool / Microsoft Project |
|---|---|
WBS (PROJWBS / WBS) |
Summary tasks, with outline level and dotted WBS code. Each WBS node's activities come first, then its child WBS nodes |
Activity (TASK / Activity) |
Task: name, start/finish (actual, else remaining/early, else planned), at-completion duration, actual dates, total float |
Activity ID (task_code / Id) |
activity_id field. In MS Project XML it goes to Text1, with the alias "Activity ID" |
| % complete | According to the activity's type: duration ((orig − remaining) / orig), physical, or units |
| Start / Finish milestones | Milestones (zero duration) |
| Critical | Not complete and total float ≤ 0 |
| Primary constraint | Start on → MSO, Start on or after → SNET, Start on or before → SNLT, Finish on → MFO, Finish on or after → FNET, Finish on or before → FNLT, ALAP, Mandatory start/finish → MSO/MFO |
Relationships PR_FS/SS/FF/SF |
FS/SS/FF/SF links with lag (hours; shown in days when whole) |
Calendars (clndr_data / work week + holidays) |
Calendars with working times and exceptions. P6 hh:59 end times are rounded to the next whole minute |
Resources, RSRCRATE / ResourceRate |
Resources (labor/non-labor → Work, material → Material) with their earliest rate |
| Resource assignments | Assignments with units/time, work and cost. Task cost = sum of assignment costs |
name,start,duration_days,predecessors,outline_level,resources,notes
Website Launch,,,,1,,
Wireframes,2026-10-19,3,,2,Alice,Low-fidelity first
Visual design,,4,2,2,Alice;Bob,
Backend,,6,2SS+1,2,Carol,
Go live,,0,3;4,2,,mpptool from-csv plan.csv plan.xml- activity_id (or id, optional): a stable ID such as
A1010. IDs must be unique and are written to the XML as Text1 ("Activity ID"). Use them instead of row numbers so inserting a row doesn't break links, progress updates or risk links. - predecessors: activity IDs (
A1010,A1010SS+2) or row numbers starting at 1 (#3forces a row number when IDs are numeric), separated by;. Each can have a link type (FSis the default, orSS,FF,SF) and a lag in working days, e.g.2SS+1or4FF-1d. - start (
YYYY-MM-DD, optional): saved as a Start No Earlier Than constraint. The earliest start becomes the project start. If no row has a start, the project starts today. - outline_level: a row followed by deeper rows becomes a summary task.
- duration_days:
0makes a milestone. - resources: created automatically, each at 100% units.
- notes: optional free text.
- optimistic, pessimistic (days, optional): three-point estimates used by
risk. - By default scheduling uses Microsoft Project's Standard calendar (Mon–Fri, 08:00–12:00 and 13:00–17:00, no holidays), so existing plans schedule exactly as before. mpptool computes total and free slack, writes both to the XML (
TotalSlack,FreeSlack) and flags critical tasks.
from-csv and risk take the same calendar options. Dates are rescheduled around all non-working time, and the calendar (weekly pattern plus every holiday/exception in the plan's years) is written into the MSPDI XML, so Microsoft Project shows the same calendar.
| Option | Meaning |
|---|---|
--holidays US |
Skip public holidays. US = US federal holidays (5 U.S.C. 6103) with the observed-date rule (Saturday → Friday, Sunday → Monday). Also CA, GB, AU, DE, FR, IN, MX; combine with commas (US,CA). Default none. |
--years 2026-2028 |
Only these years' holidays (default: every year the schedule spans). |
--holiday-file days.csv |
Custom exceptions, one per line: date[..date],name[,hours]. hours empty/off = non-working; 07:00-15:00 (or 06:00-10:00;10:30-16:30) = working with those hours; work = a normal working day. Custom days override holidays and the weekly pattern. |
--work-days mon-sat |
Working weekdays: mon-fri (default), mon-sat, all, mon,tue,thu, … |
--hours 06:00-11:00,11:30-16:30 |
Daily working hours (default 08:00-12:00,13:00-17:00). |
--day sat=07:00-12:00 |
Override days of the weekly pattern (repeatable): Saturday overtime, mon-thu=06:00-16:30 (4×10s), fri=off. |
--hours-per-day 8 |
Working hours in one duration_days day (default 8). Longer shifts finish work faster: a 3-day (24 h) task takes 2.4 ten-hour days. |
--calendar-name |
Calendar name in the XML (default Standard, or Project Calendar when customised). |
# US federal holidays, 10-hour weekday shifts plus Saturday overtime, a year-end shutdown
mpptool from-csv plan.csv plan.xml --holidays US --years 2026-2028 \
--hours 06:00-11:00,11:30-16:30 --day sat=07:00-12:00 --holiday-file shutdown.csv
mpptool holidays --country US --years 2026-2028 # list them (also --csv, --json, --country list)# shutdown.csv
2026-12-28..2026-12-31,Year-end shutdown
2026-11-28,Overtime Saturday,07:00-15:00Built-in holiday tables cover national/federal holidays only. Regional holidays (US state, Canadian province, German Land, Australian state), one-off proclaimed holidays and India's lunar-calendar holidays are not included: add them with --holiday-file.
mpptool cpm schedule.mpp [--json] # forward/backward pass: early dates, total & free float
mpptool risk schedule.xer --runs 10000 --seed 1 [--opt 0.8 --pess 1.5] [--json]
mpptool risk plan.csv --holidays US # CSV plans take the calendar options above
mpptool tasks --json schedule.mpp > tasks.json && mpptool risk tasks.json # simple JSON task list as inputBoth commands work on every format mpptool reads (.mpp, .mpx, MSPDI .xml, Primavera .xer and P6 XML), on from-csv plans, and on a JSON task list (the tasks --json output, or the same shape: uid, name, outline_level, duration_hours, predecessors [predecessor_uid, link_type, lag_hours], optional start, summary, percent_complete, constraint_type, constraint_date). Imported schedules are re-scheduled on their own project calendar (working week and exceptions); --holidays/--holiday-file add to it. Durations are remaining durations; finished tasks are anchored at their finish and in-progress tasks at the status date. This is mpptool's own CPM, not Project's or P6's scheduling engine: task/resource calendars, ALAP and finish constraints, resource leveling and elapsed durations are not modelled, so dates can differ from the source tool. Use it for float and risk analysis, not as a replacement for F9 in Project or F9/Schedule in P6.
Each run samples every task's duration from a PERT-beta distribution (optimistic, most likely = duration_days, pessimistic) and reschedules the network with the same links, lags, start constraints and calendar as from-csv. Optimistic/pessimistic come from the CSV columns, or default to 0.8× and 1.5× the duration. The report gives the deterministic finish, the probability of meeting it, the mean and P10/P50/P80/P90 finish dates, and each task's criticality index: the share of runs in which it was on the driving path to the finish. Tasks that are not critical in the deterministic plan but have a high criticality index are hidden schedule risk. The random generator is built in, so the same seed gives the same result on every platform.
Add an uncertainty column to the CSV (or optimistic/pessimistic columns, which win) so each task gets a distribution that matches its kind of uncertainty. Built-in classes, as (optimistic, pessimistic) multiples of the most likely duration. These are planning defaults, not industry statistics, so calibrate them on your own history and override with --class vendor=0.95,2.5:
| Class | O | P | Use for |
|---|---|---|---|
labor |
0.85 | 1.4 | crew work driven by production rates |
equipment |
0.9 | 1.5 | equipment-driven work |
design |
0.8 | 1.6 | design, engineering, reviews |
vendor |
0.95 | 2.0 | vendor-quoted lead times, fabrication, delivery |
permit |
0.9 | 2.0 | permits, AHJ reviews, work by others |
weather |
0.9 | 1.8 | weather-exposed exterior work |
fixed |
1.0 | 1.0 | concrete cure, notice periods |
Tasks without a class use --opt/--pess. Discrete risks from the RAID log go in a CSV via --risks risks.csv:
id,name,probability,min_days,likely_days,max_days,tasks,cost_min,cost_likely,cost_max
R1,Switchgear ships late,30%,5,10,20,A1050,0,5000,15000
R2,Inspector backlog (common cause),0.25,2,3,6,15;16,,,The cost columns are optional. With them, risk reports each event's expected cost, the risk-event cost distribution (mean/P50/P80/P90) and the joint P80: the share of runs that make both the P80 date and the P80 cost. Only the event costs are counted. Duration-driven cost (overtime premiums, extended general conditions, productivity loss on long shifts) is not modelled, so add it yourself.
In each run, an event fires with its probability. When it fires, one impact is sampled and added to every linked task (activity IDs or row numbers; a summary row means all of its tasks), so one cause hitting several tasks is modelled as one correlated hit. The report adds each task's sensitivity (Pearson correlation of its sampled duration with the finish, shown as a tornado list) and, for each event, its hit rate and mean finish impact (mean finish when it fires minus when it does not).
Risk events can be opportunities (time or cost saved). Add a type column (threat or opportunity) or write the
impact as negative days. Both rows below mean "saves 1 to 3 days, most likely 2":
id,name,type,probability,min_days,likely_days,max_days,tasks,cost_min,cost_likely,cost_max
O1,Prefabricated rough-in assemblies,opportunity,50%,1,2,3,A1110,0,3000,6000
O2,Early AHJ pre-inspection walk,,40%,-3,-2,-1,A1500,,,When an opportunity fires, its sampled saving shortens every linked task. A task never goes below 0 days, and tasks
in the fixed uncertainty class (concrete cure, notice periods) are never shortened. The report lists them as "fixed task(s) not
shortened". Opportunity cost columns are savings: positive numbers are read as savings, and negative numbers mean the
same thing. Impacts that straddle zero are rejected, so split them into a threat and an opportunity.
risk then reports threats and opportunities in separate sections, each with probability, hit rate, mean finish
impact and sensitivity (correlation of the event's sampled impact with the finish: positive for threats that
drive it, negative for opportunities that pull it in). An event effect block schedules every run's sampled durations
three times: with no events, with threats only, and with all events. This uses common random numbers, so the
threat effect (+x wd at P80), the opportunity effect (−y wd at P80) and the net effect aren't sampling noise.
JSON: events[].type, events[].sensitivity, events[].mean_sampled_days, events[].fixed_tasks_protected,
event_effect (uncertainty_only, with_threats, with_all_events, threat_p80_days, opportunity_p80_days,
net_p80_days, mean and cost effects). A risk file with threats only gives the same results as v0.10.1 for the same
seed.
Joint cost and schedule confidence level (JCL). --base-cost (the point estimate) and --cost-per-day (a
time-dependent burn rate, such as general conditions, field overhead and standby) give each run a total cost:
base + rate × (finish − deterministic finish, in working days) + the costs of the events that fired. This is a simple
form of the integrated cost and schedule risk analysis described in AACE RP 57R-09. The report gives the total-cost P50/P70/P80/P90, the joint
confidence at P50/P70/P80, and the JCL of meeting --target-date and --target-cost together (default: the P80
date and P80 cost). It also shows each target's probability on its own. NASA policy (NPR 7120.5F) budgets major projects at a 70% JCL.
Uncertainty in quantities and unit rates in the base estimate is not sampled, so this JCL is narrower than a full
cost-risk model.
mpptool risk plan.csv --holidays US --risks raid.csv --base-cost 2400000 --cost-per-day 6500 \
--target-date 2027-02-05 --target-cost 2450000.mpp/.xer/XML files carry no three-point estimates or classes, so risk on them uses --opt/--pess. Give a
sidecar CSV keyed by activity ID (or row, or name) to keep your uncertainty after handoff:
activity_id,uncertainty,optimistic,pessimistic
A1050,vendor,,
A1080,,4,9mpptool risk schedule.mpp --uncertainty uncertainty.csv --risks risks.csvThe report states the sidecar's coverage, so a stale sidecar (tasks added in MS Project, rows for deleted tasks) is visible rather than silent:
Uncertainty sidecar: 2 task(s) covered, 1 fell back to --opt/--pess, 1 sidecar row(s) matched no task
WARNING: sidecar does not cover 1 task(s): A30 Commission
WARNING: 1 sidecar row(s) match no task (stale?): A99
--json carries the same data in uncertainty_coverage (tasks_covered, fallback_tasks, unmatched_rows).
Rows that match no task are reported, not fatal, and the JSON lists are complete (the text report shows 10 of each).
Key rules: activity IDs and names match after trimming whitespace, case-insensitively, and nothing looser. Under an
activity_id header a key is only an activity ID (use a row column or #N for row numbers). A key that looks like a
plan ID with lost leading zeros (A50 for A0050) or a prefix stripped by Excel (50) is reported as
did you mean A0050? (not applied). Two sidecar rows that hit the same task are an error that lists the lines. An
activity ID used by several tasks in the plan is a warning, and a sidecar row keyed by it is an error (key by row).
--strict exits with code 1 after printing the report when any task fell back to --opt/--pess or any sidecar row
matched no task. Use it in scripts to stop on a stale sidecar.
mpptool from-csv plan.csv update.xml --progress progress.csv --status-date 2026-10-23
mpptool cpm plan.csv --progress progress.csv --status-date 2026-10-23
mpptool risk plan.csv --progress progress.csv --status-date 2026-10-23id,actual_start,actual_finish,remaining_days,percent_complete
3,2026-10-19,2026-10-21,,100
4,2026-10-22,,,50
7,2026-10-20,,4,id (or activity_id) is the task's activity ID when the plan has one, otherwise its row number (or use a name column). Finished tasks keep their actual dates. In-progress tasks keep their actual start, and their remaining work (remaining_days, else duration × (1 − % complete)) is scheduled after the status date. Unstarted work never starts before the status date. The XML carries actual start/finish, % complete and the status date, so check tests 9/11/14 and variance work on the update. Imported schedules (.mpp/.xer/…) bring their own actuals and status date.
from-csv --level (and cpm --level) delays tasks whose named resources are double-booked, in early-start order, but only within their total float, so the project finish never moves. Each resource is one unit, busy with one task at a time. Leveled tasks get an SNET constraint at their new start and a [leveled by mpptool] note. Conflicts with no float left are listed, not resolved: add crew, resequence, or accept a later finish. Because leveling adds SNET constraints, keep an unleveled copy of the plan for cpm/risk (constraints hide float), and say which file you hand over. This is a simple serial leveler, not Project's or P6's leveling engine (no partial units, splitting or priorities).
compare, variance, board, portfolio and the CPLI in check count working days on the schedule's own project calendar (holidays, 6-day weeks), not plain Mon–Fri. resources-load measures each week's capacity from the calendar: a 5×10 h + Saturday 5 h calendar gives 55 h per resource, and holiday weeks have less.
check --exclude-endpoints stops check 1 counting the project start milestone (no predecessors) and finish milestone (no successors). check --thresholds logic=10,lags=10,fs=85,hard=5,high_float=60,high_float_pct=5,high_duration=30,high_duration_pct=5,missed=5,cpli=0.9,bei=0.9 overrides any threshold. The defaults are DCMA's.
Weekly work plan (week, activity, responsible, committed, completed, reason) and an optional constraint log
(id, activity_id, description, category, owner, raised, need_by, resolved):
mpptool lps workplan.csv --constraints constraints.csv --status-date 2026-11-27
mpptool lps workplan.csv --constraints constraints.csv --plan plan.csv --holidays US --lookahead 6 --status-date 2026-11-27The report gives PPC (Percent Plan Complete: assignments completed as promised ÷ assignments committed, as defined by the Lean Construction
Institute) by week, overall, for the last 4 weeks and as a week-over-week trend. It also gives PPC by responsible party (lowest first) and a
reasons-for-variance Pareto (free text is grouped into the usual categories: prerequisite work, design/RFI,
submittal, materials, labor, equipment, weather, inspection/permit, space, owner change, underestimated effort).
From the constraint log it reports open, overdue and resolved constraints, the share resolved by their need-by date, the mean days to resolve,
open constraints by owner and by category, and the overdue items. With --plan (any schedule mpptool reads),
activities whose early start falls in the lookahead window are checked against open constraints (matched by activity ID or
exact name), giving the share made ready and a list of blocked activities. Rows with committed = no are left out of
PPC. Missed commitments with no reason are flagged.
mpptool takt trades.csv --start 2027-01-04 --holidays US --takt 3 --html flowline.html --plan-csv takt-plan.csv
mpptool takt trades.csv --start 2027-01-04 --mode flow --buffer 1 # continuous crews (line of balance)
mpptool from-csv takt-plan.csv takt.xml --holidays US # into MS ProjectInput is trade,zone,days (trades in sequence order and zones in flow order, as they first appear), or trade,days with
--zones 6 or --zones "L1 east,L1 west,L2". In takt mode, every trade moves through the zones at one rhythm:
duration = (trades + buffer wagons + zones − 1) × takt. Trade-zone cells whose work exceeds the takt are flagged as
overloaded, which means the zone or crew needs rebalancing. Train load is total work ÷ the capacity of the takt grid. In flow mode, each trade keeps
one continuous crew at its own pace and starts as early as it can without overtaking the previous trade in any zone
(plus --buffer days). The HTML page draws the flowline chart, with zones on the y axis and working days and dates on the x axis.
--plan-csv writes a from-csv plan with activity IDs T<trade>-Z<zone>, links and SNET starts.
mpptool wsjf portfolio.csv --budget 1700000
mpptool wsjf portfolio.csv --by-cd3 --jsonColumns: id, name, business_value, time_criticality, risk_reduction, job_size (relative scores on the SAFe modified
Fibonacci scale, so scores off the scale are noted), with optional cost, cod_per_week and duration_weeks. WSJF = (BV + TC +
RR/OE) ÷ job size. Ties go to the smaller job. With cost and --budget, items are funded in rank order and the
cut line is shown. With cod_per_week and duration_weeks (Reinertsen's economic cost of delay), --by-cd3 ranks by
CD3 = CoD ÷ duration, and the report compares the total cost of delay of the ranked order against the input order.
mpptool forecast closed-per-week.csv --remaining 60 --start 2026-10-19 --split 1,1.2 --by 2026-12-31
mpptool forecast finished-dates.csv --period day --holidays US --remaining 40Monte Carlo "when will it be done?" from throughput history, for any countable work: stories, punch-list items,
submittals reviewed, RFIs closed. Each run resamples past periods' completions until the remaining count, multiplied by a
sampled scope-growth/split factor, is done. The output gives the dates at 50/70/85/95% confidence, and with --by it also
gives how many items will be done by that date at each confidence level. History is a completed column per period, or one finished date per item (binned,
with empty periods counted as zero). --last N uses recent history only. Fewer than 10 samples gives a warning.
| Carried over | Not carried over (yet) |
|---|---|
| Tasks, outline, WBS, start/finish/duration, % complete, milestones, summaries, manual scheduling, blank rows | Custom fields / extended attributes |
| Dependencies (FS/SS/FF/SF, lag) | Baselines 1–10 (baseline 0 is kept) |
| Constraints, deadlines, actual start/finish | Timephased data, splits, recurring task definitions |
| Notes (plain text; rich formatting is lost) | Views, tables, filters, formatting |
| Costs: task cost, fixed cost, actual cost; resource rate, cost per use; assignment cost | Overtime rates, cost rate tables B–E |
| Calendars: base and resource calendars, working times, exceptions (recurring exceptions are expanded into individual dates) | Work weeks (alternate working periods) |
| Resources (work/material/cost), assignments (units, work) | Resource availability tables, leveling data |
Before release, every one of 606 sample .mpp files from MPXJ's test suite converted to well-formed XML that reads back with identical tasks, links, constraints, deadlines, actuals, notes and calendar exceptions. All 12 Primavera samples in MPXJ's suite (7 XER, 5 P6 XML) also convert and read back identically. The XML output has not yet been verified by opening it in real Microsoft Project (neither the converted files nor the calendars, holidays and float written by from-csv). Before importing, read the XML back with mpptool tasks out.xml (and mpptool calendars out.xml) to confirm tasks, links, dates and calendar exceptions. Please report any file that Project rejects.
Primavera P6: mpptool reads XER and P6 XML but has no XER writer yet. To get a plan into P6, import the Microsoft Project XML that mpptool writes (P6's import of MSP XML varies by version, so verify the result on your P6 version).
- No native
.mppoutput; only MSPDI XML. - Password-protected
.mppfiles cannot be read. - Asta, Phoenix, Planner and other formats are not supported.
- Primavera: these are not mapped: secondary constraints, activity codes, UDFs, steps, expenses, baselines (P6 baselines live in separate projects), resource curves, and level-of-effort / WBS-summary semantics (both are read as ordinary tasks). Durations are at-completion hours, not recalculated against calendars. Relationships to activities in other projects are dropped. XER from P6 versions older than 6 may use an older calendar format that is not parsed; those calendars fall back to Mon–Fri 08:00–17:00.
- The
from-csvscheduler is intentionally simple. It uses one project calendar (no task or resource calendars), has no resource leveling, and only supports SNET constraints. Overtime is modelled as extra working time on the calendar; separate overtime work and overtime cost rates are not written. cpm/riskon imported files use only the project calendar and SNET/MSO constraints (see above).risksamples task durations independently (risk events give common-cause correlation; there is no general correlation matrix). The JCL cost model is base + time-dependent rate + event costs; base-estimate uncertainty is not sampled.lps,takt,wsjfandforecastare planning aids over CSV data; they don't replace pull-planning sessions, a takt production system, a portfolio governance process or a team's own flow metrics tool.- Duration months are converted as 20 working days.
checktest 12 (critical path test) is a static continuity check, not DCMA's delay-and-reschedule test.compare/variancecount slip on the project calendar (no task calendars); PV and earned schedule assume work spread evenly over each baseline, not the timephased baseline.resources-loadspreads work evenly (no work contours, timephased data or per-resource calendars).boardandportfolioRAG rules are fixed (documented above), not configurable yet.- The readers are young (2026) ports of MPXJ, so expect rough edges on unusual files.
- Check XML in Microsoft Project automatically, or against a corrected MSPDI schema
- Custom fields, baselines 1–10, work weeks, timephased data in
to-xml -
--format jsonexport of the whole project in one document - Gantt charts (
gantt) and DCMA 14-point check (check) — v0.3.0 - Diff two schedules (
compare) and baseline variance / earned value (variance) — v0.4.0 - Resource loading (
resources-load) and Excel/Smartsheet export (to-xlsx) — v0.5.0 - Kanban board (
board) and portfolio dashboard (portfolio) — v0.6.0 - Holiday calendars, weekend/overtime work patterns, float in XML, Monte Carlo
risk— v0.7.0 - Risk events, uncertainty classes, sensitivity, progress updates, leveling within float, DCMA options — v0.8.0
- Opportunities, threat/opportunity effect, JCL with time-dependent cost; Last Planner (
lps), takt/flowline (takt), WSJF (wsjf), throughput forecast (forecast) — v0.11.0 - XER writer (next: TASK/TASKPRED/CALENDAR/PROJWBS tables)
- Earned value with timephased baselines; EVMS (EIA-748-E) guideline checks
- Cost-estimate uncertainty (quantity × unit-rate ranges) in the JCL; correlation between task durations
- 4D/VDC export: activity-ID ↔ model-element link table (IFC GUIDs) for Navisworks/Synchro
- Reference-class uplift (
risk --uplift) from a user-supplied reference class - Portfolio dashboards that include PPC, JCL and benefits/OKR fields
- Holiday-aware
compare/varianceslip; regional holiday tables; correlation between task durations - Primavera: activity codes/UDFs as custom fields, baselines, expenses, cross-project links, writing XER
- Code-signed Windows binaries; winget / Homebrew packages
Contributions are welcome. See CONTRIBUTING.md. AI coding agents should read AGENTS.md.
mpptool's own code is dual-licensed under MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.
mpptool depends on mpxj-rs, which is licensed under LGPL-2.1-or-later. Binaries built from this repository therefore include LGPL code. See THIRD-PARTY-NOTICES.md for what that means and how to relink. mpptool is an independent project, not affiliated with Microsoft or with the MPXJ project.