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. For
the JSON / dict shape only (no HTML), use
``critical_path_validator`` which exposes the same DCMA-14
block.
=== 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).
debug: pipe Playwright stderr / browser console to stderr.
return_html_inline: when True (default), the generated HTML
is read off disk and returned as ``html_content`` in the
response. Required for hosted/remote use; set False to
save bandwidth when calling a local server where you can
open ``html_path`` directly.
Returns:
{
"ok": True,
"html_path": "absolute path on the server",
"html_content": "<!DOCTYPE html>..." (when return_html_inline),
"current_xer": "...",
"baseline_xer": "...",
# ── Deliverable headline — the SAME figures the HTML
# renders in its header / gauge / DCMA footer
# ("GRADE C · 69% · YELLOW"). Extracted verbatim from the
# dashboard's embedded payload; NOT recomputed here. These
# are the authoritative grade for citing the deliverable.
"grade": "C", # letter grade A-F (or None)
"health_score": 69, # gauge percent = round(PASS/SCORED*100)
"status_band": "YELLOW", # GREEN | YELLOW | RED
"headline": { # full block (None if absent)
"grade": "C", "grade_label": "Acceptable",
"health_score": 69, "health_score_exact": 68.75,
"status_band": "YELLOW",
"passed": int, "failed": int, "scored": int,
"not_scored": int,
"basis": "health_score = round(passed / scored * 100); "
"scored excludes not-scored criteria",
},
# NOTE: result["health_score"] (the gauge percent) and
# dcma_14.summary.pass_rate are now the SAME ratio on the
# SAME basis — PASS / SCORED, where SCORED excludes the
# unscored (status "NONE" / pass:null) criteria. So
# round(dcma_14.summary.pass_rate * 100) == health_score
# (e.g. 0.692 → 69), matching the HTML "69% compliance".
# (Before 2026-06-28 pass_rate divided by total-criteria —
# 9/14 = 0.643 — and silently contradicted the 9/13 = 69%
# dashboard; that is the report-safety bug this fixed.) Cite
# `health_score` / `grade` / `status_band` for the headline;
# use dcma_14.summary for the raw criterion tallies.
"dcma_14": { # ← sibling of html_content;
# matches critical_path_validator shape
"criteria": {1: {...}, 2: {...}, ...},
# Each criterion carries `scored` (bool) and a TRI-STATE
# `pass`:
# "scored": True/False — did the dashboard reach a
# PASS/FAIL/WARN verdict? False means the criterion
# was NOT evaluated (e.g. C10 Resources when the
# TASKRSRC section is absent; status "NONE").
# "pass": True — scored and PASSED
# "pass": False — scored and FAILED/WARNED
# "pass": null — NOT scored (no verdict). null is
# distinct from false: to count failed criteria,
# filter pass == False (or scored == True and not
# pass), NOT pass != True — an unscored criterion is
# not a failure. `summary.fail` already excludes it.
# `scored` = pass + fail + warn (the dashboard's
# denominator); `pass_rate` = pass / scored, NOT
# pass / total. `unscored` (status NONE) is excluded from
# `scored`. In degraded mode `not_applicable` counts the
# baseline-dependent criteria excluded from the score and
# `degraded: true` + `degraded_note` are stamped inline.
"summary": {"total": int, "scored": int, "pass": int,
"fail": int, "warn": int, "unscored": int,
"pass_rate": float | None},
},
"metrics": { # ← DEPRECATED — alias for dcma_14
# DEPRECATED. Identical payload to `dcma_14`. Retained
# for backward-compat with clients written against the
# pre-Round-4 schema. New code should read `dcma_14`.
# The `deprecated_alias_for` key is set on every
# response to make migration explicit. This key may be
# removed in a future major version.
"deprecated_alias_for": "dcma_14",
"criteria": {1: {...}, 2: {...}, ...},
"summary": { # identical payload to dcma_14.summary
"total": int, "scored": int, "pass": int,
"fail": int, "warn": int, "unscored": int,
"pass_rate": float | None,
},
}
}
On error: {"error": "..."}
Note: the inline HTML payload can be ~1.3 MB. Some MCP transport
stacks have request/response size limits (typically 5-20 MB).
For very large XERs / very long dashboards, this may fail at the
transport layer; in that case set ``return_html_inline=False``
and arrange to fetch the file from ``html_path`` separately.