pyp6xer_schedule_health_check
Calculates a composite schedule health score from 0 to 100 using data date currency, float distribution, critical path density, open ends, and constraint usage, with a narrative summary.
Instructions
Generate a composite schedule health score with narrative summary.
Combines data date currency, float distribution, critical path density, open ends, and constraint usage into a single 0–100 health score.
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:903-992 (handler)The actual handler implementation of the pyp6xer_schedule_health_check tool. It computes a composite 0-100 health score considering: data date currency (up to -20), open ends (up to -20), negative float (-15), hard constraints (-10), critical path density (-10), and schedule overrun (-15). Returns health_score, rating, issues, and recommendations.
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False)) def pyp6xer_schedule_health_check( 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: """Generate a composite schedule health score with narrative summary. Combines data date currency, float distribution, critical path density, open ends, and constraint usage into a single 0–100 health score. """ xer = _get_xer(ctx, cache_key) proj = _get_project(xer, proj_id) tasks = proj.tasks if proj_id else list(xer.tasks.values()) workable = [ t for t in tasks if not t.type.is_loe and not t.type.is_wbs and not t.status.is_completed ] n = len(workable) score = 100 issues = [] if n == 0: return json.dumps({"health_score": "N/A", "message": "No open activities found."}) # 1. Data date currency (deduct up to 20 pts if data date is old) days_since_update = (datetime.now() - proj.data_date).days if days_since_update > 90: score -= 20 issues.append(f"Data date is {days_since_update} days old (>90 days)") elif days_since_update > 30: score -= 10 issues.append(f"Data date is {days_since_update} days old (>30 days)") # 2. Open ends no_pred_pct = sum(1 for t in workable if not t.predecessors) / n * 100 no_succ_pct = sum(1 for t in workable if not t.successors) / n * 100 open_end_pct = (no_pred_pct + no_succ_pct) / 2 if open_end_pct > 10: score -= 20 issues.append(f"{open_end_pct:.1f}% open ends (predecessors or successors missing)") elif open_end_pct > 5: score -= 10 issues.append(f"{open_end_pct:.1f}% open ends") # 3. Negative float neg_float = [t for t in workable if t.total_float is not None and t.total_float < 0] if neg_float: score -= 15 issues.append(f"{len(neg_float)} activities have negative float") # 4. Hard constraints constrained_pct = sum( 1 for t in workable if t.cstr_type and t.cstr_type not in ("CS_ALAP", "CS_MSOA") ) / n * 100 if constrained_pct > 10: score -= 10 issues.append(f"{constrained_pct:.1f}% of activities have hard constraints") # 5. Critical path density critical_pct = sum(1 for t in workable if t.is_critical) / n * 100 if critical_pct > 50: score -= 10 issues.append(f"High critical path density: {critical_pct:.1f}%") # 6. Schedule overrun if proj.data_date > proj.finish_date: score -= 15 issues.append("Scheduled finish date has already passed the data date") score = max(0, score) rating = ( "Excellent" if score >= 85 else "Good" if score >= 70 else "Fair" if score >= 55 else "Poor" ) return json.dumps({ "health_score": score, "rating": rating, "open_activities": n, "data_date": _fmt_date(proj.data_date), "finish_date": _fmt_date(proj.finish_date), "days_since_update": days_since_update, "issues_found": issues, "recommendations": issues if issues else ["Schedule appears healthy."], }, indent=2) - server.py:903-908 (schema)Input schema for pyp6xer_schedule_health_check defined via Pydantic Field annotations: cache_key (default 'default'), proj_id (optional), and ctx (Context).
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False)) def pyp6xer_schedule_health_check( 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: - server.py:903-908 (registration)Registration of the tool via @mcp.tool decorator with annotations readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False.
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False)) def pyp6xer_schedule_health_check( 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: