pyp6xer_progress_summary
Summarize schedule progress with status breakdown, weighted percent complete, and milestone statistics to quickly assess project performance.
Instructions
Summarise schedule progress: status breakdown, percent complete, milestones.
Returns counts by status, weighted percent complete, and milestone statistics.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| cache_key | No | Cache key identifying the loaded XER file (set when calling pyp6xer_load_file) | default |
| proj_id | No | Project ID or short name; uses first project if omitted |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- server.py:1296-1353 (handler)The main handler function for the pyp6xer_progress_summary tool. It retrieves the loaded XER file from cache, computes schedule progress metrics including status breakdown, percent complete (weighted by duration and simple average), milestone statistics, and cost summary.
def pyp6xer_progress_summary( cache_key: Annotated[str, Field(description="Cache key identifying the loaded XER file (set when calling pyp6xer_load_file)")] = "default", proj_id: Annotated[str | None, Field(description="Project ID or short name; uses first project if omitted")] = None, ctx: Context = None, ) -> str: """Summarise schedule progress: status breakdown, percent complete, milestones. Returns counts by status, weighted percent complete, and milestone statistics. """ xer = _get_xer(ctx, cache_key) proj = _get_project(xer, proj_id) tasks = proj.tasks if proj_id else list(xer.tasks.values()) not_started = [t for t in tasks if t.status.is_not_started] in_progress = [t for t in tasks if t.status.is_in_progress] completed = [t for t in tasks if t.status.is_completed] milestones = [t for t in tasks if t.type.is_milestone] # Weighted percent complete by original duration total_dur = sum(t.original_duration for t in tasks if t.original_duration > 0) weighted_pct = ( sum(t.percent_complete * t.original_duration for t in tasks if t.original_duration > 0) / total_dur if total_dur else 0 ) # Simple average simple_pct = sum(t.percent_complete for t in tasks) / len(tasks) if tasks else 0 ms_completed = sum(1 for m in milestones if m.status.is_completed) ms_open = len(milestones) - ms_completed return json.dumps({ "data_date": _fmt_date(proj.data_date), "project_finish": _fmt_date(proj.finish_date), "total_activities": len(tasks), "status_breakdown": { "not_started": len(not_started), "in_progress": len(in_progress), "completed": len(completed), }, "percent_complete": { "weighted_by_duration": _fmt_pct(weighted_pct), "simple_average": _fmt_pct(simple_pct), "project_duration_pct": _fmt_pct(proj.duration_percent), "project_task_pct": _fmt_pct(proj.task_percent), }, "milestones": { "total": len(milestones), "completed": ms_completed, "remaining": ms_open, }, "cost_summary": { "budgeted": round(proj.budgeted_cost, 2), "actual": round(proj.actual_cost, 2), "remaining": round(proj.remaining_cost, 2), }, }, indent=2) - server.py:1295-1295 (registration)The tool is registered via the @mcp.tool decorator on the pyp6xer_progress_summary function. FastMCP auto-discovers the tool from the decorated function. The annotations indicate it is read-only, non-destructive, and idempotent.
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False)) - server.py:129-137 (helper)The _fmt_date helper is used to format datetime objects into YYYY-MM-DD strings for the progress summary output.
def _fmt_date(dt: datetime | None) -> str: if dt is None: return "" return dt.strftime(DATE_FMT) def _fmt_pct(v: float) -> str: return f"{v * 100:.1f}%" - server.py:135-137 (helper)The _fmt_pct helper converts a float (0-1) to a formatted percentage string like '75.0%', used in the percent_complete output.
def _fmt_pct(v: float) -> str: return f"{v * 100:.1f}%" - server.py:139-151 (helper)The _get_cache and _get_xer helper functions retrieve the cached XER file data that the progress_summary tool accesses.
def _get_cache(ctx: Context, cache_key: str) -> dict: cache = ctx.lifespan_context["cache"] if cache_key not in cache: raise ValueError( f"No file loaded with key '{cache_key}'. " "Call pyp6xer_load_file first." ) return cache[cache_key] def _get_xer(ctx: Context, cache_key: str) -> Xer: return _get_cache(ctx, cache_key)["xer"]