Scrum Master Expert
Sprint planning, agile ceremonies facilitation, backlog management, and velocity tracking — an expert Scrum Master in your workflow.
What this skill does
Gain an expert partner for planning work cycles, tracking progress, and improving overall productivity. You will receive reliable delivery forecasts and health reports that highlight blockers and suggest concrete improvements for your group. Use this whenever you need to facilitate meetings, manage task lists, or understand how quickly your team can realistically deliver results.
name: “scrum-master” description: “Advanced Scrum Master skill for data-driven agile team analysis and coaching. Use when the user asks about sprint planning, velocity tracking, retrospectives, standup facilitation, backlog grooming, story points, burndown charts, blocker resolution, or agile team health. Runs Python scripts to analyse sprint JSON exports from Jira or similar tools: velocity_analyzer.py for Monte Carlo sprint forecasting, sprint_health_scorer.py for multi-dimension health scoring, and retrospective_analyzer.py for action-item and theme tracking. Produces confidence-interval forecasts, health grade reports, and improvement-velocity trends for high-performing Scrum teams.” license: MIT metadata: version: 2.0.0 author: Alireza Rezvani category: project-management domain: agile-development updated: 2026-02-15 python-tools: velocity_analyzer.py, sprint_health_scorer.py, retrospective_analyzer.py tech-stack: scrum, agile-coaching, team-dynamics, data-analysis
Scrum Master Expert
Data-driven Scrum Master skill combining sprint analytics, probabilistic forecasting, and team development coaching. The unique value is in the three Python analysis scripts and their workflows — refer to references/ and assets/ for deeper framework detail.
Table of Contents
- Analysis Tools & Usage
- Input Requirements
- Sprint Execution Workflows
- Team Development Workflow
- Key Metrics & Targets
- Limitations
Analysis Tools & Usage
1. Velocity Analyzer (scripts/velocity_analyzer.py)
Runs rolling averages, linear-regression trend detection, and Monte Carlo simulation over sprint history.
# Text report
python velocity_analyzer.py sprint_data.json --format text
# JSON output for downstream processing
python velocity_analyzer.py sprint_data.json --format json > analysis.json
Outputs: velocity trend (improving/stable/declining), coefficient of variation, 6-sprint Monte Carlo forecast at 50 / 70 / 85 / 95% confidence intervals, anomaly flags with root-cause suggestions.
Validation: If fewer than 3 sprints are present in the input, stop and prompt the user: “Velocity analysis needs at least 3 sprints. Please provide additional sprint data.” 6+ sprints are recommended for statistically significant Monte Carlo results.
2. Sprint Health Scorer (scripts/sprint_health_scorer.py)
Scores team health across 6 weighted dimensions, producing an overall 0–100 grade.
| Dimension | Weight | Target |
|---|---|---|
| Commitment Reliability | 25% | >85% sprint goals met |
| Scope Stability | 20% | <15% mid-sprint changes |
| Blocker Resolution | 15% | <3 days average |
| Ceremony Engagement | 15% | >90% participation |
| Story Completion Distribution | 15% | High ratio of fully done stories |
| Velocity Predictability | 10% | CV <20% |
python sprint_health_scorer.py sprint_data.json --format text
Outputs: overall health score + grade, per-dimension scores with recommendations, sprint-over-sprint trend, intervention priority matrix.
Validation: Requires 2+ sprints with ceremony and story-completion data. If data is missing, report which dimensions cannot be scored and ask the user to supply the gaps.
3. Retrospective Analyzer (scripts/retrospective_analyzer.py)
Tracks action-item completion, recurring themes, sentiment trends, and team maturity progression.
python retrospective_analyzer.py sprint_data.json --format text
Outputs: action-item completion rate by priority/owner, recurring-theme persistence scores, team maturity level (forming/storming/norming/performing), improvement-velocity trend.
Validation: Requires 3+ retrospectives with action-item tracking. With fewer, note the limitation and offer partial theme analysis only.
Input Requirements
All scripts accept JSON following the schema in assets/sample_sprint_data.json:
{
"team_info": { "name": "string", "size": "number", "scrum_master": "string" },
"sprints": [
{
"sprint_number": "number",
"planned_points": "number",
"completed_points": "number",
"stories": [...],
"blockers": [...],
"ceremonies": {...}
}
],
"retrospectives": [
{
"sprint_number": "number",
"went_well": ["string"],
"to_improve": ["string"],
"action_items": [...]
}
]
}
Jira and similar tools can export sprint data; map exported fields to this schema before running the scripts. See assets/sample_sprint_data.json for a complete 6-sprint example and assets/expected_output.json for corresponding expected results (velocity avg 20.2 pts, CV 12.7%, health score 78.3/100, action-item completion 46.7%).
Sprint Execution Workflows
Sprint Planning
- Run velocity analysis:
python velocity_analyzer.py sprint_data.json --format text - Use the 70% confidence interval as the recommended commitment ceiling for the sprint backlog.
- Review the health scorer’s Commitment Reliability and Scope Stability scores to calibrate negotiation with the Product Owner.
- If Monte Carlo output shows high volatility (CV >20%), surface this to stakeholders with range estimates rather than single-point forecasts.
- Document capacity assumptions (leave, dependencies) for retrospective comparison.
Daily Standup
- Track participation and help-seeking patterns — feed ceremony data into
sprint_health_scorer.pyat sprint end. - Log each blocker with date opened; resolution time feeds the Blocker Resolution dimension.
- If a blocker is unresolved after 2 days, escalate proactively and note in sprint data.
Sprint Review
- Present velocity trend and health score alongside the demo to give stakeholders delivery context.
- Capture scope-change requests raised during review; record as scope-change events in sprint data for next scoring cycle.
Sprint Retrospective
- Run all three scripts before the session:
python sprint_health_scorer.py sprint_data.json --format text > health.txt python retrospective_analyzer.py sprint_data.json --format text > retro.txt - Open with the health score and top-flagged dimensions to focus discussion.
- Use the retrospective analyzer’s action-item completion rate to determine how many new action items the team can realistically absorb (target: ≤3 if completion rate <60%).
- Assign each action item an owner and measurable success criterion before closing the session.
- Record new action items in
sprint_data.jsonfor tracking in the next cycle.
Team Development Workflow
Assessment
python sprint_health_scorer.py team_data.json > health_assessment.txt
python retrospective_analyzer.py team_data.json > retro_insights.txt
- Map retrospective analyzer maturity output to the appropriate development stage.
- Supplement with an anonymous psychological safety pulse survey (Edmondson 7-point scale) and individual 1:1 observations.
- If maturity output is
formingorstorming, prioritise safety and conflict-facilitation interventions before process optimisation.
Intervention
Apply stage-specific facilitation (details in references/team-dynamics-framework.md):
| Stage | Focus |
|---|---|
| Forming | Structure, process education, trust building |
| Storming | Conflict facilitation, psychological safety maintenance |
| Norming | Autonomy building, process ownership transfer |
| Performing | Challenge introduction, innovation support |
Progress Measurement
- Sprint cadence: re-run health scorer; target overall score improvement of ≥5 points per quarter.
- Monthly: psychological safety pulse survey; target >4.0/5.0.
- Quarterly: full maturity re-assessment via retrospective analyzer.
- If scores plateau or regress for 2 consecutive sprints, escalate intervention strategy (see
references/team-dynamics-framework.md).
Key Metrics & Targets
| Metric | Target |
|---|---|
| Overall Health Score | >80/100 |
| Psychological Safety Index | >4.0/5.0 |
| Velocity CV (predictability) | <20% |
| Commitment Reliability | >85% |
| Scope Stability | <15% mid-sprint changes |
| Blocker Resolution Time | <3 days |
| Ceremony Engagement | >90% |
| Retrospective Action Completion | >70% |
Limitations
- Sample size: fewer than 6 sprints reduces Monte Carlo confidence; always state confidence intervals, not point estimates.
- Data completeness: missing ceremony or story-completion fields suppress affected scoring dimensions — report gaps explicitly.
- Context sensitivity: script recommendations must be interpreted alongside organisational and team context not captured in JSON data.
- Quantitative bias: metrics do not replace qualitative observation; combine scores with direct team interaction.
- Team size: techniques are optimised for 5–9 member teams; larger groups may require adaptation.
- External factors: cross-team dependencies and organisational constraints are not fully modelled by single-team metrics.
Related Skills
- Agile Product Owner (
product-team/agile-product-owner/) — User stories and backlog feed sprint planning - Senior PM (
project-management/senior-pm/) — Portfolio health context informs sprint priorities
For deep framework references see references/velocity-forecasting-guide.md and references/team-dynamics-framework.md. For template assets see assets/sprint_report_template.md and assets/team_health_check_template.md.
{
"velocity_analysis": {
"summary": {
"total_sprints": 6,
"velocity_stats": {
"mean": 20.17,
"median": 20.0,
"min": 17,
"max": 24,
"total_points": 121
},
"commitment_analysis": {
"average_commitment_ratio": 0.908,
"commitment_consistency": 0.179,
"sprints_under_committed": 3,
"sprints_over_committed": 2
},
"volatility": {
"volatility": "low",
"coefficient_of_variation": 0.127
}
},
"trend_analysis": {
"trend": "stable",
"confidence": 0.15,
"relative_slope": -0.013
},
"forecasting": {
"expected_total": 121.0,
"forecasted_totals": {
"50%": 115,
"70%": 125,
"85%": 135,
"95%": 148
}
},
"anomalies": [
{
"sprint_number": 5,
"velocity": 17,
"anomaly_type": "outlier",
"deviation_percentage": -15.7
}
]
},
"sprint_health": {
"overall_score": 78.3,
"health_grade": "good",
"dimension_scores": {
"commitment_reliability": {
"score": 96.8,
"grade": "excellent"
},
"scope_stability": {
"score": 54.8,
"grade": "poor"
},
"blocker_resolution": {
"score": 51.7,
"grade": "poor"
},
"ceremony_engagement": {
"score": 92.3,
"grade": "excellent"
},
"story_completion_distribution": {
"score": 93.3,
"grade": "excellent"
},
"velocity_predictability": {
"score": 80.5,
"grade": "good"
}
}
},
"retrospective_analysis": {
"summary": {
"total_retrospectives": 6,
"average_duration": 74,
"average_attendance": 0.933
},
"action_item_analysis": {
"total_action_items": 15,
"completion_rate": 0.467,
"overdue_rate": 0.533,
"priority_analysis": {
"high": {"completion_rate": 0.50},
"medium": {"completion_rate": 0.33},
"low": {"completion_rate": 0.67}
}
},
"theme_analysis": {
"recurring_themes": {
"process": {"frequency": 1.0, "trend": {"direction": "decreasing"}},
"team_dynamics": {"frequency": 1.0, "trend": {"direction": "increasing"}},
"technical": {"frequency": 0.83, "trend": {"direction": "increasing"}},
"communication": {"frequency": 0.67, "trend": {"direction": "decreasing"}}
}
},
"improvement_trends": {
"team_maturity_score": {
"score": 75.6,
"level": "performing"
},
"improvement_velocity": {
"velocity": "moderate",
"velocity_score": 0.62
}
}
},
"interpretation": {
"strengths": [
"Excellent commitment reliability - team consistently delivers what they commit to",
"High ceremony engagement - team actively participates in scrum events",
"Good story completion distribution - stories are finished rather than left partially done",
"Low velocity volatility - predictable delivery capability"
],
"areas_for_improvement": [
"Scope instability - too much mid-sprint change (22.6% average)",
"Blocker resolution time - 4.7 days average is too long",
"Action item completion rate - only 46.7% completed",
"High overdue rate - 53.3% of action items become overdue"
],
"recommended_actions": [
"Strengthen backlog refinement to reduce scope changes",
"Implement faster blocker escalation process",
"Reduce number of retrospective action items and focus on follow-through",
"Create external dependency register to proactively manage blockers"
]
}
} {
"summary": {
"total_sprints": 6,
"velocity_stats": {
"mean": 20.166666666666668,
"median": 20.0,
"min": 17,
"max": 24,
"total_points": 121
},
"commitment_analysis": {
"average_commitment_ratio": 0.9075307422046552,
"commitment_consistency": 0.17889820455801825,
"sprints_under_committed": 3,
"sprints_over_committed": 2
},
"scope_change_analysis": {
"average_scope_change": 0.22586752619361317,
"scope_change_volatility": 0.1828476660567787
},
"rolling_averages": {
"3": [
null,
null,
19.333333333333332,
20.666666666666668,
19.333333333333332,
21.0
],
"5": [
null,
null,
19.333333333333332,
20.0,
19.4,
20.6
],
"8": [
null,
null,
19.333333333333332,
20.0,
19.4,
20.166666666666668
]
},
"volatility": {
"volatility": "low",
"coefficient_of_variation": 0.13088153980052333,
"standard_deviation": 2.6394443859772205,
"mean_velocity": 20.166666666666668,
"velocity_range": 7,
"range_ratio": 0.3471074380165289,
"min_velocity": 17,
"max_velocity": 24
}
},
"trend_analysis": {
"trend": "stable",
"slope": 0.6,
"relative_slope": 0.029752066115702476,
"correlation": 0.42527784332026836,
"confidence": 0.42527784332026836,
"recent_sprints_analyzed": 6,
"average_velocity": 20.166666666666668
},
"forecasting": {
"sprints_ahead": 6,
"historical_sprints_used": 6,
"mean_velocity": 20.166666666666668,
"velocity_std_dev": 2.6394443859772205,
"forecasted_totals": {
"50%": 121.00756172377734,
"70%": 124.35398229685968,
"85%": 127.68925669583572,
"95%": 131.66775744677182
},
"average_per_sprint": 20.166666666666668,
"expected_total": 121.0
},
"anomalies": [],
"recommendations": [
"Good velocity stability. Continue current practices."
]
}
{
"team_info": {
"name": "Phoenix Development Team",
"size": 5,
"scrum_master": "Sarah Chen",
"product_owner": "Mike Rodriguez"
},
"sprints": [
{
"sprint_number": 1,
"sprint_name": "Sprint Alpha",
"start_date": "2024-01-08",
"end_date": "2024-01-19",
"planned_points": 23,
"completed_points": 18,
"added_points": 3,
"removed_points": 2,
"carry_over_points": 5,
"team_capacity": 40,
"working_days": 10,
"team_size": 5,
"stories": [
{
"id": "US-101",
"title": "User authentication system",
"points": 8,
"status": "completed",
"assigned_to": "John Doe",
"created_date": "2024-01-08",
"completed_date": "2024-01-16",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-102",
"title": "Dashboard layout implementation",
"points": 5,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-01-08",
"completed_date": "2024-01-18",
"blocked_days": 1,
"priority": "medium"
},
{
"id": "US-103",
"title": "API integration for user data",
"points": 5,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-01-08",
"completed_date": "2024-01-19",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-104",
"title": "Advanced filtering options",
"points": 5,
"status": "in_progress",
"assigned_to": "Alice Brown",
"created_date": "2024-01-08",
"blocked_days": 2,
"priority": "low"
}
],
"blockers": [
{
"id": "B-001",
"description": "Third-party API documentation incomplete",
"created_date": "2024-01-10",
"resolved_date": "2024-01-12",
"resolution_days": 2,
"affected_stories": ["US-103"],
"category": "external"
}
],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.92,
"engagement_score": 0.85
},
"sprint_planning": {
"attendance_rate": 1.0,
"engagement_score": 0.90
},
"sprint_review": {
"attendance_rate": 0.96,
"engagement_score": 0.88
},
"retrospective": {
"attendance_rate": 1.0,
"engagement_score": 0.95
}
}
},
{
"sprint_number": 2,
"sprint_name": "Sprint Beta",
"start_date": "2024-01-22",
"end_date": "2024-02-02",
"planned_points": 21,
"completed_points": 21,
"added_points": 1,
"removed_points": 1,
"carry_over_points": 3,
"team_capacity": 38,
"working_days": 9,
"team_size": 5,
"stories": [
{
"id": "US-105",
"title": "Email notification system",
"points": 8,
"status": "completed",
"assigned_to": "John Doe",
"created_date": "2024-01-22",
"completed_date": "2024-01-30",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-106",
"title": "User profile management",
"points": 5,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-01-22",
"completed_date": "2024-02-01",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-107",
"title": "Data export functionality",
"points": 3,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-01-22",
"completed_date": "2024-01-31",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-104",
"title": "Advanced filtering options",
"points": 5,
"status": "completed",
"assigned_to": "Alice Brown",
"created_date": "2024-01-08",
"completed_date": "2024-02-02",
"blocked_days": 0,
"priority": "low"
}
],
"blockers": [],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.94,
"engagement_score": 0.88
},
"sprint_planning": {
"attendance_rate": 1.0,
"engagement_score": 0.92
},
"sprint_review": {
"attendance_rate": 1.0,
"engagement_score": 0.90
},
"retrospective": {
"attendance_rate": 1.0,
"engagement_score": 0.93
}
}
},
{
"sprint_number": 3,
"sprint_name": "Sprint Gamma",
"start_date": "2024-02-05",
"end_date": "2024-02-16",
"planned_points": 24,
"completed_points": 19,
"added_points": 4,
"removed_points": 3,
"carry_over_points": 5,
"team_capacity": 42,
"working_days": 10,
"team_size": 5,
"stories": [
{
"id": "US-108",
"title": "Real-time chat implementation",
"points": 13,
"status": "in_progress",
"assigned_to": "John Doe",
"created_date": "2024-02-05",
"blocked_days": 3,
"priority": "high"
},
{
"id": "US-109",
"title": "Mobile responsive design",
"points": 8,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-02-05",
"completed_date": "2024-02-14",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-110",
"title": "Performance optimization",
"points": 3,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-02-05",
"completed_date": "2024-02-13",
"blocked_days": 1,
"priority": "medium"
}
],
"blockers": [
{
"id": "B-002",
"description": "WebSocket library compatibility issue",
"created_date": "2024-02-07",
"resolved_date": "2024-02-11",
"resolution_days": 4,
"affected_stories": ["US-108"],
"category": "technical"
},
{
"id": "B-003",
"description": "Database migration pending approval",
"created_date": "2024-02-09",
"resolution_days": 0,
"affected_stories": ["US-110"],
"category": "process"
}
],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.88,
"engagement_score": 0.82
},
"sprint_planning": {
"attendance_rate": 0.96,
"engagement_score": 0.85
},
"sprint_review": {
"attendance_rate": 0.92,
"engagement_score": 0.83
},
"retrospective": {
"attendance_rate": 1.0,
"engagement_score": 0.87
}
}
},
{
"sprint_number": 4,
"sprint_name": "Sprint Delta",
"start_date": "2024-02-19",
"end_date": "2024-03-01",
"planned_points": 20,
"completed_points": 22,
"added_points": 2,
"removed_points": 0,
"carry_over_points": 2,
"team_capacity": 40,
"working_days": 10,
"team_size": 5,
"stories": [
{
"id": "US-108",
"title": "Real-time chat implementation",
"points": 13,
"status": "completed",
"assigned_to": "John Doe",
"created_date": "2024-02-05",
"completed_date": "2024-02-28",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-111",
"title": "Search functionality enhancement",
"points": 5,
"status": "completed",
"assigned_to": "Alice Brown",
"created_date": "2024-02-19",
"completed_date": "2024-02-26",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-112",
"title": "Unit test coverage improvement",
"points": 3,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-02-19",
"completed_date": "2024-02-27",
"blocked_days": 0,
"priority": "low"
},
{
"id": "US-113",
"title": "Error handling improvements",
"points": 1,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-02-25",
"completed_date": "2024-03-01",
"blocked_days": 0,
"priority": "medium"
}
],
"blockers": [],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.96,
"engagement_score": 0.90
},
"sprint_planning": {
"attendance_rate": 1.0,
"engagement_score": 0.94
},
"sprint_review": {
"attendance_rate": 1.0,
"engagement_score": 0.92
},
"retrospective": {
"attendance_rate": 1.0,
"engagement_score": 0.95
}
}
},
{
"sprint_number": 5,
"sprint_name": "Sprint Epsilon",
"start_date": "2024-03-04",
"end_date": "2024-03-15",
"planned_points": 25,
"completed_points": 17,
"added_points": 6,
"removed_points": 8,
"carry_over_points": 8,
"team_capacity": 35,
"working_days": 9,
"team_size": 4,
"stories": [
{
"id": "US-114",
"title": "Advanced analytics dashboard",
"points": 13,
"status": "blocked",
"assigned_to": "John Doe",
"created_date": "2024-03-04",
"blocked_days": 7,
"priority": "high"
},
{
"id": "US-115",
"title": "User permissions system",
"points": 8,
"status": "in_progress",
"assigned_to": "Alice Brown",
"created_date": "2024-03-04",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-116",
"title": "API rate limiting",
"points": 2,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-03-04",
"completed_date": "2024-03-08",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-117",
"title": "Documentation updates",
"points": 2,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-03-04",
"completed_date": "2024-03-10",
"blocked_days": 0,
"priority": "low"
}
],
"blockers": [
{
"id": "B-004",
"description": "Analytics service downtime",
"created_date": "2024-03-05",
"resolution_days": 0,
"affected_stories": ["US-114"],
"category": "external"
},
{
"id": "B-005",
"description": "Team member on sick leave",
"created_date": "2024-03-07",
"resolved_date": "2024-03-15",
"resolution_days": 8,
"affected_stories": ["US-115"],
"category": "team"
}
],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.75,
"engagement_score": 0.70
},
"sprint_planning": {
"attendance_rate": 0.80,
"engagement_score": 0.75
},
"sprint_review": {
"attendance_rate": 0.85,
"engagement_score": 0.78
},
"retrospective": {
"attendance_rate": 0.95,
"engagement_score": 0.88
}
}
},
{
"sprint_number": 6,
"sprint_name": "Sprint Zeta",
"start_date": "2024-03-18",
"end_date": "2024-03-29",
"planned_points": 22,
"completed_points": 24,
"added_points": 2,
"removed_points": 0,
"carry_over_points": 6,
"team_capacity": 45,
"working_days": 10,
"team_size": 5,
"stories": [
{
"id": "US-115",
"title": "User permissions system",
"points": 8,
"status": "completed",
"assigned_to": "Alice Brown",
"created_date": "2024-03-04",
"completed_date": "2024-03-25",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-118",
"title": "Backup and recovery system",
"points": 8,
"status": "completed",
"assigned_to": "John Doe",
"created_date": "2024-03-18",
"completed_date": "2024-03-28",
"blocked_days": 0,
"priority": "high"
},
{
"id": "US-119",
"title": "UI theme customization",
"points": 5,
"status": "completed",
"assigned_to": "Jane Smith",
"created_date": "2024-03-18",
"completed_date": "2024-03-26",
"blocked_days": 0,
"priority": "medium"
},
{
"id": "US-120",
"title": "Performance monitoring",
"points": 3,
"status": "completed",
"assigned_to": "Bob Wilson",
"created_date": "2024-03-18",
"completed_date": "2024-03-24",
"blocked_days": 0,
"priority": "low"
}
],
"blockers": [],
"ceremonies": {
"daily_standup": {
"attendance_rate": 0.98,
"engagement_score": 0.93
},
"sprint_planning": {
"attendance_rate": 1.0,
"engagement_score": 0.96
},
"sprint_review": {
"attendance_rate": 1.0,
"engagement_score": 0.94
},
"retrospective": {
"attendance_rate": 1.0,
"engagement_score": 0.97
}
}
}
],
"retrospectives": [
{
"sprint_number": 1,
"date": "2024-01-19",
"facilitator": "Sarah Chen",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown", "Sarah Chen"],
"duration_minutes": 75,
"went_well": [
"Team collaboration was excellent during planning",
"Daily standups were efficient and focused",
"Good technical problem-solving on authentication system",
"New team member integrated well",
"Clear user story definitions"
],
"to_improve": [
"Story estimation accuracy needs work",
"Too many blockers appeared mid-sprint",
"API documentation was incomplete at start",
"Need better communication with external teams"
],
"action_items": [
{
"id": "AI-001",
"description": "Schedule estimation workshop for next sprint planning",
"owner": "Sarah Chen",
"priority": "high",
"due_date": "2024-01-26",
"status": "completed",
"created_sprint": 1,
"completed_sprint": 2,
"category": "process",
"effort_estimate": "medium"
},
{
"id": "AI-002",
"description": "Establish direct communication channel with API team",
"owner": "Bob Wilson",
"priority": "medium",
"due_date": "2024-01-30",
"status": "completed",
"created_sprint": 1,
"completed_sprint": 2,
"category": "communication",
"effort_estimate": "low"
},
{
"id": "AI-003",
"description": "Create blocker escalation process documentation",
"owner": "Sarah Chen",
"priority": "medium",
"due_date": "2024-02-02",
"status": "in_progress",
"created_sprint": 1,
"category": "process",
"effort_estimate": "low"
}
]
},
{
"sprint_number": 2,
"date": "2024-02-02",
"facilitator": "Sarah Chen",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown", "Sarah Chen"],
"duration_minutes": 60,
"went_well": [
"Perfect sprint execution - completed all planned work",
"No blockers encountered",
"Estimation workshop improved accuracy significantly",
"Team velocity is stabilizing",
"Good ceremony attendance and engagement"
],
"to_improve": [
"Could have taken on more work given the smooth execution",
"Need to celebrate successes more",
"Sprint review could be more interactive",
"Documentation still lagging behind development"
],
"action_items": [
{
"id": "AI-004",
"description": "Implement team celebration ritual for successful sprints",
"owner": "Jane Smith",
"priority": "low",
"due_date": "2024-02-09",
"status": "completed",
"created_sprint": 2,
"completed_sprint": 3,
"category": "team_dynamics",
"effort_estimate": "low"
},
{
"id": "AI-005",
"description": "Create documentation sprint for next iteration",
"owner": "Alice Brown",
"priority": "medium",
"due_date": "2024-02-16",
"status": "cancelled",
"created_sprint": 2,
"category": "process",
"effort_estimate": "high"
}
]
},
{
"sprint_number": 3,
"date": "2024-02-16",
"facilitator": "John Doe",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown"],
"duration_minutes": 90,
"went_well": [
"Good adaptation when faced with technical challenges",
"Team helped each other overcome blockers",
"Mobile design work exceeded expectations",
"Performance improvements had measurable impact"
],
"to_improve": [
"WebSocket integration took longer than expected",
"Too much scope change during the sprint",
"Daily standup attendance dropped",
"Need better technical spike planning",
"Database migration process is too slow"
],
"action_items": [
{
"id": "AI-006",
"description": "Schedule technical spike for complex integrations",
"owner": "John Doe",
"priority": "high",
"due_date": "2024-02-23",
"status": "completed",
"created_sprint": 3,
"completed_sprint": 4,
"category": "technical",
"effort_estimate": "medium"
},
{
"id": "AI-007",
"description": "Review scope change process with Product Owner",
"owner": "Sarah Chen",
"priority": "medium",
"due_date": "2024-02-26",
"status": "completed",
"created_sprint": 3,
"completed_sprint": 4,
"category": "process",
"effort_estimate": "low"
},
{
"id": "AI-008",
"description": "Improve database migration approval workflow",
"owner": "Bob Wilson",
"priority": "medium",
"due_date": "2024-03-08",
"status": "blocked",
"created_sprint": 3,
"category": "process",
"effort_estimate": "high"
}
]
},
{
"sprint_number": 4,
"date": "2024-03-01",
"facilitator": "Sarah Chen",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown", "Sarah Chen"],
"duration_minutes": 45,
"went_well": [
"Exceeded sprint goal by completing extra work",
"Real-time chat finally delivered with high quality",
"Technical spikes prevented major blockers",
"Team ceremonies back to full engagement",
"Search functionality delivered ahead of schedule"
],
"to_improve": [
"Sprint retrospective was rushed due to time constraints",
"Need better capacity planning for variable team sizes",
"Unit test coverage still below target"
],
"action_items": [
{
"id": "AI-009",
"description": "Block more time for retrospectives in calendar",
"owner": "Sarah Chen",
"priority": "low",
"due_date": "2024-03-08",
"status": "completed",
"created_sprint": 4,
"completed_sprint": 5,
"category": "process",
"effort_estimate": "low"
},
{
"id": "AI-010",
"description": "Establish unit test coverage gates in CI/CD",
"owner": "Bob Wilson",
"priority": "high",
"due_date": "2024-03-15",
"status": "in_progress",
"created_sprint": 4,
"category": "technical",
"effort_estimate": "medium"
}
]
},
{
"sprint_number": 5,
"date": "2024-03-15",
"facilitator": "Alice Brown",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown"],
"duration_minutes": 105,
"went_well": [
"Team adapted well to reduced capacity",
"Good support for team member on sick leave",
"Documentation work was delivered on time",
"Rate limiting implementation was smooth"
],
"to_improve": [
"External service dependencies caused major delays",
"Too much scope change again - need better discipline",
"Team capacity planning needs improvement",
"Daily standup attendance dropped significantly",
"Analytics service reliability is a recurring issue"
],
"action_items": [
{
"id": "AI-011",
"description": "Create external service dependency register",
"owner": "John Doe",
"priority": "high",
"due_date": "2024-03-22",
"status": "not_started",
"created_sprint": 5,
"category": "process",
"effort_estimate": "medium"
},
{
"id": "AI-012",
"description": "Escalate analytics service reliability issues",
"owner": "Sarah Chen",
"priority": "high",
"due_date": "2024-03-18",
"status": "completed",
"created_sprint": 5,
"completed_sprint": 6,
"category": "external",
"effort_estimate": "low"
},
{
"id": "AI-013",
"description": "Implement capacity planning buffer for sick leave",
"owner": "Sarah Chen",
"priority": "medium",
"due_date": "2024-03-29",
"status": "in_progress",
"created_sprint": 5,
"category": "process",
"effort_estimate": "medium"
}
]
},
{
"sprint_number": 6,
"date": "2024-03-29",
"facilitator": "Sarah Chen",
"attendees": ["John Doe", "Jane Smith", "Bob Wilson", "Alice Brown", "Sarah Chen"],
"duration_minutes": 70,
"went_well": [
"Excellent sprint execution with team back to full capacity",
"Delivered more points than planned",
"No blockers encountered",
"Strong ceremony engagement across all events",
"Backup system implementation was flawless",
"Team morale has improved significantly"
],
"to_improve": [
"Need to maintain this momentum",
"Could optimize sprint planning efficiency",
"Theme customization feature needs user feedback",
"Performance monitoring setup could be automated"
],
"action_items": [
{
"id": "AI-014",
"description": "Gather user feedback on theme customization",
"owner": "Jane Smith",
"priority": "medium",
"due_date": "2024-04-05",
"status": "not_started",
"created_sprint": 6,
"category": "external",
"effort_estimate": "low"
},
{
"id": "AI-015",
"description": "Automate performance monitoring setup",
"owner": "Bob Wilson",
"priority": "low",
"due_date": "2024-04-12",
"status": "not_started",
"created_sprint": 6,
"category": "technical",
"effort_estimate": "medium"
}
]
}
]
} Sprint [NUMBER] - [SPRINT_NAME] Report
Team: [TEAM_NAME]
Scrum Master: [SCRUM_MASTER_NAME]
Sprint Period: [START_DATE] to [END_DATE]
Report Date: [REPORT_DATE]
Executive Summary
Sprint Goal Achievement: [ACHIEVED/PARTIALLY_ACHIEVED/NOT_ACHIEVED]
Overall Health Grade: [EXCELLENT/GOOD/FAIR/POOR] ([HEALTH_SCORE]/100)
Velocity: [COMPLETED_POINTS] points ([VELOCITY_TREND] from previous sprint)
Commitment Ratio: [COMMITMENT_PERCENTAGE]% of planned work completed
Key Highlights
- [KEY_ACHIEVEMENT_1]
- [KEY_ACHIEVEMENT_2]
- [KEY_CHALLENGE_1]
- [KEY_CHALLENGE_2]
Sprint Metrics Dashboard
Delivery Performance
| Metric | Value | Target | Status |
|---|---|---|---|
| Planned Points | [PLANNED_POINTS] | - | - |
| Completed Points | [COMPLETED_POINTS] | [TARGET_VELOCITY] | [ON_TRACK/BELOW/ABOVE] |
| Commitment Ratio | [COMMITMENT_PERCENTAGE]% | 85-100% | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
| Stories Completed | [COMPLETED_STORIES]/[TOTAL_STORIES] | 80%+ | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
| Carry-over Points | [CARRY_OVER_POINTS] | <20% | [GOOD/ACCEPTABLE/CONCERNING] |
Process Health
| Metric | Value | Target | Status |
|---|---|---|---|
| Scope Change | [SCOPE_CHANGE_PERCENTAGE]% | <15% | [STABLE/MODERATE/UNSTABLE] |
| Blocker Resolution | [AVG_RESOLUTION_DAYS] days | <3 days | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
| Daily Standup Attendance | [STANDUP_ATTENDANCE]% | >90% | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
| Retrospective Participation | [RETRO_ATTENDANCE]% | >95% | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
Quality Indicators
| Metric | Value | Target | Status |
|---|---|---|---|
| Definition of Done Adherence | [DOD_ADHERENCE]% | 100% | [EXCELLENT/NEEDS_IMPROVEMENT] |
| Test Coverage | [TEST_COVERAGE]% | >80% | [EXCELLENT/GOOD/NEEDS_IMPROVEMENT] |
| Code Review Completion | [CODE_REVIEW_COMPLETION]% | 100% | [EXCELLENT/NEEDS_IMPROVEMENT] |
| Technical Debt Items | [TECH_DEBT_ADDED]/[TECH_DEBT_RESOLVED] | Net negative | [IMPROVING/STABLE/CONCERNING] |
User Stories Delivered
Completed Stories ([COMPLETED_COUNT])
| Story ID | Title | Points | Owner | Completion Date | Notes |
|---|---|---|---|---|---|
| [STORY_ID_1] | [STORY_TITLE_1] | [POINTS_1] | [OWNER_1] | [DATE_1] | [NOTES_1] |
| [STORY_ID_2] | [STORY_TITLE_2] | [POINTS_2] | [OWNER_2] | [DATE_2] | [NOTES_2] |
In Progress Stories ([IN_PROGRESS_COUNT])
| Story ID | Title | Points | Owner | Progress | Expected Completion |
|---|---|---|---|---|---|
| [STORY_ID_3] | [STORY_TITLE_3] | [POINTS_3] | [OWNER_3] | [PROGRESS_3] | [ETA_3] |
Blocked Stories ([BLOCKED_COUNT])
| Story ID | Title | Points | Owner | Blocker | Days Blocked | Escalation Status |
|---|---|---|---|---|---|---|
| [STORY_ID_4] | [STORY_TITLE_4] | [POINTS_4] | [OWNER_4] | [BLOCKER_4] | [DAYS_4] | [ESCALATION_4] |
Blockers & Impediments
Resolved This Sprint ([RESOLVED_BLOCKERS_COUNT])
| ID | Description | Category | Created | Resolved | Resolution Time | Impact |
|---|---|---|---|---|---|---|
| [BLOCKER_ID_1] | [DESCRIPTION_1] | [CATEGORY_1] | [CREATED_1] | [RESOLVED_1] | [TIME_1] days | [IMPACT_1] |
Active Blockers ([ACTIVE_BLOCKERS_COUNT])
| ID | Description | Category | Age | Owner | Next Steps | Priority |
|---|---|---|---|---|---|---|
| [BLOCKER_ID_2] | [DESCRIPTION_2] | [CATEGORY_2] | [AGE_2] days | [OWNER_2] | [NEXT_STEPS_2] | [PRIORITY_2] |
Escalation Required
- [ESCALATION_ITEM_1]
- [ESCALATION_ITEM_2]
Team Performance Analysis
Velocity Trend
Sprint [N-2]: [VELOCITY_N2] points
Sprint [N-1]: [VELOCITY_N1] points
Sprint [N]: [VELOCITY_N] points
Trend: [IMPROVING/STABLE/DECLINING] ([TREND_PERCENTAGE]% change)Predictability Assessment
- Coefficient of Variation: [CV_PERCENTAGE]% ([HIGH/MODERATE/LOW] volatility)
- Commitment Reliability: [COMMITMENT_RELIABILITY_SCORE]/100
- Forecast Confidence: [FORECAST_CONFIDENCE]% for next sprint
Team Health Indicators
| Dimension | Score | Grade | Trend | Action Required |
|---|---|---|---|---|
| Commitment Reliability | [SCORE_1]/100 | [GRADE_1] | [TREND_1] | [ACTION_1] |
| Scope Stability | [SCORE_2]/100 | [GRADE_2] | [TREND_2] | [ACTION_2] |
| Blocker Resolution | [SCORE_3]/100 | [GRADE_3] | [TREND_3] | [ACTION_3] |
| Ceremony Engagement | [SCORE_4]/100 | [GRADE_4] | [TREND_4] | [ACTION_4] |
| Story Completion | [SCORE_5]/100 | [GRADE_5] | [TREND_5] | [ACTION_5] |
Retrospective Insights
What Went Well
- [WENT_WELL_1]
- [WENT_WELL_2]
- [WENT_WELL_3]
Areas for Improvement
- [IMPROVE_1]
- [IMPROVE_2]
- [IMPROVE_3]
Action Items from Retrospective
| ID | Action | Owner | Due Date | Priority | Status |
|---|---|---|---|---|---|
| [AI_ID_1] | [ACTION_1] | [OWNER_1] | [DUE_1] | [PRIORITY_1] | [STATUS_1] |
| [AI_ID_2] | [ACTION_2] | [OWNER_2] | [DUE_2] | [PRIORITY_2] | [STATUS_2] |
Previous Sprint Action Items Follow-up
| ID | Action | Owner | Status | Completion Notes |
|---|---|---|---|---|
| [PREV_AI_1] | [PREV_ACTION_1] | [PREV_OWNER_1] | [PREV_STATUS_1] | [PREV_NOTES_1] |
Risks & Dependencies
High Priority Risks
| Risk | Probability | Impact | Mitigation Plan | Owner |
|---|---|---|---|---|
| [RISK_1] | [PROB_1] | [IMPACT_1] | [MITIGATION_1] | [OWNER_1] |
External Dependencies
| Dependency | Provider | Status | Expected Resolution | Contingency Plan |
|---|---|---|---|---|
| [DEP_1] | [PROVIDER_1] | [STATUS_1] | [RESOLUTION_1] | [CONTINGENCY_1] |
Looking Ahead: Next Sprint
Sprint Goals
- [GOAL_1]
- [GOAL_2]
- [GOAL_3]
Planned Capacity
- Team Size: [TEAM_SIZE] members
- Available Capacity: [AVAILABLE_HOURS] hours ([CAPACITY_POINTS] points)
- Planned Velocity: [PLANNED_VELOCITY] points
- Capacity Buffer: [BUFFER_PERCENTAGE]% for unknowns
Key Focus Areas
- [FOCUS_AREA_1]
- [FOCUS_AREA_2]
- [FOCUS_AREA_3]
Dependencies to Monitor
- [MONITOR_DEP_1]
- [MONITOR_DEP_2]
Recommendations
Immediate Actions (This Sprint)
- [HIGH_PRIORITY_ACTION_1] - [DESCRIPTION] (Owner: [OWNER], Due: [DATE])
- [HIGH_PRIORITY_ACTION_2] - [DESCRIPTION] (Owner: [OWNER], Due: [DATE])
Process Improvements (Next 2-3 Sprints)
- [PROCESS_IMPROVEMENT_1] - [DESCRIPTION]
- [PROCESS_IMPROVEMENT_2] - [DESCRIPTION]
Team Development Opportunities
- [DEVELOPMENT_1] - [DESCRIPTION]
- [DEVELOPMENT_2] - [DESCRIPTION]
Appendix
Sprint Burndown Chart
[BURNDOWN_CHART_REFERENCE]
Detailed Metrics
[DETAILED_METRICS_REFERENCE]
Team Feedback
[TEAM_FEEDBACK_SUMMARY]
Report prepared by: [SCRUM_MASTER_NAME]
Next review date: [NEXT_REVIEW_DATE]
Distribution: Product Owner, Development Team, Stakeholders
This report is generated using standardized sprint health metrics and retrospective analysis. For questions or deeper analysis, please contact the Scrum Master.
Team Health Check - Spotify Squad Model
Team: [TEAM_NAME]
Assessment Date: [DATE]
Facilitator: [FACILITATOR_NAME]
Participants: [PARTICIPANT_COUNT] of [TOTAL_TEAM_SIZE] members
Health Check Overview
The Team Health Check is based on Spotify's Squad Health Check model, designed to visualize team health across multiple dimensions. Each dimension is assessed using a simple traffic light system:
- 🟢 Green (Awesome): We're doing great! No major concerns.
- 🟡 Yellow (Some Concerns): We're doing okay, but there are some things we could improve.
- 🔴 Red (Not Good): This really sucks and we need to do something about it.
Assessment Method
- Anonymous individual ratings followed by team discussion
- Focus on trends over time rather than absolute scores
- Action-oriented outcomes for improvement areas
Health Dimensions Assessment
1. Delivering Value 🎯
Are we delivering value to our users and stakeholders?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
2. Learning 📚
Are we learning and growing as individuals and as a team?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
3. Fun 🎉
Do we enjoy working together and find our work engaging?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
4. Health of Codebase 🏗️
Is our code healthy, maintainable, and of good quality?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
5. Mission Clarity 🎯
Do we understand why we exist and what we're supposed to achieve?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
6. Suitable Process ⚙️
Is our process helping us be effective?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
7. Support 🤝
Do we get the support we need from management and other teams?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
8. Speed ⚡
Are we able to deliver quickly without compromising quality?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
9. Pawns or Players 👥
Do we feel like we have control over our work and destiny?
Current Status: [🟢/🟡/🔴]
Trend from Last Check: [⬆️ Improving / ➡️ Stable / ⬇️ Declining]
Team Rating: [X]/5 team members voted Green, [Y]/5 Yellow, [Z]/5 Red
What's Working Well:
- [POSITIVE_POINT_1]
- [POSITIVE_POINT_2]
Areas of Concern:
- [CONCERN_1]
- [CONCERN_2]
Suggested Actions:
- [ACTION_1]
- [ACTION_2]
Overall Health Summary
Health Score Distribution
- 🟢 Green Dimensions: [GREEN_COUNT]/9 ([GREEN_PERCENTAGE]%)
- 🟡 Yellow Dimensions: [YELLOW_COUNT]/9 ([YELLOW_PERCENTAGE]%)
- 🔴 Red Dimensions: [RED_COUNT]/9 ([RED_PERCENTAGE]%)
Overall Health Grade: [EXCELLENT/GOOD/FAIR/POOR]
Trend Analysis
- Improving: [IMPROVING_COUNT] dimensions
- Stable: [STABLE_COUNT] dimensions
- Declining: [DECLINING_COUNT] dimensions
Team Maturity Level
Based on the health check results and team dynamics observed: [FORMING/STORMING/NORMING/PERFORMING/ADJOURNING]
Priority Action Items
High Priority (Red Dimensions)
[RED_DIMENSION_1]: [ACTION_DESCRIPTION_1]
- Owner: [OWNER_1]
- Timeline: [TIMELINE_1]
- Success Criteria: [CRITERIA_1]
[RED_DIMENSION_2]: [ACTION_DESCRIPTION_2]
- Owner: [OWNER_2]
- Timeline: [TIMELINE_2]
- Success Criteria: [CRITERIA_2]
Medium Priority (Yellow Dimensions)
[YELLOW_DIMENSION_1]: [ACTION_DESCRIPTION_1]
- Owner: [OWNER_1]
- Timeline: [TIMELINE_1]
[YELLOW_DIMENSION_2]: [ACTION_DESCRIPTION_2]
- Owner: [OWNER_2]
- Timeline: [TIMELINE_2]
Maintain Strengths (Green Dimensions)
- [GREEN_DIMENSION_1]: Continue [STRENGTH_PRACTICE_1]
- [GREEN_DIMENSION_2]: Share [BEST_PRACTICE_1] with other teams
Psychological Safety Assessment
Separate anonymous assessment of team psychological safety
Psychological Safety Indicators
Speaking Up: Team members feel safe to speak up with ideas, questions, concerns, or mistakes
- Score: [SCORE_1]/5 ⭐⭐⭐⭐⭐
Risk Taking: Team members feel safe to take risks and make mistakes
- Score: [SCORE_2]/5 ⭐⭐⭐⭐⭐
Asking for Help: Team members feel comfortable asking for help or admitting they don't know something
- Score: [SCORE_3]/5 ⭐⭐⭐⭐⭐
Discussing Problems: Difficult topics and problems can be discussed openly
- Score: [SCORE_4]/5 ⭐⭐⭐⭐⭐
Being Yourself: Team members don't feel they have to pretend to be someone else
- Score: [SCORE_5]/5 ⭐⭐⭐⭐⭐
Overall Psychological Safety Score: [TOTAL_SCORE]/25
Psychological Safety Actions
- [PSYCH_SAFETY_ACTION_1]
- [PSYCH_SAFETY_ACTION_2]
Communication & Collaboration Assessment
Communication Quality
- Clarity of Communication: [SCORE]/5 ⭐⭐⭐⭐⭐
- Frequency of Communication: [SCORE]/5 ⭐⭐⭐⭐⭐
- Openness & Transparency: [SCORE]/5 ⭐⭐⭐⭐⭐
Collaboration Patterns
- Cross-functional Collaboration: [SCORE]/5 ⭐⭐⭐⭐⭐
- Knowledge Sharing: [SCORE]/5 ⭐⭐⭐⭐⭐
- Conflict Resolution: [SCORE]/5 ⭐⭐⭐⭐⭐
Follow-up Plan
Next Health Check
Scheduled Date: [NEXT_DATE]
Frequency: [MONTHLY/QUARTERLY/BI-ANNUAL]
Interim Check-ins
- Sprint Retrospectives: Continue monitoring health indicators
- Weekly 1:1s: Individual pulse checks with team members
- Monthly Team Lunches: Informal health and morale assessment
Success Metrics
We'll know we're improving when we see:
- [SUCCESS_METRIC_1]
- [SUCCESS_METRIC_2]
- [SUCCESS_METRIC_3]
Historical Comparison
Previous Health Checks
| Date | Green | Yellow | Red | Overall Trend |
|---|---|---|---|---|
| [PREV_DATE_1] | [G1] | [Y1] | [R1] | [TREND_1] |
| [PREV_DATE_2] | [G2] | [Y2] | [R2] | [TREND_2] |
| [CURRENT_DATE] | [G3] | [Y3] | [R3] | [TREND_3] |
Long-term Improvements
- [LONG_TERM_IMPROVEMENT_1]
- [LONG_TERM_IMPROVEMENT_2]
Persistent Challenges
- [PERSISTENT_CHALLENGE_1]
- [PERSISTENT_CHALLENGE_2]
Team Comments & Feedback
Anonymous feedback from team members
What's the most important thing we should focus on?
- "[FEEDBACK_1]"
- "[FEEDBACK_2]"
- "[FEEDBACK_3]"
What's our biggest strength as a team?
- "[STRENGTH_1]"
- "[STRENGTH_2]"
- "[STRENGTH_3]"
If you could change one thing, what would it be?
- "[CHANGE_1]"
- "[CHANGE_2]"
- "[CHANGE_3]"
Action Item Summary
| Priority | Action | Owner | Due Date | Success Criteria | Status |
|---|---|---|---|---|---|
| High | [ACTION_1] | [OWNER_1] | [DATE_1] | [CRITERIA_1] | [STATUS_1] |
| High | [ACTION_2] | [OWNER_2] | [DATE_2] | [CRITERIA_2] | [STATUS_2] |
| Medium | [ACTION_3] | [OWNER_3] | [DATE_3] | [CRITERIA_3] | [STATUS_3] |
| Medium | [ACTION_4] | [OWNER_4] | [DATE_4] | [CRITERIA_4] | [STATUS_4] |
Assessment completed by: [FACILITATOR_NAME]
Report distribution: Team Members, Product Owner, Management (summary only)
Confidentiality: Individual responses kept confidential, only aggregate data shared
This health check is based on the Spotify Squad Health Check model. The goal is continuous improvement, not judgment. Use this data to have better conversations about how to work together effectively.
Sprint Retrospective Formats
Start/Stop/Continue
Best for: Teams new to retrospectives, quick format Duration: 45-60 minutes
Structure
Create three columns:
- Start: What should we begin doing?
- Stop: What should we stop doing?
- Continue: What's working well that we should keep doing?
Process
- Team silently adds items to each column (10 min)
- Group similar items (5 min)
- Discuss each category, vote on top items (20 min)
- Select 2-3 actions (10 min)
Example Output
Start:
- Pairing on complex stories
- Code reviews within 4 hours
Stop:
- Taking on work mid-sprint
- Skipping acceptance criteria
Continue:
- Daily standups at 9:30am
- Demo prep on Thursday
Glad/Sad/Mad
Best for: Emotional check-in, team morale assessment Duration: 60-75 minutes
Structure
Create three areas:
- Glad: What made you happy this sprint?
- Sad: What disappointed you?
- Mad: What frustrated you?
Process
- Silent brainstorming (10 min)
- Share items, one person at a time (15 min)
- Group themes (5 min)
- Discuss top items from each category (20 min)
- Identify action items (10 min)
Example Output
Glad:
- Shipped feature X on time
- Great collaboration with design team
- New deployment process worked well
Sad:
- Lost time to production bugs
- Didn't finish all committed work
- Documentation fell behind
Mad:
- Environment was down 2 days
- Requirements changed mid-sprint
- Still waiting on API key from vendor
Facilitation Tips
- Acknowledge emotions, don't dismiss
- Focus on what we can control
- Convert frustrations into actions
4Ls (Liked, Learned, Lacked, Longed For)
Best for: Deeper reflection, learning focus Duration: 60-90 minutes
Structure
- Liked: What went well? What did we enjoy?
- Learned: What new insights did we gain?
- Lacked: What was missing? What did we need?
- Longed For: What do we wish we had?
Process
- Individual reflection (10 min)
- Round-robin sharing (20 min)
- Group similar items (10 min)
- Deep dive on top items (20 min)
- Action planning (15 min)
Example Output
Liked:
- Pair programming sessions
- Clear acceptance criteria
- Product Owner availability
Learned:
- New testing framework capabilities
- How to better estimate stories
- Importance of architectural review
Lacked:
- Automated deployment
- Clear API documentation
- Sufficient testing time
Longed For:
- Better development environments
- More design time upfront
- Dedicated QA support
Sailboat
Best for: Visual teams, identifying headwinds and tailwinds Duration: 60-90 minutes
Structure
Draw a sailboat with:
- Wind (propellers): What's helping us go faster?
- Anchors: What's slowing us down?
- Rocks (hazards): What risks are ahead?
- Island (goal): Where are we headed?
Process
- Explain metaphor (5 min)
- Team adds sticky notes to each area (15 min)
- Group and discuss each area (30 min)
- Prioritize anchors to remove (10 min)
- Create action plan (15 min)
Example Output
Wind:
- Strong team collaboration
- Clear product vision
- Good tooling
Anchors:
- Slow CI/CD pipeline
- Too many meetings
- Technical debt
Rocks:
- Upcoming dependency on Team B
- Key person on vacation next sprint
- Infrastructure migration
Island:
- Launch v2.0 by end of quarter
- Improve system stability
- Reduce production bugs by 50%
Timeline
Best for: Detailed sprint review, identifying patterns Duration: 75-90 minutes
Structure
Create a timeline of the sprint on a whiteboard:
- Days of the sprint across the top
- Events, milestones, feelings plotted on timeline
Process
- Draw sprint timeline (5 min)
- Team adds events chronologically (15 min)
- Add emotion indicators (happy/sad/stressed) (10 min)
- Identify patterns and themes (20 min)
- Discuss high/low points (20 min)
- Extract learnings and actions (15 min)
Example Timeline
Day 1: Sprint planning, feeling optimistic 😊
Day 3: Production bug discovered, stressed 😰
Day 5: Bug fixed, relieved 😌
Day 7: Design feedback changed scope, frustrated 😠
Day 9: Great pairing session on new feature 😊
Day 10: Demo went really well! 🎉Facilitation Tips
- Focus on objective events first, emotions second
- Look for correlations between events and feelings
- Identify early warning signs
- Celebrate wins
Starfish
Best for: More granular feedback than Start/Stop/Continue Duration: 60-90 minutes
Structure
Five categories:
- Keep Doing: What's working, don't change
- Less Of: What should we reduce?
- More Of: What should we increase?
- Stop Doing: What should we eliminate?
- Start Doing: What new practices should we try?
Process
- Explain each category (5 min)
- Silent brainstorming (15 min)
- Share and group items (15 min)
- Discuss each category (25 min)
- Vote on top actions (10 min)
- Create action plan (15 min)
Example Output
Keep Doing:
- Pairing on complex stories
- Demo every Friday
Less Of:
- Context switching
- Unplanned work
More Of:
- Automated testing
- Design upfront
Stop Doing:
- Skipping code reviews
- Working weekends
Start Doing:
- Mob programming for knowledge sharing
- Weekly architecture discussions
Speed Dating
Best for: Large teams, fresh perspectives Duration: 60 minutes
Structure
- Pair up team members who don't usually work together
- Rotate pairs every 10 minutes
- Discuss sprint from different perspectives
Process
- Create pairs (2 min)
- Round 1: "What went well?" (10 min)
- Rotate pairs (2 min)
- Round 2: "What could improve?" (10 min)
- Rotate pairs (2 min)
- Round 3: "What should we try?" (10 min)
- Full group synthesis (15 min)
- Action planning (10 min)
Facilitation Tips
- Ensure quiet voices are heard
- Mix up pairs intentionally
- Capture themes as they emerge
- Focus on shared themes in synthesis
Three Little Pigs
Best for: Architecture and technical decisions Duration: 60-75 minutes
Structure
Based on the story:
- Straw House: What's fragile? What will blow down?
- Stick House: What's okay but could be better?
- Brick House: What's solid and will last?
Process
- Explain metaphor (5 min)
- Team identifies items for each house (15 min)
- Group and discuss (20 min)
- Prioritize straw house items to fix (10 min)
- Create action plan (15 min)
Example Output
Straw House (fragile):
- Manual deployment process
- No automated tests for API
- Undocumented code
Stick House (needs improvement):
- Test coverage at 60%
- Some documentation exists
- Partially automated builds
Brick House (solid):
- Strong CI/CD for frontend
- Well-tested core modules
- Clear architecture docs
Facilitation Best Practices
Before Retrospective
- Review previous action items
- Gather sprint metrics
- Choose format based on team needs
- Prepare collaboration space
During Retrospective
- Set the stage: Create safe environment
- Prime directive: "Regardless of what we discover, we understand and truly believe that everyone did the best job they could, given what they knew at the time, their skills and abilities, the resources available, and the situation at hand."
- Timebox discussions: Keep energy high
- Focus on actions: Not just talk
- Limit action items: 1-3 max for next sprint
- Get specific: Vague actions don't happen
After Retrospective
- Document immediately in Confluence
- Create Jira tickets for actions
- Assign owners and due dates
- Track completion
- Start next retro by reviewing these
Red Flags
- Same issues every retro → Need deeper intervention
- No action items → Team not engaged
- Blame game → Not safe environment
- No follow-through → Actions not valued
- Facilitator talks more than team → Not facilitating
Rotation Strategy
- Vary formats every 2-3 sprints
- Let team choose occasionally
- Match format to team mood
- Try new format when stuck
Team Dynamics Framework for Scrum Teams
Table of Contents
- Overview
- Tuckman's Model Applied to Scrum
- Psychological Safety in Agile Teams
- Team Performance Metrics
- Facilitation Techniques by Stage
- Conflict Resolution Strategies
- Assessment Tools
- Intervention Strategies
- Measurement & Tracking
Overview
Understanding team dynamics is crucial for Scrum Masters to effectively guide teams through their development journey. This framework combines Tuckman's stages of group development with psychological safety principles and practical scrum-specific interventions.
Core Principles
- Development is Non-Linear: Teams may cycle between stages based on changes
- Each Stage Has Value: Every stage serves a purpose in team development
- Facilitation Must Adapt: Leadership style should match the team's developmental stage
- Psychological Safety is Foundational: Without safety, teams cannot reach high performance
- Measurement Enables Improvement: Track dynamics to guide interventions
Framework Components
- Tuckman's Stages: Forming → Storming → Norming → Performing → Adjourning
- Psychological Safety: Environment for risk-taking and learning
- Scrum Ceremonies: Team development accelerators when facilitated well
- Metrics & Assessment: Data-driven approach to team health
Tuckman's Model Applied to Scrum
Stage 1: Forming (Team Inception)
"Getting to know each other and understanding the work"
Characteristics in Scrum Context
- Individual Focus: Members work independently, unsure of roles
- Politeness: Conflict is avoided, everyone tries to be agreeable
- Dependency: Heavy reliance on Scrum Master for guidance
- Ceremony Awkwardness: Standups feel forced, retrospectives are superficial
- Low Velocity: Productivity is low as team learns to work together
Scrum Master Behaviors
- Directing Style: Provide clear structure and guidance
- Process Champion: Teach scrum framework and ceremonies rigorously
- Relationship Builder: Facilitate team bonding and trust building
- Context Setter: Explain the "why" behind practices and goals
Key Metrics & Indicators
| Metric | Forming Range | Assessment Method |
|---|---|---|
| Ceremony Participation | 60-80% | Attendance tracking |
| Cross-team Collaboration | Low | Story pairing frequency |
| Velocity Predictability | High volatility (CV >40%) | Velocity coefficient of variation |
| Psychological Safety | 2.0-3.5/5.0 | Anonymous team survey |
| Conflict Frequency | Very low | Retrospective themes |
Intervention Strategies
- Team Charter Creation: Define working agreements and values together
- Skill Inventory: Map team capabilities and identify knowledge gaps
- Pairing/Mobbing: Encourage collaborative work to build relationships
- Social Activities: Team lunches, informal interactions
- Process Education: Intensive scrum training and coaching
Success Indicators
- Consistent ceremony attendance (>85%)
- Team members start asking questions about process
- Initial working agreements are established
- Some cross-functional collaboration begins
Stage 2: Storming (Productive Conflict)
"Working through differences and establishing team dynamics"
Characteristics in Scrum Context
- Conflict Emergence: Disagreements about technical approaches, priorities
- Role Struggles: Tension around responsibilities and decision-making authority
- Process Pushback: Questioning scrum practices, suggesting changes
- Subgroup Formation: Cliques or mini-alliances may form
- Velocity Fluctuations: Performance varies as team works through conflicts
Scrum Master Behaviors
- Coaching Style: Guide conflict resolution without directing solutions
- Neutral Facilitator: Help team work through disagreements constructively
- Psychological Safety Guardian: Ensure conflicts remain productive
- Process Flexibility: Adapt ceremonies to team's evolving needs
Key Metrics & Indicators
| Metric | Storming Range | Assessment Method |
|---|---|---|
| Conflict Frequency | Moderate-High | Retrospective action items |
| Ceremony Engagement | Variable (70-90%) | Participation quality scoring |
| Velocity Volatility | Moderate (CV 25-40%) | Sprint-to-sprint variation |
| Psychological Safety | 2.5-4.0/5.0 | Team surveys + observation |
| Process Adherence | Inconsistent | Ceremony audit scores |
Intervention Strategies
- Conflict Facilitation: Structured conflict resolution sessions
- Retrospective Focus: Deep-dive into team dynamics and relationships
- Individual Coaching: 1:1s to address personal concerns and conflicts
- Working Agreement Updates: Revisit and refine team agreements
- External Facilitation: Bring in neutral parties for significant conflicts
Success Indicators
- Conflicts are addressed openly rather than avoided
- Team develops mechanisms for working through disagreements
- Ceremony participation becomes more authentic
- Velocity starts to stabilize
Stage 3: Norming (Agreement & Collaboration)
"Establishing effective ways of working together"
Characteristics in Scrum Context
- Shared Ownership: Team takes collective responsibility for outcomes
- Process Refinement: Self-organizing improvements to scrum practices
- Collaboration Increase: More cross-functional pairing and knowledge sharing
- Ceremony Effectiveness: Meetings become more focused and productive
- Velocity Stabilization: More predictable delivery patterns emerge
Scrum Master Behaviors
- Supporting Style: Step back and let team lead, provide support when needed
- Impediment Remover: Focus on external blockers and organizational issues
- Continuous Improvement Coach: Help team identify and implement improvements
- Shield Provider: Protect team from external disruptions
Key Metrics & Indicators
| Metric | Norming Range | Assessment Method |
|---|---|---|
| Self-Organization | Increasing | Decision-making autonomy tracking |
| Ceremony Effectiveness | 80-90% | Time-to-value ratios |
| Velocity Consistency | Good (CV 15-25%) | Rolling average stability |
| Psychological Safety | 3.5-4.5/5.0 | Regular pulse surveys |
| Knowledge Sharing | High | Cross-training metrics |
Intervention Strategies
- Process Ownership Transfer: Guide team to own ceremony facilitation
- Skill Development: Focus on technical and collaboration skills
- Measurement Introduction: Help team define their own success metrics
- External Relationship Building: Facilitate connections with other teams
- Continuous Improvement Rhythm: Establish regular process refinement
Success Indicators
- Team members facilitate some ceremonies themselves
- Proactive identification and resolution of impediments
- Stable, predictable velocity patterns
- High-quality retrospectives with actionable outcomes
Stage 4: Performing (High Performance)
"Delivering exceptional results together"
Characteristics in Scrum Context
- Collective Excellence: Team consistently exceeds expectations
- Adaptive Expertise: Quick response to changing requirements
- Self-Management: Minimal need for external direction
- Innovation: Team generates creative solutions and process improvements
- Knowledge Multiplication: Members actively develop others
Scrum Master Behaviors
- Delegating Style: Minimal intervention, team is largely autonomous
- Strategic Facilitator: Focus on long-term team development and capability
- Organizational Catalyst: Help team influence broader organizational change
- Mentor Developer: Coach team members to become coaches themselves
Key Metrics & Indicators
| Metric | Performing Range | Assessment Method |
|---|---|---|
| Autonomy Level | High | Decision independence tracking |
| Innovation Frequency | Regular | New idea implementation rate |
| Velocity Excellence | High + Consistent (CV <15%) | Performance benchmarking |
| Psychological Safety | 4.0-5.0/5.0 | Team assessment + observation |
| External Impact | Significant | Other teams adopting practices |
Intervention Strategies
- Challenge Provision: Introduce stretch goals and complex problems
- Leadership Development: Grow team members into coaches/leaders
- Knowledge Sharing: Facilitate teaching other teams
- Strategic Alignment: Connect team excellence to organizational goals
- Innovation Support: Create space for experimentation and learning
Success Indicators
- Consistent delivery of high-quality work with minimal defects
- Team serves as a model for other teams in the organization
- Members are sought out for coaching and mentoring roles
- Proactive contribution to organizational process improvements
Stage 5: Adjourning (Transition & Legacy)
"Wrapping up and transitioning knowledge"
Characteristics in Scrum Context
- Closure Activities: Project completion or team dissolution
- Knowledge Transfer: Documenting learnings and sharing expertise
- Relationship Maintenance: Preserving professional networks
- Legacy Creation: Ensuring practices continue beyond the team
- Emotional Processing: Addressing feelings about team ending
Scrum Master Behaviors
- Closure Facilitator: Guide proper conclusion of work and relationships
- Legacy Curator: Ensure knowledge and practices are preserved
- Transition Planner: Help members move to new roles/teams effectively
- Emotional Support: Acknowledge and process team disbanding feelings
Key Activities
- Final Retrospective: Comprehensive review of team journey and learnings
- Practice Documentation: Record effective processes for future teams
- Knowledge Transfer Sessions: Share expertise with successor teams
- Celebration: Acknowledge achievements and relationships built
- Network Maintenance: Establish ongoing professional connections
Psychological Safety in Agile Teams
Definition & Importance
Psychological safety is the belief that one can show vulnerability, ask questions, admit mistakes, and propose ideas without risk of negative consequences to self-image, status, or career.
Google's Four Components Applied to Scrum
- Ability to show vulnerability and ask for help
- Permission to discuss difficult topics and disagreements
- Freedom to take risks and make mistakes
- Encouragement to be authentic and express oneself
Building Psychological Safety in Scrum Teams
Daily Standups
- Model Vulnerability: Scrum Master admits own mistakes and uncertainties
- Normalize Help-Seeking: "Who needs help?" vs. "Any blockers?"
- Celebrate Learning: Highlight lessons learned from failures
- Time Protection: Ensure everyone has space to speak
Sprint Planning
- Estimation Comfort: No judgment for "wrong" estimates
- Capacity Honesty: Safe to express realistic availability
- Question Encouragement: Reward curiosity and clarification requests
- Scope Negotiation: Team can push back on unrealistic commitments
Sprint Reviews
- Failure Normalization: Discuss what didn't work without blame
- Stakeholder Preparation: Coach stakeholders on constructive feedback
- Team Support: Unified front when facing criticism
- Learning Focus: Frame setbacks as learning opportunities
Retrospectives
- Non-Judgmental Space: Focus on systems, not individuals
- Equal Participation: Ensure all voices are heard
- Actionable Outcomes: Team commits to improvements together
- Confidentiality: What's said in retro stays in retro
Measuring Psychological Safety
Edmondson's 7-Point Scale
- If you make a mistake on this team, it is often held against you
- Members of this team are able to bring up problems and tough issues
- People on this team sometimes reject others for being different
- It is safe to take a risk on this team
- It is difficult to ask other members of this team for help
- No one on this team would deliberately act to undermine my efforts
- Working with members of this team, my unique skills and talents are valued and utilized
Practical Assessment Questions
- Risk Taking: "Do team members speak up when they disagree with leadership?"
- Mistake Handling: "How does the team respond when someone makes an error?"
- Help Seeking: "Do people admit when they don't know something?"
- Inclusion: "Are all team members' ideas heard and considered?"
- Innovation: "Does the team experiment with new approaches?"
Team Performance Metrics
Quantitative Indicators
Velocity & Predictability
- Sprint Velocity Trends: Improvement over time indicates team development
- Commitment Reliability: Ability to deliver planned work consistently
- Velocity Volatility (CV): Lower variation indicates team maturity
- Forecast Accuracy: Precision in release planning improves with development
Quality Metrics
- Defect Rates: High-performing teams have lower defect introduction
- Definition of Done Adherence: Mature teams consistently meet quality criteria
- Technical Debt Management: Performing teams proactively address debt
- Customer Satisfaction: Ultimately reflected in user/stakeholder feedback
Collaboration Indicators
- Cross-functional Work: Story completion without handoffs
- Knowledge Sharing: Pair programming, code review participation
- Skill Development: Team members learning from each other
- Collective Ownership: Shared responsibility for all team outputs
Qualitative Assessments
Ceremony Quality
- Engagement Level: Active participation vs. passive attendance
- Value Generation: Productive outcomes from time invested
- Self-Facilitation: Team taking ownership of meeting effectiveness
- Adaptation: Tailoring practices to team's specific needs
Communication Patterns
- Openness: Willingness to share problems and concerns
- Constructive Conflict: Disagreements lead to better solutions
- Active Listening: Team members build on each other's ideas
- Feedback Culture: Regular, specific, actionable feedback exchange
Facilitation Techniques by Stage
Forming Stage Facilitation
- Structured Introductions: Personal/professional background sharing
- Explicit Process Teaching: Step-by-step ceremony instruction
- Role Clarification: Clear explanation of responsibilities and expectations
- Safe-to-Fail Experiments: Low-risk opportunities to try new things
Storming Stage Facilitation
- Conflict Normalization: "Conflict is healthy and expected"
- Ground Rules Enforcement: Maintain respectful disagreement standards
- Perspective Taking: Help team members understand different viewpoints
- External Processing: Individual coaching sessions for complex issues
Norming Stage Facilitation
- Autonomy Building: Gradually reduce direct intervention
- Process Ownership Transfer: Team takes responsibility for improvements
- Skill Gap Identification: Focus on capability development
- Success Pattern Recognition: Help team understand what's working
Performing Stage Facilitation
- Challenge Introduction: Stretch goals and complex problems
- Innovation Support: Time and space for experimentation
- Teaching Opportunities: Help team share knowledge with others
- Strategic Connection: Link team excellence to organizational goals
Conflict Resolution Strategies
Healthy vs. Unhealthy Conflict
Healthy Conflict Characteristics
- Task-Focused: About work, not personalities
- Solution-Oriented: Aimed at finding better ways forward
- Open and Direct: Issues addressed transparently
- Respectful: Maintains dignity of all parties
- Temporary: Resolved and doesn't fester
Unhealthy Conflict Characteristics
- Personal Attacks: Targeting individuals rather than ideas
- Win-Lose Mentality: Zero-sum thinking
- Underground: Gossip and indirect communication
- Destructive: Damages relationships and trust
- Persistent: Continues without resolution
Conflict Resolution Process
1. Early Detection
- Retrospective Themes: Recurring issues or tensions
- Ceremony Observation: Body language, participation patterns
- 1:1 Conversations: Individual team member concerns
- Performance Indicators: Velocity drops, quality issues
2. Assessment & Preparation
- Stakeholder Mapping: Who's involved, who's affected
- Issue Clarification: Separate facts from interpretations
- Desired Outcomes: What would resolution look like?
- Facilitation Planning: Process design for resolution session
3. Facilitated Resolution
- Ground Rules: Safe space for honest dialogue
- Perspective Sharing: Each party states their view
- Common Ground: Identify shared interests and values
- Solution Generation: Collaborative problem-solving
- Agreement Creation: Clear commitments and follow-up
4. Follow-up & Learning
- Implementation Support: Help parties honor agreements
- Relationship Repair: Ongoing relationship building
- Process Improvement: Learn from conflict for future prevention
- Team Strengthening: Use resolution as team development opportunity
Assessment Tools
Team Development Stage Assessment
Behavioral Indicators Checklist
Forming Indicators:
- Heavy reliance on Scrum Master for decisions
- Polite, superficial interactions
- Individual work preferences
- Process confusion or resistance
- Low ceremony engagement
Storming Indicators:
- Open disagreements about approach
- Questioning of established processes
- Subgroup formation
- Inconsistent performance
- Emotional reactions to feedback
Norming Indicators:
- Collaborative problem-solving
- Process adaptation and improvement
- Shared responsibility for outcomes
- Constructive feedback exchange
- Stable performance patterns
Performing Indicators:
- Self-organization without external direction
- Proactive problem anticipation
- Innovation and experimentation
- Mentoring of other teams
- Exceptional results consistently
Psychological Safety Assessment Survey
Team Member Self-Assessment (5-point Likert Scale)
- Mistake Tolerance: "When I make a mistake, my team supports me in learning from it"
- Voice Safety: "I feel comfortable challenging decisions or raising concerns"
- Inclusion: "My unique perspective is valued by the team"
- Risk Taking: "I can take calculated risks without fear of negative consequences"
- Help Seeking: "I can admit when I don't know something without judgment"
- Authenticity: "I can be myself without pretending or hiding parts of my personality"
- Innovation: "We try new approaches even if they might not work"
Behavioral Observation Checklist
- Speaking Up: Team members voice disagreements respectfully
- Mistake Response: Errors are discussed openly for learning
- Help Seeking: People admit knowledge gaps and ask for assistance
- Experimentation: Team tries new approaches without excessive fear
- Inclusion: All members participate actively in discussions
- Feedback: Constructive criticism is given and received well
Intervention Strategies
Stage-Specific Interventions
Forming → Storming Transition
- Trust Building Activities: Structured sharing and team bonding
- Psychological Safety Foundation: Establish ground rules for safe conflict
- Process Education: Deep training on collaboration and communication
- Individual Coaching: Prepare team members for productive disagreement
Storming → Norming Transition
- Conflict Resolution Skills: Training in constructive disagreement
- Working Agreement Updates: Refine team collaboration standards
- Success Celebration: Acknowledge progress through difficult conversations
- Process Ownership: Begin transferring facilitation responsibilities
Norming → Performing Transition
- Challenge Introduction: Stretch goals to push team capabilities
- Leadership Development: Grow coaching and mentoring skills
- Innovation Support: Create time and space for experimentation
- External Engagement: Opportunities to influence other teams
Crisis Interventions
Performance Regression
Symptoms: Sudden drops in velocity, quality, or team satisfaction Interventions:
- Team health check and root cause analysis
- Individual 1:1s to understand personal factors
- Process audit to identify systemic issues
- Targeted support for specific capability gaps
Psychological Safety Violations
Symptoms: Team members withdrawing, avoiding risk, or leaving Interventions:
- Immediate protective actions for affected individuals
- Team-wide discussion of psychological safety principles
- Leadership coaching for those who violated safety
- System changes to prevent future violations
External Pressure Impact
Symptoms: Team stress, process shortcuts, decreased collaboration Interventions:
- Stakeholder education about sustainable pace
- Scope negotiation and priority clarification
- Team capacity protection and workload management
- Stress management and resilience building
Measurement & Tracking
Dashboard Metrics by Stage
Forming Stage Metrics
- Ceremony attendance rates
- Individual vs. collaborative work ratios
- Process adherence scores
- Initial psychological safety baseline
Storming Stage Metrics
- Conflict frequency and resolution time
- Ceremony engagement quality
- Velocity volatility measures
- Team satisfaction surveys
Norming Stage Metrics
- Self-organization indicators
- Process improvement frequency
- Knowledge sharing metrics
- Stakeholder satisfaction
Performing Stage Metrics
- Innovation and experimentation rates
- External influence and mentoring
- Exceptional result achievement
- Leadership development outcomes
Tracking Tools & Methods
Regular Assessment Schedule
- Weekly: Ceremony quality observation
- Sprint: Velocity and quality metrics
- Monthly: Psychological safety pulse survey
- Quarterly: Comprehensive team development assessment
Data Collection Methods
- Quantitative: Sprint metrics, attendance, survey scores
- Qualitative: Observation notes, retrospective themes, interview insights
- Behavioral: Video/audio analysis of team interactions (with consent)
- External: Stakeholder feedback, other team perceptions
Progress Visualization
- Team Development Radar: Multi-dimensional progress tracking
- Psychological Safety Trends: Safety metrics over time
- Stage Transition Timeline: Development milestone tracking
- Intervention Impact Assessment: Before/after comparison
Conclusion
Effective team dynamics facilitation requires understanding that team development is a journey, not a destination. Scrum Masters must:
- Assess Accurately: Understand current team development stage
- Facilitate Appropriately: Match leadership style to team needs
- Build Safety First: Psychological safety enables all other development
- Measure Progress: Track both quantitative and qualitative indicators
- Intervene Thoughtfully: Apply stage-appropriate interventions
- Celebrate Growth: Acknowledge progress and learning throughout the journey
The goal is not just high-performing teams, but sustainable high performance built on strong relationships, psychological safety, and continuous learning. This framework provides the structure and tools to guide teams through their development journey effectively.
This framework combines research-based models with practical scrum implementation experience. Adapt the tools and techniques to fit your specific organizational context and team needs.
Velocity Forecasting Guide: Monte Carlo Methods & Probabilistic Estimation
Table of Contents
- Overview
- Monte Carlo Simulation Fundamentals
- Velocity-Based Forecasting
- Implementation Approaches
- Confidence Intervals & Risk Assessment
- Practical Applications
- Advanced Techniques
- Common Pitfalls
- Case Studies
Overview
Velocity forecasting using Monte Carlo simulation provides probabilistic estimates for sprint and project completion, moving beyond single-point estimates to give stakeholders a range of likely outcomes with associated confidence levels.
Why Probabilistic Forecasting?
- Uncertainty Acknowledgment: Software development is inherently uncertain
- Risk Quantification: Provides probability distributions rather than false precision
- Stakeholder Communication: Better expectation management through confidence intervals
- Decision Support: Enables data-driven planning and resource allocation
Core Principles
- Historical Velocity Patterns: Use actual team performance data
- Statistical Modeling: Apply appropriate probability distributions
- Confidence Intervals: Provide ranges, not single points
- Continuous Calibration: Update forecasts with new data
Monte Carlo Simulation Fundamentals
What is Monte Carlo Simulation?
Monte Carlo simulation uses random sampling to model the probability of different outcomes in systems that cannot be easily predicted due to random variables.
Application to Velocity Forecasting
For each simulation iteration:
1. Sample a velocity value from historical distribution
2. Calculate projected completion time
3. Repeat thousands of times
4. Analyze the distribution of resultsKey Statistical Concepts
Normal Distribution
Most teams' velocity follows a roughly normal distribution after stabilization:
- Mean (μ): Average historical velocity
- Standard Deviation (σ): Velocity variability measure
- 68-95-99.7 Rule: Probability ranges for forecasting
Distribution Characteristics
- Symmetry: Balanced around the mean (normal teams)
- Skewness: Teams with frequent disruptions may show positive skew
- Kurtosis: Measure of "tail heaviness" - extreme outcomes frequency
Velocity-Based Forecasting
Basic Velocity Forecasting Formula
Single Sprint Forecast:
Confidence Interval = μ ± (Z-score × σ)
Where:
- μ = historical mean velocity
- σ = standard deviation of velocity
- Z-score = confidence level multiplierMulti-Sprint Forecast:
Total Points = Σ(sampled_velocity_i) for i = 1 to n sprints
Where each velocity_i is randomly sampled from historical distributionConfidence Level Z-Scores
| Confidence Level | Z-Score | Interpretation |
|---|---|---|
| 50% | 0.67 | Median outcome |
| 70% | 1.04 | Moderate confidence |
| 85% | 1.44 | High confidence |
| 95% | 1.96 | Very high confidence |
| 99% | 2.58 | Extremely high confidence |
Implementation Approaches
1. Simple Historical Distribution Method
def simple_monte_carlo_forecast(velocities, sprints_ahead, iterations=10000):
results = []
for _ in range(iterations):
total_points = sum(random.choice(velocities) for _ in range(sprints_ahead))
results.append(total_points)
return analyze_results(results)Pros: Simple, uses actual data points Cons: Ignores trends, assumes stationary distribution
2. Normal Distribution Method
def normal_distribution_forecast(velocities, sprints_ahead, iterations=10000):
mean_velocity = statistics.mean(velocities)
std_velocity = statistics.stdev(velocities)
results = []
for _ in range(iterations):
total_points = sum(
max(0, random.normalvariate(mean_velocity, std_velocity))
for _ in range(sprints_ahead)
)
results.append(total_points)
return analyze_results(results)Pros: Mathematically clean, handles interpolation Cons: Assumes normal distribution, may generate impossible values
3. Bootstrap Sampling Method
def bootstrap_forecast(velocities, sprints_ahead, iterations=10000):
n = len(velocities)
results = []
for _ in range(iterations):
# Sample with replacement
bootstrap_sample = [random.choice(velocities) for _ in range(n)]
# Calculate statistics from bootstrap sample
mean_vel = statistics.mean(bootstrap_sample)
std_vel = statistics.stdev(bootstrap_sample)
total_points = sum(
max(0, random.normalvariate(mean_vel, std_vel))
for _ in range(sprints_ahead)
)
results.append(total_points)
return analyze_results(results)Pros: Robust to distribution assumptions, accounts for sampling uncertainty Cons: More complex, requires sufficient historical data
Confidence Intervals & Risk Assessment
Interpreting Forecast Results
Percentile-Based Confidence Intervals
def calculate_confidence_intervals(results, confidence_levels=[0.5, 0.7, 0.85, 0.95]):
sorted_results = sorted(results)
intervals = {}
for confidence in confidence_levels:
percentile_index = int(confidence * len(sorted_results))
intervals[f"{int(confidence*100)}%"] = sorted_results[percentile_index]
return intervalsExample Interpretation
For a 6-sprint forecast with results:
- 50%: 120 points (median outcome)
- 70%: 135 points (likely case)
- 85%: 150 points (conservative case)
- 95%: 170 points (very conservative case)
Risk Assessment Framework
Delivery Probability
P(Completion ≤ Target) = (# simulations ≤ target) / total_simulationsRisk Categories
| Probability Range | Risk Level | Recommendation |
|---|---|---|
| > 85% | Low Risk | Proceed with confidence |
| 70-85% | Moderate Risk | Add buffer, monitor closely |
| 50-70% | High Risk | Reduce scope or extend timeline |
| < 50% | Very High Risk | Significant replanning required |
Practical Applications
Sprint Planning
Use velocity forecasting to:
- Set realistic sprint goals
- Communicate uncertainty to Product Owner
- Plan capacity buffers for unknowns
- Identify when to adjust scope
Release Planning
Apply Monte Carlo methods to:
- Estimate feature completion dates
- Plan release milestones
- Assess project schedule risk
- Make go/no-go decisions
Stakeholder Communication
Present forecasts as:
- Range estimates, not single points
- Probability statements ("70% confident we'll deliver X by date Y")
- Risk scenarios with mitigation options
- Visual distributions showing uncertainty
Advanced Techniques
1. Trend-Adjusted Forecasting
Account for improving or declining velocity trends:
def trend_adjusted_forecast(velocities, sprints_ahead):
# Calculate linear trend
x = range(len(velocities))
slope, intercept = calculate_linear_regression(x, velocities)
# Adjust future velocities for trend
adjusted_velocities = []
for i in range(sprints_ahead):
future_sprint = len(velocities) + i
predicted_velocity = slope * future_sprint + intercept
adjusted_velocities.append(predicted_velocity)
return monte_carlo_with_adjusted_velocities(adjusted_velocities)2. Seasonality Adjustments
For teams with seasonal patterns (holidays, budget cycles):
def seasonal_adjustment(velocities, sprint_dates, forecast_dates):
# Identify seasonal patterns
seasonal_factors = calculate_seasonal_factors(velocities, sprint_dates)
# Apply factors to forecast
adjusted_forecast = apply_seasonal_factors(forecast_dates, seasonal_factors)
return adjusted_forecast3. Capacity-Based Modeling
Incorporate team capacity changes:
def capacity_adjusted_forecast(velocities, historical_capacity, future_capacity):
# Calculate velocity per capacity unit
velocity_per_capacity = [v/c for v, c in zip(velocities, historical_capacity)]
baseline_efficiency = statistics.mean(velocity_per_capacity)
# Forecast based on future capacity
future_velocities = [capacity * baseline_efficiency for capacity in future_capacity]
return monte_carlo_forecast(future_velocities)4. Multi-Team Forecasting
For dependencies across teams:
def multi_team_forecast(team_forecasts, dependencies):
# Account for critical path and dependencies
# Use min/max operations for dependent deliveries
# Model coordination overhead
passCommon Pitfalls
1. Insufficient Historical Data
Problem: Using too few sprint data points Solution: Minimum 6-8 sprints for reliable forecasting Mitigation: Use industry benchmarks or similar team data
2. Non-Stationary Data
Problem: Including data from different team compositions or processes Solution: Use only recent, relevant historical data Identification: Look for structural breaks in velocity time series
3. False Precision
Problem: Reporting over-precise estimates (e.g., "23.7 points") Solution: Round to reasonable precision, emphasize ranges Communication: Use language like "approximately" and "around"
4. Ignoring External Factors
Problem: Not accounting for holidays, team changes, external dependencies Solution: Adjust historical data or forecasts for known factors Documentation: Maintain context for each sprint's circumstances
5. Overconfidence in Models
Problem: Treating forecasts as guarantees Solution: Regular calibration against actual outcomes Improvement: Update models based on forecast accuracy
Case Studies
Case Study 1: Stabilizing Team
Situation: New team, first 10 sprints, velocity ranging 15-25 points Approach:
- Used bootstrap sampling due to small sample size
- Applied 30% buffer for team learning curve
- Updated forecast every 2 sprints
Results:
- Initial forecast: 20 ± 8 points per sprint
- Final 3 sprints: 22 ± 3 points per sprint
- Accuracy improved from 60% to 85% confidence bands
Case Study 2: Seasonal Product Team
Situation: E-commerce team with holiday impacts Data: 24 sprints showing clear seasonal patterns Approach:
- Identified seasonal multipliers (0.7x during holidays)
- Used 2-year historical data for seasonal adjustment
- Applied capacity-based modeling for temporary staff
Results:
- Standard model: 40% forecast accuracy during Q4
- Seasonal-adjusted model: 80% forecast accuracy
- Better resource planning and stakeholder communication
Case Study 3: Platform Team with Dependencies
Situation: Infrastructure team supporting multiple product teams Challenge: High variability due to urgent requests and dependencies Approach:
- Separated planned vs. unplanned work velocity
- Used wider confidence intervals (90% vs 70%)
- Implemented buffer management strategy
Results:
- Planned work predictability: 85%
- Total work predictability: 65% (acceptable for context)
- Improved capacity allocation decisions
Tools and Implementation
Recommended Tools
- Python/R: For custom implementation and complex models
- Excel/Google Sheets: For simple implementations and visualization
- Jira/Azure DevOps: For automated data collection
- Specialized Tools: ActionableAgile, Monte Carlo simulation software
Key Metrics to Track
- Forecast Accuracy: How often do actual results fall within predicted ranges?
- Calibration: Do 70% confidence intervals contain 70% of actual results?
- Bias: Are forecasts consistently optimistic or pessimistic?
- Resolution: How precise are the forecasts for decision-making?
Implementation Checklist
- Historical velocity data collection (minimum 6 sprints)
- Data quality validation (outliers, context)
- Distribution analysis (normal, skewed, multi-modal)
- Model selection and parameter estimation
- Validation against held-out data
- Visualization and communication materials
- Regular calibration and model updates
Conclusion
Monte Carlo velocity forecasting transforms uncertain estimates into probabilistic statements that enable better decision-making. Success requires:
- Quality Data: Clean, relevant historical velocity data
- Appropriate Models: Choose methods suited to your team's patterns
- Clear Communication: Present uncertainty honestly to stakeholders
- Continuous Improvement: Calibrate and refine models over time
- Contextual Awareness: Account for team changes, external factors, and business context
The goal is not perfect prediction, but better understanding of uncertainty to make more informed planning decisions.
This guide provides a comprehensive foundation for implementing probabilistic velocity forecasting. Adapt the techniques to your team's specific context and constraints.
#!/usr/bin/env python3
"""
Retrospective Analyzer
Processes retrospective data to track action item completion rates, identify
recurring themes, measure improvement trends, and generate insights for
continuous team improvement.
Usage:
python retrospective_analyzer.py retro_data.json
python retrospective_analyzer.py retro_data.json --format json
"""
import argparse
import json
import re
import statistics
import sys
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Set, Tuple
# ---------------------------------------------------------------------------
# Configuration and Constants
# ---------------------------------------------------------------------------
SENTIMENT_KEYWORDS = {
"positive": [
"good", "great", "excellent", "awesome", "fantastic", "wonderful",
"improved", "better", "success", "achievement", "celebration",
"working well", "effective", "efficient", "smooth", "pleased",
"happy", "satisfied", "proud", "accomplished", "breakthrough"
],
"negative": [
"bad", "terrible", "awful", "horrible", "frustrating", "annoying",
"problem", "issue", "blocker", "impediment", "concern", "worry",
"difficult", "challenging", "struggling", "failing", "broken",
"slow", "delayed", "confused", "unclear", "chaos", "stressed"
],
"neutral": [
"okay", "average", "normal", "standard", "typical", "usual",
"process", "procedure", "meeting", "discussion", "review",
"update", "status", "information", "data", "report"
]
}
THEME_CATEGORIES = {
"communication": [
"communication", "meeting", "standup", "discussion", "feedback",
"information", "clarity", "understanding", "alignment", "sync",
"reporting", "updates", "transparency", "visibility"
],
"process": [
"process", "procedure", "workflow", "methodology", "framework",
"scrum", "agile", "ceremony", "planning", "retrospective",
"review", "estimation", "refinement", "definition of done"
],
"technical": [
"technical", "code", "development", "bug", "testing", "deployment",
"architecture", "infrastructure", "tools", "technology",
"performance", "quality", "automation", "ci/cd", "devops"
],
"team_dynamics": [
"team", "collaboration", "cooperation", "support", "morale",
"motivation", "engagement", "culture", "relationship", "trust",
"conflict", "personality", "workload", "capacity", "burnout"
],
"external": [
"customer", "stakeholder", "management", "product owner", "business",
"requirement", "priority", "deadline", "budget", "resource",
"dependency", "vendor", "third party", "integration"
]
}
ACTION_PRIORITY_KEYWORDS = {
"high": ["urgent", "critical", "asap", "immediately", "blocker", "must"],
"medium": ["important", "should", "needed", "required", "significant"],
"low": ["nice to have", "consider", "explore", "investigate", "eventually"]
}
COMPLETION_STATUS_MAPPING = {
"completed": ["done", "completed", "finished", "resolved", "closed", "achieved"],
"in_progress": ["in progress", "ongoing", "working on", "started", "partial"],
"blocked": ["blocked", "stuck", "waiting", "dependent", "impediment"],
"cancelled": ["cancelled", "dropped", "abandoned", "not needed", "deprioritized"],
"not_started": ["not started", "pending", "todo", "planned", "upcoming"]
}
# ---------------------------------------------------------------------------
# Data Models
# ---------------------------------------------------------------------------
class ActionItem:
"""Represents a single action item from a retrospective."""
def __init__(self, data: Dict[str, Any]):
self.id: str = data.get("id", "")
self.description: str = data.get("description", "")
self.owner: str = data.get("owner", "")
self.priority: str = data.get("priority", "medium").lower()
self.due_date: Optional[str] = data.get("due_date")
self.status: str = data.get("status", "not_started").lower()
self.created_sprint: int = data.get("created_sprint", 0)
self.completed_sprint: Optional[int] = data.get("completed_sprint")
self.category: str = data.get("category", "")
self.effort_estimate: str = data.get("effort_estimate", "medium")
# Normalize status
self.normalized_status = self._normalize_status(self.status)
# Infer priority from description if not explicitly set
if self.priority == "medium":
self.inferred_priority = self._infer_priority(self.description)
else:
self.inferred_priority = self.priority
def _normalize_status(self, status: str) -> str:
"""Normalize status to standard categories."""
status_lower = status.lower().strip()
for category, statuses in COMPLETION_STATUS_MAPPING.items():
if any(s in status_lower for s in statuses):
return category
return "not_started"
def _infer_priority(self, description: str) -> str:
"""Infer priority from description text."""
description_lower = description.lower()
for priority, keywords in ACTION_PRIORITY_KEYWORDS.items():
if any(keyword in description_lower for keyword in keywords):
return priority
return "medium"
@property
def is_completed(self) -> bool:
return self.normalized_status == "completed"
@property
def is_overdue(self) -> bool:
if not self.due_date:
return False
try:
due_date = datetime.strptime(self.due_date, "%Y-%m-%d")
return datetime.now() > due_date and not self.is_completed
except ValueError:
return False
class RetrospectiveData:
"""Represents data from a single retrospective session."""
def __init__(self, data: Dict[str, Any]):
self.sprint_number: int = data.get("sprint_number", 0)
self.date: str = data.get("date", "")
self.facilitator: str = data.get("facilitator", "")
self.attendees: List[str] = data.get("attendees", [])
self.duration_minutes: int = data.get("duration_minutes", 0)
# Retrospective categories
self.went_well: List[str] = data.get("went_well", [])
self.to_improve: List[str] = data.get("to_improve", [])
self.action_items_data: List[Dict[str, Any]] = data.get("action_items", [])
# Create action items
self.action_items: List[ActionItem] = [
ActionItem({**item, "created_sprint": self.sprint_number})
for item in self.action_items_data
]
# Calculate metrics
self._calculate_metrics()
def _calculate_metrics(self):
"""Calculate retrospective session metrics."""
self.total_items = len(self.went_well) + len(self.to_improve)
self.action_items_count = len(self.action_items)
self.attendance_rate = len(self.attendees) / max(1, 5) # Assume team of 5
# Sentiment analysis
self.sentiment_scores = self._analyze_sentiment()
# Theme analysis
self.themes = self._extract_themes()
def _analyze_sentiment(self) -> Dict[str, float]:
"""Analyze sentiment of retrospective items."""
all_text = " ".join(self.went_well + self.to_improve).lower()
sentiment_scores = {}
for sentiment, keywords in SENTIMENT_KEYWORDS.items():
count = sum(1 for keyword in keywords if keyword in all_text)
sentiment_scores[sentiment] = count
# Normalize to percentages
total_sentiment = sum(sentiment_scores.values())
if total_sentiment > 0:
for sentiment in sentiment_scores:
sentiment_scores[sentiment] = sentiment_scores[sentiment] / total_sentiment
return sentiment_scores
def _extract_themes(self) -> Dict[str, int]:
"""Extract themes from retrospective items."""
all_text = " ".join(self.went_well + self.to_improve).lower()
theme_counts = {}
for theme, keywords in THEME_CATEGORIES.items():
count = sum(1 for keyword in keywords if keyword in all_text)
if count > 0:
theme_counts[theme] = count
return theme_counts
class RetroAnalysisResult:
"""Complete retrospective analysis results."""
def __init__(self):
self.summary: Dict[str, Any] = {}
self.action_item_analysis: Dict[str, Any] = {}
self.theme_analysis: Dict[str, Any] = {}
self.improvement_trends: Dict[str, Any] = {}
self.recommendations: List[str] = []
# ---------------------------------------------------------------------------
# Analysis Functions
# ---------------------------------------------------------------------------
def analyze_action_item_completion(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Analyze action item completion rates and patterns."""
all_action_items = []
for retro in retros:
all_action_items.extend(retro.action_items)
if not all_action_items:
return {
"total_action_items": 0,
"completion_rate": 0.0,
"average_completion_time": 0.0
}
# Overall completion statistics
completed_items = [item for item in all_action_items if item.is_completed]
completion_rate = len(completed_items) / len(all_action_items)
# Completion time analysis
completion_times = []
for item in completed_items:
if item.completed_sprint and item.created_sprint:
completion_time = item.completed_sprint - item.created_sprint
if completion_time >= 0:
completion_times.append(completion_time)
avg_completion_time = statistics.mean(completion_times) if completion_times else 0.0
# Status distribution
status_counts = Counter(item.normalized_status for item in all_action_items)
# Priority analysis
priority_completion = {}
for priority in ["high", "medium", "low"]:
priority_items = [item for item in all_action_items if item.inferred_priority == priority]
if priority_items:
priority_completed = sum(1 for item in priority_items if item.is_completed)
priority_completion[priority] = {
"total": len(priority_items),
"completed": priority_completed,
"completion_rate": priority_completed / len(priority_items)
}
# Owner analysis
owner_performance = defaultdict(lambda: {"total": 0, "completed": 0})
for item in all_action_items:
if item.owner:
owner_performance[item.owner]["total"] += 1
if item.is_completed:
owner_performance[item.owner]["completed"] += 1
for owner in owner_performance:
owner_data = owner_performance[owner]
owner_data["completion_rate"] = owner_data["completed"] / owner_data["total"]
# Overdue items
overdue_items = [item for item in all_action_items if item.is_overdue]
return {
"total_action_items": len(all_action_items),
"completion_rate": completion_rate,
"completed_items": len(completed_items),
"average_completion_time": avg_completion_time,
"status_distribution": dict(status_counts),
"priority_analysis": priority_completion,
"owner_performance": dict(owner_performance),
"overdue_items": len(overdue_items),
"overdue_rate": len(overdue_items) / len(all_action_items) if all_action_items else 0.0
}
def analyze_recurring_themes(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Identify recurring themes across retrospectives."""
theme_evolution = defaultdict(list)
sentiment_evolution = defaultdict(list)
# Track themes over time
for retro in retros:
sprint = retro.sprint_number
# Theme tracking
for theme, count in retro.themes.items():
theme_evolution[theme].append((sprint, count))
# Sentiment tracking
for sentiment, score in retro.sentiment_scores.items():
sentiment_evolution[sentiment].append((sprint, score))
# Identify recurring themes (appear in >50% of retros)
recurring_threshold = len(retros) * 0.5
recurring_themes = {}
for theme, occurrences in theme_evolution.items():
if len(occurrences) >= recurring_threshold:
sprints, counts = zip(*occurrences)
recurring_themes[theme] = {
"frequency": len(occurrences) / len(retros),
"average_mentions": statistics.mean(counts),
"trend": _calculate_trend(list(counts)),
"first_appearance": min(sprints),
"last_appearance": max(sprints),
"total_mentions": sum(counts)
}
# Sentiment trend analysis
sentiment_trends = {}
for sentiment, scores_by_sprint in sentiment_evolution.items():
if len(scores_by_sprint) >= 3: # Need at least 3 data points
_, scores = zip(*scores_by_sprint)
sentiment_trends[sentiment] = {
"average_score": statistics.mean(scores),
"trend": _calculate_trend(list(scores)),
"volatility": statistics.stdev(scores) if len(scores) > 1 else 0.0
}
# Identify persistent issues (negative themes that recur)
persistent_issues = []
for theme, data in recurring_themes.items():
if theme in ["technical", "process", "external"] and data["frequency"] > 0.6:
if data["trend"]["direction"] in ["stable", "increasing"]:
persistent_issues.append({
"theme": theme,
"frequency": data["frequency"],
"severity": data["average_mentions"],
"trend": data["trend"]["direction"]
})
return {
"recurring_themes": recurring_themes,
"sentiment_trends": sentiment_trends,
"persistent_issues": persistent_issues,
"total_themes_identified": len(theme_evolution),
"themes_per_retro": sum(len(r.themes) for r in retros) / len(retros) if retros else 0
}
def analyze_improvement_trends(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Analyze improvement trends across retrospectives."""
if len(retros) < 3:
return {"error": "Need at least 3 retrospectives for trend analysis"}
# Sort retrospectives by sprint number
sorted_retros = sorted(retros, key=lambda r: r.sprint_number)
# Track various metrics over time
metrics_over_time = {
"action_items_per_retro": [len(r.action_items) for r in sorted_retros],
"attendance_rate": [r.attendance_rate for r in sorted_retros],
"duration": [r.duration_minutes for r in sorted_retros],
"positive_sentiment": [r.sentiment_scores.get("positive", 0) for r in sorted_retros],
"negative_sentiment": [r.sentiment_scores.get("negative", 0) for r in sorted_retros],
"total_items_discussed": [r.total_items for r in sorted_retros]
}
# Calculate trends for each metric
trend_analysis = {}
for metric_name, values in metrics_over_time.items():
if len(values) >= 3:
trend_analysis[metric_name] = {
"values": values,
"trend": _calculate_trend(values),
"average": statistics.mean(values),
"latest": values[-1],
"change_from_first": ((values[-1] - values[0]) / values[0]) if values[0] != 0 else 0
}
# Action item completion trend
completion_rates_by_sprint = []
for i, retro in enumerate(sorted_retros):
if i > 0: # Skip first retro as it has no previous action items to complete
prev_retro = sorted_retros[i-1]
if prev_retro.action_items:
completed_count = sum(1 for item in prev_retro.action_items
if item.is_completed and item.completed_sprint == retro.sprint_number)
completion_rate = completed_count / len(prev_retro.action_items)
completion_rates_by_sprint.append(completion_rate)
if completion_rates_by_sprint:
trend_analysis["action_item_completion"] = {
"values": completion_rates_by_sprint,
"trend": _calculate_trend(completion_rates_by_sprint),
"average": statistics.mean(completion_rates_by_sprint),
"latest": completion_rates_by_sprint[-1] if completion_rates_by_sprint else 0
}
# Team maturity indicators
maturity_score = _calculate_team_maturity(sorted_retros)
return {
"trend_analysis": trend_analysis,
"team_maturity_score": maturity_score,
"retrospective_quality_trend": _assess_retrospective_quality_trend(sorted_retros),
"improvement_velocity": _calculate_improvement_velocity(sorted_retros)
}
def _calculate_trend(values: List[float]) -> Dict[str, Any]:
"""Calculate trend direction and strength for a series of values."""
if len(values) < 2:
return {"direction": "insufficient_data", "strength": 0.0}
# Simple linear regression
n = len(values)
x_values = list(range(n))
x_mean = sum(x_values) / n
y_mean = sum(values) / n
numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, values))
denominator = sum((x - x_mean) ** 2 for x in x_values)
if denominator == 0:
slope = 0
else:
slope = numerator / denominator
# Calculate correlation coefficient for trend strength
try:
correlation = statistics.correlation(x_values, values) if n > 2 else 0.0
except statistics.StatisticsError:
correlation = 0.0
# Determine trend direction
if abs(slope) < 0.01: # Practically no change
direction = "stable"
elif slope > 0:
direction = "increasing"
else:
direction = "decreasing"
return {
"direction": direction,
"slope": slope,
"strength": abs(correlation),
"correlation": correlation
}
def _calculate_team_maturity(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Calculate team maturity based on retrospective patterns."""
if len(retros) < 3:
return {"score": 50, "level": "developing"}
maturity_indicators = {
"action_item_focus": 0, # Fewer but higher quality action items
"sentiment_balance": 0, # Balanced positive/negative sentiment
"theme_consistency": 0, # Consistent themes without chaos
"participation": 0, # High attendance rates
"follow_through": 0 # Good action item completion
}
# Action item focus (quality over quantity)
avg_action_items = sum(len(r.action_items) for r in retros) / len(retros)
if 2 <= avg_action_items <= 5: # Sweet spot
maturity_indicators["action_item_focus"] = 100
elif avg_action_items < 2 or avg_action_items > 8:
maturity_indicators["action_item_focus"] = 30
else:
maturity_indicators["action_item_focus"] = 70
# Sentiment balance
avg_positive = sum(r.sentiment_scores.get("positive", 0) for r in retros) / len(retros)
avg_negative = sum(r.sentiment_scores.get("negative", 0) for r in retros) / len(retros)
if 0.3 <= avg_positive <= 0.6 and 0.2 <= avg_negative <= 0.4:
maturity_indicators["sentiment_balance"] = 100
else:
maturity_indicators["sentiment_balance"] = 50
# Participation
avg_attendance = sum(r.attendance_rate for r in retros) / len(retros)
maturity_indicators["participation"] = min(100, avg_attendance * 100)
# Theme consistency (not too chaotic, not too narrow)
avg_themes = sum(len(r.themes) for r in retros) / len(retros)
if 2 <= avg_themes <= 4:
maturity_indicators["theme_consistency"] = 100
else:
maturity_indicators["theme_consistency"] = 70
# Follow-through (estimated from action item patterns)
# This is simplified - in reality would track actual completion
recent_retros = retros[-3:] if len(retros) >= 3 else retros
avg_recent_actions = sum(len(r.action_items) for r in recent_retros) / len(recent_retros)
if avg_recent_actions <= 3: # Fewer action items might indicate better follow-through
maturity_indicators["follow_through"] = 80
else:
maturity_indicators["follow_through"] = 60
# Calculate overall maturity score
overall_score = sum(maturity_indicators.values()) / len(maturity_indicators)
if overall_score >= 85:
level = "high_performing"
elif overall_score >= 70:
level = "performing"
elif overall_score >= 55:
level = "developing"
else:
level = "forming"
return {
"score": overall_score,
"level": level,
"indicators": maturity_indicators
}
def _assess_retrospective_quality_trend(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Assess the quality trend of retrospectives over time."""
quality_scores = []
for retro in retros:
score = 0
# Duration appropriateness (60-90 minutes is ideal)
if 60 <= retro.duration_minutes <= 90:
score += 25
elif 45 <= retro.duration_minutes <= 120:
score += 15
else:
score += 5
# Participation
score += min(25, retro.attendance_rate * 25)
# Balance of content
went_well_count = len(retro.went_well)
to_improve_count = len(retro.to_improve)
total_items = went_well_count + to_improve_count
if total_items > 0:
balance = min(went_well_count, to_improve_count) / total_items
score += balance * 25
# Action items quality (not too many, not too few)
action_count = len(retro.action_items)
if 2 <= action_count <= 5:
score += 25
elif 1 <= action_count <= 7:
score += 15
else:
score += 5
quality_scores.append(score)
if len(quality_scores) >= 2:
trend = _calculate_trend(quality_scores)
else:
trend = {"direction": "insufficient_data", "strength": 0.0}
return {
"quality_scores": quality_scores,
"average_quality": statistics.mean(quality_scores),
"trend": trend,
"latest_quality": quality_scores[-1] if quality_scores else 0
}
def _calculate_improvement_velocity(retros: List[RetrospectiveData]) -> Dict[str, Any]:
"""Calculate how quickly the team improves based on retrospective patterns."""
if len(retros) < 4:
return {"velocity": "insufficient_data"}
# Look at theme evolution - are persistent issues being resolved?
theme_counts = defaultdict(list)
for retro in retros:
for theme, count in retro.themes.items():
theme_counts[theme].append(count)
resolved_themes = 0
persistent_themes = 0
for theme, counts in theme_counts.items():
if len(counts) >= 3:
recent_avg = statistics.mean(counts[-2:])
early_avg = statistics.mean(counts[:2])
if recent_avg < early_avg * 0.7: # 30% reduction
resolved_themes += 1
elif recent_avg > early_avg * 0.9: # Still persistent
persistent_themes += 1
total_themes = resolved_themes + persistent_themes
if total_themes > 0:
resolution_rate = resolved_themes / total_themes
else:
resolution_rate = 0.5 # Neutral if no data
# Action item completion trends
if len(retros) >= 4:
recent_action_density = sum(len(r.action_items) for r in retros[-2:]) / 2
early_action_density = sum(len(r.action_items) for r in retros[:2]) / 2
action_efficiency = 1.0
if early_action_density > 0:
action_efficiency = min(1.0, early_action_density / max(recent_action_density, 1))
else:
action_efficiency = 0.5
# Overall velocity score
velocity_score = (resolution_rate * 0.6) + (action_efficiency * 0.4)
if velocity_score >= 0.8:
velocity = "high"
elif velocity_score >= 0.6:
velocity = "moderate"
elif velocity_score >= 0.4:
velocity = "low"
else:
velocity = "stagnant"
return {
"velocity": velocity,
"velocity_score": velocity_score,
"theme_resolution_rate": resolution_rate,
"action_efficiency": action_efficiency,
"resolved_themes": resolved_themes,
"persistent_themes": persistent_themes
}
def generate_recommendations(result: RetroAnalysisResult) -> List[str]:
"""Generate actionable recommendations based on retrospective analysis."""
recommendations = []
# Action item recommendations
action_analysis = result.action_item_analysis
completion_rate = action_analysis.get("completion_rate", 0)
if completion_rate < 0.5:
recommendations.append("CRITICAL: Low action item completion rate (<50%). Reduce action items per retro and focus on realistic, achievable goals.")
elif completion_rate < 0.7:
recommendations.append("Improve action item follow-through. Consider assigning owners and due dates more systematically.")
elif completion_rate > 0.9:
recommendations.append("Excellent action item completion! Consider taking on more ambitious improvement initiatives.")
overdue_rate = action_analysis.get("overdue_rate", 0)
if overdue_rate > 0.3:
recommendations.append("High overdue rate suggests unrealistic timelines. Review estimation and prioritization process.")
# Theme recommendations
theme_analysis = result.theme_analysis
persistent_issues = theme_analysis.get("persistent_issues", [])
if len(persistent_issues) >= 2:
recommendations.append(f"Address {len(persistent_issues)} persistent issues that keep recurring across retrospectives.")
for issue in persistent_issues[:2]: # Top 2 issues
recommendations.append(f"Focus on resolving recurring {issue['theme']} issues (appears in {issue['frequency']:.0%} of retros).")
# Trend-based recommendations
improvement_trends = result.improvement_trends
if "team_maturity_score" in improvement_trends:
maturity = improvement_trends["team_maturity_score"]
level = maturity.get("level", "forming")
if level == "forming":
recommendations.append("Team is in forming stage. Focus on establishing basic retrospective disciplines and psychological safety.")
elif level == "developing":
recommendations.append("Team is developing. Work on action item follow-through and deeper root cause analysis.")
elif level == "performing":
recommendations.append("Good team maturity. Consider advanced techniques like continuous improvement tracking.")
elif level == "high_performing":
recommendations.append("Excellent retrospective maturity! Share practices with other teams and focus on innovation.")
# Quality recommendations
if "retrospective_quality_trend" in improvement_trends:
quality_trend = improvement_trends["retrospective_quality_trend"]
avg_quality = quality_trend.get("average_quality", 50)
if avg_quality < 60:
recommendations.append("Retrospective quality is below average. Review facilitation techniques and engagement strategies.")
trend_direction = quality_trend.get("trend", {}).get("direction", "stable")
if trend_direction == "decreasing":
recommendations.append("Retrospective quality is declining. Consider changing facilitation approach or addressing team engagement issues.")
return recommendations
# ---------------------------------------------------------------------------
# Main Analysis Function
# ---------------------------------------------------------------------------
def analyze_retrospectives(data: Dict[str, Any]) -> RetroAnalysisResult:
"""Perform comprehensive retrospective analysis."""
result = RetroAnalysisResult()
try:
# Parse retrospective data
retro_records = data.get("retrospectives", [])
retros = [RetrospectiveData(record) for record in retro_records]
if not retros:
raise ValueError("No retrospective data found")
# Sort by sprint number
retros.sort(key=lambda r: r.sprint_number)
# Basic summary
result.summary = {
"total_retrospectives": len(retros),
"date_range": {
"first": retros[0].date if retros else "",
"last": retros[-1].date if retros else "",
"span_sprints": retros[-1].sprint_number - retros[0].sprint_number + 1 if retros else 0
},
"average_duration": statistics.mean([r.duration_minutes for r in retros if r.duration_minutes > 0]),
"average_attendance": statistics.mean([r.attendance_rate for r in retros]),
}
# Action item analysis
result.action_item_analysis = analyze_action_item_completion(retros)
# Theme analysis
result.theme_analysis = analyze_recurring_themes(retros)
# Improvement trends
result.improvement_trends = analyze_improvement_trends(retros)
# Generate recommendations
result.recommendations = generate_recommendations(result)
except Exception as e:
result.summary = {"error": str(e)}
return result
# ---------------------------------------------------------------------------
# Output Formatting
# ---------------------------------------------------------------------------
def format_text_output(result: RetroAnalysisResult) -> str:
"""Format analysis results as readable text report."""
lines = []
lines.append("="*60)
lines.append("RETROSPECTIVE ANALYSIS REPORT")
lines.append("="*60)
lines.append("")
if "error" in result.summary:
lines.append(f"ERROR: {result.summary['error']}")
return "\n".join(lines)
# Summary section
summary = result.summary
lines.append("RETROSPECTIVE SUMMARY")
lines.append("-"*30)
lines.append(f"Total Retrospectives: {summary['total_retrospectives']}")
lines.append(f"Sprint Range: {summary['date_range']['span_sprints']} sprints")
lines.append(f"Average Duration: {summary.get('average_duration', 0):.0f} minutes")
lines.append(f"Average Attendance: {summary.get('average_attendance', 0):.1%}")
lines.append("")
# Action item analysis
action_analysis = result.action_item_analysis
lines.append("ACTION ITEM ANALYSIS")
lines.append("-"*30)
lines.append(f"Total Action Items: {action_analysis.get('total_action_items', 0)}")
lines.append(f"Completion Rate: {action_analysis.get('completion_rate', 0):.1%}")
lines.append(f"Average Completion Time: {action_analysis.get('average_completion_time', 0):.1f} sprints")
lines.append(f"Overdue Items: {action_analysis.get('overdue_items', 0)} ({action_analysis.get('overdue_rate', 0):.1%})")
priority_analysis = action_analysis.get('priority_analysis', {})
if priority_analysis:
lines.append("Priority-based completion rates:")
for priority, data in priority_analysis.items():
lines.append(f" {priority.title()}: {data['completion_rate']:.1%} ({data['completed']}/{data['total']})")
lines.append("")
# Theme analysis
theme_analysis = result.theme_analysis
lines.append("THEME ANALYSIS")
lines.append("-"*30)
recurring_themes = theme_analysis.get("recurring_themes", {})
if recurring_themes:
lines.append("Top recurring themes:")
sorted_themes = sorted(recurring_themes.items(), key=lambda x: x[1]['frequency'], reverse=True)
for theme, data in sorted_themes[:5]:
lines.append(f" {theme.replace('_', ' ').title()}: {data['frequency']:.1%} frequency, {data['trend']['direction']} trend")
persistent_issues = theme_analysis.get("persistent_issues", [])
if persistent_issues:
lines.append("Persistent issues requiring attention:")
for issue in persistent_issues:
lines.append(f" {issue['theme'].replace('_', ' ').title()}: {issue['frequency']:.1%} frequency")
lines.append("")
# Improvement trends
improvement_trends = result.improvement_trends
if "team_maturity_score" in improvement_trends:
maturity = improvement_trends["team_maturity_score"]
lines.append("TEAM MATURITY")
lines.append("-"*30)
lines.append(f"Maturity Level: {maturity['level'].replace('_', ' ').title()}")
lines.append(f"Maturity Score: {maturity['score']:.0f}/100")
lines.append("")
if "improvement_velocity" in improvement_trends:
velocity = improvement_trends["improvement_velocity"]
lines.append("IMPROVEMENT VELOCITY")
lines.append("-"*30)
lines.append(f"Velocity: {velocity['velocity'].title()}")
lines.append(f"Theme Resolution Rate: {velocity.get('theme_resolution_rate', 0):.1%}")
lines.append("")
# Recommendations
if result.recommendations:
lines.append("RECOMMENDATIONS")
lines.append("-"*30)
for i, rec in enumerate(result.recommendations, 1):
lines.append(f"{i}. {rec}")
return "\n".join(lines)
def format_json_output(result: RetroAnalysisResult) -> Dict[str, Any]:
"""Format analysis results as JSON."""
return {
"summary": result.summary,
"action_item_analysis": result.action_item_analysis,
"theme_analysis": result.theme_analysis,
"improvement_trends": result.improvement_trends,
"recommendations": result.recommendations,
}
# ---------------------------------------------------------------------------
# CLI Interface
# ---------------------------------------------------------------------------
def main() -> int:
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Analyze retrospective data for continuous improvement insights"
)
parser.add_argument(
"data_file",
help="JSON file containing retrospective data"
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)"
)
args = parser.parse_args()
try:
# Load and validate data
with open(args.data_file, 'r') as f:
data = json.load(f)
# Perform analysis
result = analyze_retrospectives(data)
# Output results
if args.format == "json":
output = format_json_output(result)
print(json.dumps(output, indent=2))
else:
output = format_text_output(result)
print(output)
return 0
except FileNotFoundError:
print(f"Error: File '{args.data_file}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.data_file}': {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main()) #!/usr/bin/env python3
"""
Sprint Health Scorer
Scores sprint health across multiple dimensions including commitment reliability,
scope creep, blocker resolution time, ceremony attendance, and story completion
distribution. Produces composite health scores with actionable recommendations.
Usage:
python sprint_health_scorer.py sprint_data.json
python sprint_health_scorer.py sprint_data.json --format json
"""
import argparse
import json
import statistics
import sys
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# Scoring Configuration
# ---------------------------------------------------------------------------
HEALTH_DIMENSIONS = {
"commitment_reliability": {
"weight": 0.25,
"excellent_threshold": 0.95, # 95%+ commitment achievement
"good_threshold": 0.85, # 85%+ commitment achievement
"poor_threshold": 0.70, # Below 70% is poor
},
"scope_stability": {
"weight": 0.20,
"excellent_threshold": 0.05, # ≤5% scope change
"good_threshold": 0.15, # ≤15% scope change
"poor_threshold": 0.30, # >30% scope change is poor
},
"blocker_resolution": {
"weight": 0.15,
"excellent_threshold": 1.0, # ≤1 day average resolution
"good_threshold": 3.0, # ≤3 days average resolution
"poor_threshold": 7.0, # >7 days is poor
},
"ceremony_engagement": {
"weight": 0.15,
"excellent_threshold": 0.95, # 95%+ attendance
"good_threshold": 0.85, # 85%+ attendance
"poor_threshold": 0.70, # Below 70% is poor
},
"story_completion_distribution": {
"weight": 0.15,
"excellent_threshold": 0.80, # 80%+ stories fully completed
"good_threshold": 0.65, # 65%+ stories completed
"poor_threshold": 0.50, # Below 50% is poor
},
"velocity_predictability": {
"weight": 0.10,
"excellent_threshold": 0.10, # ≤10% CV
"good_threshold": 0.20, # ≤20% CV
"poor_threshold": 0.35, # >35% CV is poor
}
}
OVERALL_HEALTH_THRESHOLDS = {
"excellent": 85,
"good": 70,
"fair": 55,
"poor": 40,
}
STORY_STATUS_MAPPING = {
"completed": ["done", "completed", "closed", "resolved"],
"in_progress": ["in progress", "in_progress", "development", "testing"],
"blocked": ["blocked", "impediment", "waiting"],
"not_started": ["todo", "to do", "backlog", "new", "open"],
}
# ---------------------------------------------------------------------------
# Data Models
# ---------------------------------------------------------------------------
class Story:
"""Represents a user story within a sprint."""
def __init__(self, data: Dict[str, Any]):
self.id: str = data.get("id", "")
self.title: str = data.get("title", "")
self.points: int = data.get("points", 0)
self.status: str = data.get("status", "").lower()
self.assigned_to: str = data.get("assigned_to", "")
self.created_date: str = data.get("created_date", "")
self.completed_date: Optional[str] = data.get("completed_date")
self.blocked_days: int = data.get("blocked_days", 0)
self.priority: str = data.get("priority", "medium")
# Normalize status
self.normalized_status = self._normalize_status(self.status)
def _normalize_status(self, status: str) -> str:
"""Normalize status to standard categories."""
status_lower = status.lower().strip()
for category, statuses in STORY_STATUS_MAPPING.items():
if status_lower in statuses:
return category
return "unknown"
@property
def is_completed(self) -> bool:
return self.normalized_status == "completed"
@property
def is_blocked(self) -> bool:
return self.normalized_status == "blocked" or self.blocked_days > 0
class SprintHealthData:
"""Comprehensive sprint health data model."""
def __init__(self, data: Dict[str, Any]):
self.sprint_number: int = data.get("sprint_number", 0)
self.sprint_name: str = data.get("sprint_name", "")
self.start_date: str = data.get("start_date", "")
self.end_date: str = data.get("end_date", "")
self.team_size: int = data.get("team_size", 0)
self.working_days: int = data.get("working_days", 10)
# Commitment and delivery
self.planned_points: int = data.get("planned_points", 0)
self.completed_points: int = data.get("completed_points", 0)
self.added_points: int = data.get("added_points", 0)
self.removed_points: int = data.get("removed_points", 0)
# Stories
story_data = data.get("stories", [])
self.stories: List[Story] = [Story(story) for story in story_data]
# Blockers
self.blockers: List[Dict[str, Any]] = data.get("blockers", [])
# Ceremonies
self.ceremonies: Dict[str, Any] = data.get("ceremonies", {})
# Calculate derived metrics
self._calculate_derived_metrics()
def _calculate_derived_metrics(self):
"""Calculate derived health metrics."""
# Commitment reliability
self.commitment_ratio = (
self.completed_points / max(self.planned_points, 1)
)
# Scope change
total_scope_change = self.added_points + self.removed_points
self.scope_change_ratio = total_scope_change / max(self.planned_points, 1)
# Story completion distribution
total_stories = len(self.stories)
if total_stories > 0:
completed_stories = sum(1 for story in self.stories if story.is_completed)
self.story_completion_ratio = completed_stories / total_stories
else:
self.story_completion_ratio = 0.0
# Blocked stories analysis
blocked_stories = [story for story in self.stories if story.is_blocked]
self.blocked_stories_count = len(blocked_stories)
self.blocked_points = sum(story.points for story in blocked_stories)
class HealthScoreResult:
"""Complete health scoring results."""
def __init__(self):
self.dimension_scores: Dict[str, Dict[str, Any]] = {}
self.overall_score: float = 0.0
self.health_grade: str = ""
self.trend_analysis: Dict[str, Any] = {}
self.recommendations: List[str] = []
self.detailed_metrics: Dict[str, Any] = {}
# ---------------------------------------------------------------------------
# Scoring Functions
# ---------------------------------------------------------------------------
def score_commitment_reliability(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score commitment reliability across sprints."""
if not sprints:
return {"score": 0, "grade": "insufficient_data"}
commitment_ratios = [sprint.commitment_ratio for sprint in sprints]
avg_commitment = statistics.mean(commitment_ratios)
consistency = 1.0 - (statistics.stdev(commitment_ratios) if len(commitment_ratios) > 1 else 0)
# Score based on average achievement and consistency
config = HEALTH_DIMENSIONS["commitment_reliability"]
base_score = _calculate_dimension_score(avg_commitment, config)
# Penalty for inconsistency
consistency_bonus = min(10, consistency * 10)
final_score = min(100, base_score + consistency_bonus)
return {
"score": final_score,
"grade": _score_to_grade(final_score),
"average_commitment": avg_commitment,
"consistency": consistency,
"commitment_ratios": commitment_ratios,
"details": f"Average commitment: {avg_commitment:.1%}, Consistency: {consistency:.1%}"
}
def score_scope_stability(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score scope stability (low scope change is better)."""
if not sprints:
return {"score": 0, "grade": "insufficient_data"}
scope_change_ratios = [sprint.scope_change_ratio for sprint in sprints]
avg_scope_change = statistics.mean(scope_change_ratios)
# For scope change, lower is better, so invert the scoring
config = HEALTH_DIMENSIONS["scope_stability"]
if avg_scope_change <= config["excellent_threshold"]:
score = 90 + (config["excellent_threshold"] - avg_scope_change) * 200
elif avg_scope_change <= config["good_threshold"]:
score = 70 + (config["good_threshold"] - avg_scope_change) * 200
elif avg_scope_change <= config["poor_threshold"]:
score = 40 + (config["poor_threshold"] - avg_scope_change) * 200
else:
score = max(0, 40 - (avg_scope_change - config["poor_threshold"]) * 100)
score = min(100, max(0, score))
return {
"score": score,
"grade": _score_to_grade(score),
"average_scope_change": avg_scope_change,
"scope_change_ratios": scope_change_ratios,
"details": f"Average scope change: {avg_scope_change:.1%}"
}
def score_blocker_resolution(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score blocker resolution efficiency."""
if not sprints:
return {"score": 0, "grade": "insufficient_data"}
all_blockers = []
for sprint in sprints:
all_blockers.extend(sprint.blockers)
if not all_blockers:
return {
"score": 100,
"grade": "excellent",
"average_resolution_time": 0,
"details": "No blockers reported"
}
# Calculate average resolution time
resolution_times = []
for blocker in all_blockers:
resolution_time = blocker.get("resolution_days", 0)
if resolution_time > 0:
resolution_times.append(resolution_time)
if not resolution_times:
return {"score": 50, "grade": "fair", "details": "No resolution time data"}
avg_resolution_time = statistics.mean(resolution_times)
# Score based on resolution time (lower is better)
config = HEALTH_DIMENSIONS["blocker_resolution"]
if avg_resolution_time <= config["excellent_threshold"]:
score = 95
elif avg_resolution_time <= config["good_threshold"]:
score = 80 - (avg_resolution_time - config["excellent_threshold"]) * 10
elif avg_resolution_time <= config["poor_threshold"]:
score = 60 - (avg_resolution_time - config["good_threshold"]) * 5
else:
score = max(20, 40 - (avg_resolution_time - config["poor_threshold"]) * 3)
return {
"score": score,
"grade": _score_to_grade(score),
"average_resolution_time": avg_resolution_time,
"total_blockers": len(all_blockers),
"resolved_blockers": len(resolution_times),
"details": f"Average resolution: {avg_resolution_time:.1f} days from {len(all_blockers)} blockers"
}
def score_ceremony_engagement(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score team engagement in scrum ceremonies."""
if not sprints:
return {"score": 0, "grade": "insufficient_data"}
ceremony_scores = []
ceremony_details = {}
for sprint in sprints:
ceremonies = sprint.ceremonies
sprint_ceremony_scores = []
for ceremony_name, ceremony_data in ceremonies.items():
if isinstance(ceremony_data, dict):
attendance_rate = ceremony_data.get("attendance_rate", 0)
engagement_score = ceremony_data.get("engagement_score", 0)
# Weight attendance more heavily than engagement
ceremony_score = (attendance_rate * 0.7) + (engagement_score * 0.3)
sprint_ceremony_scores.append(ceremony_score)
if ceremony_name not in ceremony_details:
ceremony_details[ceremony_name] = []
ceremony_details[ceremony_name].append({
"sprint": sprint.sprint_number,
"attendance": attendance_rate,
"engagement": engagement_score,
"score": ceremony_score
})
if sprint_ceremony_scores:
ceremony_scores.append(statistics.mean(sprint_ceremony_scores))
if not ceremony_scores:
return {"score": 50, "grade": "fair", "details": "No ceremony data available"}
avg_ceremony_score = statistics.mean(ceremony_scores)
config = HEALTH_DIMENSIONS["ceremony_engagement"]
score = _calculate_dimension_score(avg_ceremony_score, config)
return {
"score": score,
"grade": _score_to_grade(score),
"average_ceremony_score": avg_ceremony_score,
"ceremony_details": ceremony_details,
"details": f"Average ceremony engagement: {avg_ceremony_score:.1%}"
}
def score_story_completion_distribution(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score how well stories are completed vs. partially done."""
if not sprints:
return {"score": 0, "grade": "insufficient_data"}
completion_ratios = []
story_analysis = {
"total_stories": 0,
"completed_stories": 0,
"blocked_stories": 0,
"partial_completion": 0
}
for sprint in sprints:
if sprint.stories:
sprint_completion = sprint.story_completion_ratio
completion_ratios.append(sprint_completion)
story_analysis["total_stories"] += len(sprint.stories)
story_analysis["completed_stories"] += sum(1 for s in sprint.stories if s.is_completed)
story_analysis["blocked_stories"] += sum(1 for s in sprint.stories if s.is_blocked)
if not completion_ratios:
return {"score": 50, "grade": "fair", "details": "No story data available"}
avg_completion_ratio = statistics.mean(completion_ratios)
config = HEALTH_DIMENSIONS["story_completion_distribution"]
score = _calculate_dimension_score(avg_completion_ratio, config)
# Penalty for high number of blocked stories
if story_analysis["total_stories"] > 0:
blocked_ratio = story_analysis["blocked_stories"] / story_analysis["total_stories"]
if blocked_ratio > 0.20: # More than 20% blocked
score = max(0, score - (blocked_ratio - 0.20) * 100)
return {
"score": score,
"grade": _score_to_grade(score),
"average_completion_ratio": avg_completion_ratio,
"story_analysis": story_analysis,
"details": f"Average story completion: {avg_completion_ratio:.1%}"
}
def score_velocity_predictability(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Score velocity predictability based on coefficient of variation."""
if len(sprints) < 2:
return {"score": 50, "grade": "fair", "details": "Insufficient sprints for predictability analysis"}
velocities = [sprint.completed_points for sprint in sprints]
mean_velocity = statistics.mean(velocities)
if mean_velocity == 0:
return {"score": 0, "grade": "poor", "details": "No velocity recorded"}
velocity_cv = statistics.stdev(velocities) / mean_velocity
# Lower CV is better for predictability
config = HEALTH_DIMENSIONS["velocity_predictability"]
if velocity_cv <= config["excellent_threshold"]:
score = 95
elif velocity_cv <= config["good_threshold"]:
score = 80 - (velocity_cv - config["excellent_threshold"]) * 150
elif velocity_cv <= config["poor_threshold"]:
score = 60 - (velocity_cv - config["good_threshold"]) * 100
else:
score = max(20, 40 - (velocity_cv - config["poor_threshold"]) * 50)
return {
"score": score,
"grade": _score_to_grade(score),
"coefficient_of_variation": velocity_cv,
"mean_velocity": mean_velocity,
"velocity_std_dev": statistics.stdev(velocities),
"details": f"Velocity CV: {velocity_cv:.1%} (lower is more predictable)"
}
def _calculate_dimension_score(value: float, config: Dict[str, Any]) -> float:
"""Calculate dimension score based on thresholds."""
if value >= config["excellent_threshold"]:
return 95
elif value >= config["good_threshold"]:
# Linear interpolation between good and excellent
range_size = config["excellent_threshold"] - config["good_threshold"]
position = (value - config["good_threshold"]) / range_size
return 80 + (position * 15)
elif value >= config["poor_threshold"]:
# Linear interpolation between poor and good
range_size = config["good_threshold"] - config["poor_threshold"]
position = (value - config["poor_threshold"]) / range_size
return 50 + (position * 30)
else:
# Below poor threshold
return max(20, 50 - (config["poor_threshold"] - value) * 100)
def _score_to_grade(score: float) -> str:
"""Convert numerical score to letter grade."""
if score >= OVERALL_HEALTH_THRESHOLDS["excellent"]:
return "excellent"
elif score >= OVERALL_HEALTH_THRESHOLDS["good"]:
return "good"
elif score >= OVERALL_HEALTH_THRESHOLDS["fair"]:
return "fair"
else:
return "poor"
# ---------------------------------------------------------------------------
# Main Analysis Function
# ---------------------------------------------------------------------------
def analyze_sprint_health(data: Dict[str, Any]) -> HealthScoreResult:
"""Perform comprehensive sprint health analysis."""
result = HealthScoreResult()
try:
# Parse sprint data
sprint_records = data.get("sprints", [])
sprints = [SprintHealthData(record) for record in sprint_records]
if not sprints:
raise ValueError("No sprint data found")
# Sort by sprint number
sprints.sort(key=lambda s: s.sprint_number)
# Calculate dimension scores
dimensions = {
"commitment_reliability": score_commitment_reliability,
"scope_stability": score_scope_stability,
"blocker_resolution": score_blocker_resolution,
"ceremony_engagement": score_ceremony_engagement,
"story_completion_distribution": score_story_completion_distribution,
"velocity_predictability": score_velocity_predictability,
}
weighted_scores = []
for dimension_name, scoring_func in dimensions.items():
dimension_result = scoring_func(sprints)
result.dimension_scores[dimension_name] = dimension_result
# Calculate weighted contribution
weight = HEALTH_DIMENSIONS[dimension_name]["weight"]
weighted_score = dimension_result["score"] * weight
weighted_scores.append(weighted_score)
# Calculate overall score
result.overall_score = sum(weighted_scores)
result.health_grade = _score_to_grade(result.overall_score)
# Generate detailed metrics
result.detailed_metrics = _generate_detailed_metrics(sprints)
# Generate recommendations
result.recommendations = _generate_health_recommendations(result)
except Exception as e:
result.dimension_scores = {"error": str(e)}
result.overall_score = 0
return result
def _generate_detailed_metrics(sprints: List[SprintHealthData]) -> Dict[str, Any]:
"""Generate detailed metrics for analysis."""
metrics = {
"sprint_count": len(sprints),
"date_range": {
"start": sprints[0].start_date if sprints else "",
"end": sprints[-1].end_date if sprints else "",
},
"team_metrics": {},
"story_metrics": {},
"blocker_metrics": {},
}
if not sprints:
return metrics
# Team metrics
team_sizes = [sprint.team_size for sprint in sprints if sprint.team_size > 0]
if team_sizes:
metrics["team_metrics"] = {
"average_team_size": statistics.mean(team_sizes),
"team_size_stability": statistics.stdev(team_sizes) if len(team_sizes) > 1 else 0,
}
# Story metrics
all_stories = []
for sprint in sprints:
all_stories.extend(sprint.stories)
if all_stories:
story_points = [story.points for story in all_stories if story.points > 0]
metrics["story_metrics"] = {
"total_stories": len(all_stories),
"average_story_points": statistics.mean(story_points) if story_points else 0,
"completed_stories": sum(1 for story in all_stories if story.is_completed),
"blocked_stories": sum(1 for story in all_stories if story.is_blocked),
}
# Blocker metrics
all_blockers = []
for sprint in sprints:
all_blockers.extend(sprint.blockers)
if all_blockers:
resolution_times = [b.get("resolution_days", 0) for b in all_blockers if b.get("resolution_days", 0) > 0]
metrics["blocker_metrics"] = {
"total_blockers": len(all_blockers),
"resolved_blockers": len(resolution_times),
"average_resolution_days": statistics.mean(resolution_times) if resolution_times else 0,
}
return metrics
def _generate_health_recommendations(result: HealthScoreResult) -> List[str]:
"""Generate actionable recommendations based on health scores."""
recommendations = []
# Overall health recommendations
if result.overall_score < OVERALL_HEALTH_THRESHOLDS["poor"]:
recommendations.append("CRITICAL: Sprint health is poor across multiple dimensions. Immediate intervention required.")
elif result.overall_score < OVERALL_HEALTH_THRESHOLDS["fair"]:
recommendations.append("Sprint health needs improvement. Focus on top 2-3 problem areas.")
elif result.overall_score >= OVERALL_HEALTH_THRESHOLDS["excellent"]:
recommendations.append("Excellent sprint health! Maintain current practices and share learnings with other teams.")
# Dimension-specific recommendations
for dimension, scores in result.dimension_scores.items():
if isinstance(scores, dict) and "score" in scores:
score = scores["score"]
grade = scores["grade"]
if score < 50: # Poor performance
if dimension == "commitment_reliability":
recommendations.append("Improve sprint planning accuracy and realistic capacity estimation.")
elif dimension == "scope_stability":
recommendations.append("Reduce mid-sprint scope changes. Strengthen backlog refinement process.")
elif dimension == "blocker_resolution":
recommendations.append("Implement faster blocker escalation and resolution processes.")
elif dimension == "ceremony_engagement":
recommendations.append("Improve ceremony facilitation and team engagement strategies.")
elif dimension == "story_completion_distribution":
recommendations.append("Focus on completing stories fully rather than starting many partially.")
elif dimension == "velocity_predictability":
recommendations.append("Work on consistent estimation and delivery patterns.")
elif score >= 85: # Excellent performance
dimension_name = dimension.replace("_", " ").title()
recommendations.append(f"Excellent {dimension_name}! Document and share best practices.")
return recommendations
# ---------------------------------------------------------------------------
# Output Formatting
# ---------------------------------------------------------------------------
def format_text_output(result: HealthScoreResult) -> str:
"""Format results as readable text report."""
lines = []
lines.append("="*60)
lines.append("SPRINT HEALTH ANALYSIS REPORT")
lines.append("="*60)
lines.append("")
if "error" in result.dimension_scores:
lines.append(f"ERROR: {result.dimension_scores['error']}")
return "\n".join(lines)
# Overall health summary
lines.append("OVERALL HEALTH SUMMARY")
lines.append("-"*30)
lines.append(f"Health Score: {result.overall_score:.1f}/100")
lines.append(f"Health Grade: {result.health_grade.title()}")
lines.append("")
# Dimension scores
lines.append("DIMENSION SCORES")
lines.append("-"*30)
for dimension, scores in result.dimension_scores.items():
if isinstance(scores, dict) and "score" in scores:
dimension_name = dimension.replace("_", " ").title()
weight = HEALTH_DIMENSIONS[dimension]["weight"]
lines.append(f"{dimension_name} (Weight: {weight:.0%})")
lines.append(f" Score: {scores['score']:.1f}/100 ({scores['grade'].title()})")
lines.append(f" Details: {scores['details']}")
lines.append("")
# Detailed metrics
metrics = result.detailed_metrics
if metrics:
lines.append("DETAILED METRICS")
lines.append("-"*30)
lines.append(f"Sprints Analyzed: {metrics.get('sprint_count', 0)}")
if "team_metrics" in metrics and metrics["team_metrics"]:
team = metrics["team_metrics"]
lines.append(f"Average Team Size: {team.get('average_team_size', 0):.1f}")
if "story_metrics" in metrics and metrics["story_metrics"]:
stories = metrics["story_metrics"]
lines.append(f"Total Stories: {stories.get('total_stories', 0)}")
lines.append(f"Completed Stories: {stories.get('completed_stories', 0)}")
lines.append(f"Blocked Stories: {stories.get('blocked_stories', 0)}")
if "blocker_metrics" in metrics and metrics["blocker_metrics"]:
blockers = metrics["blocker_metrics"]
lines.append(f"Total Blockers: {blockers.get('total_blockers', 0)}")
lines.append(f"Average Resolution Time: {blockers.get('average_resolution_days', 0):.1f} days")
lines.append("")
# Recommendations
if result.recommendations:
lines.append("RECOMMENDATIONS")
lines.append("-"*30)
for i, rec in enumerate(result.recommendations, 1):
lines.append(f"{i}. {rec}")
return "\n".join(lines)
def format_json_output(result: HealthScoreResult) -> Dict[str, Any]:
"""Format results as JSON."""
return {
"overall_score": result.overall_score,
"health_grade": result.health_grade,
"dimension_scores": result.dimension_scores,
"detailed_metrics": result.detailed_metrics,
"recommendations": result.recommendations,
}
# ---------------------------------------------------------------------------
# CLI Interface
# ---------------------------------------------------------------------------
def main() -> int:
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Analyze sprint health across multiple dimensions"
)
parser.add_argument(
"data_file",
help="JSON file containing sprint health data"
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)"
)
args = parser.parse_args()
try:
# Load and validate data
with open(args.data_file, 'r') as f:
data = json.load(f)
# Perform analysis
result = analyze_sprint_health(data)
# Output results
if args.format == "json":
output = format_json_output(result)
print(json.dumps(output, indent=2))
else:
output = format_text_output(result)
print(output)
return 0
except FileNotFoundError:
print(f"Error: File '{args.data_file}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.data_file}': {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main()) #!/usr/bin/env python3
"""
Sprint Velocity Analyzer
Analyzes sprint velocity data to calculate rolling averages, detect trends, forecast
capacity, and identify anomalies. Supports multiple statistical measures and
probabilistic forecasting for scrum teams.
Usage:
python velocity_analyzer.py sprint_data.json
python velocity_analyzer.py sprint_data.json --format json
"""
import argparse
import json
import math
import statistics
import sys
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple, Union
# ---------------------------------------------------------------------------
# Constants and Configuration
# ---------------------------------------------------------------------------
VELOCITY_THRESHOLDS: Dict[str, Dict[str, float]] = {
"trend_detection": {
"strong_improvement": 0.15, # 15% improvement
"improvement": 0.08, # 8% improvement
"stable": 0.05, # ±5% stable range
"decline": -0.08, # 8% decline
"strong_decline": -0.15, # 15% decline
},
"volatility": {
"low": 0.15, # CV below 15%
"moderate": 0.25, # CV 15-25%
"high": 0.40, # CV 25-40%
"very_high": 0.40, # CV above 40%
},
"anomaly_detection": {
"outlier_threshold": 2.0, # Standard deviations from mean
"extreme_outlier": 3.0, # Extreme outlier threshold
}
}
FORECASTING_CONFIG: Dict[str, Any] = {
"confidence_levels": [0.50, 0.70, 0.85, 0.95],
"monte_carlo_iterations": 10000,
"min_sprints_for_forecast": 3,
"max_sprints_lookback": 8,
}
# ---------------------------------------------------------------------------
# Data Structures and Types
# ---------------------------------------------------------------------------
class SprintData:
"""Represents a single sprint's velocity and metadata."""
def __init__(self, data: Dict[str, Any]):
self.sprint_number: int = data.get("sprint_number", 0)
self.sprint_name: str = data.get("sprint_name", "")
self.start_date: str = data.get("start_date", "")
self.end_date: str = data.get("end_date", "")
self.planned_points: int = data.get("planned_points", 0)
self.completed_points: int = data.get("completed_points", 0)
self.added_points: int = data.get("added_points", 0)
self.removed_points: int = data.get("removed_points", 0)
self.carry_over_points: int = data.get("carry_over_points", 0)
self.team_capacity: float = data.get("team_capacity", 0.0)
self.working_days: int = data.get("working_days", 10)
# Calculate derived metrics
self.velocity: int = self.completed_points
self.commitment_ratio: float = (
self.completed_points / max(self.planned_points, 1)
)
self.scope_change_ratio: float = (
(self.added_points + self.removed_points) / max(self.planned_points, 1)
)
class VelocityAnalysis:
"""Complete velocity analysis results."""
def __init__(self):
self.summary: Dict[str, Any] = {}
self.trend_analysis: Dict[str, Any] = {}
self.forecasting: Dict[str, Any] = {}
self.anomalies: List[Dict[str, Any]] = []
self.recommendations: List[str] = []
# ---------------------------------------------------------------------------
# Core Analysis Functions
# ---------------------------------------------------------------------------
def calculate_rolling_averages(sprints: List[SprintData],
window_sizes: List[int] = [3, 5, 8]) -> Dict[int, List[float]]:
"""Calculate rolling averages for different window sizes."""
velocities = [sprint.velocity for sprint in sprints]
rolling_averages = {}
for window_size in window_sizes:
averages = []
for i in range(len(velocities)):
start_idx = max(0, i - window_size + 1)
window = velocities[start_idx:i + 1]
if len(window) >= min(3, window_size): # Minimum data points
averages.append(sum(window) / len(window))
else:
averages.append(None)
rolling_averages[window_size] = averages
return rolling_averages
def detect_trend(sprints: List[SprintData], lookback_sprints: int = 6) -> Dict[str, Any]:
"""Detect velocity trends using linear regression and statistical analysis."""
if len(sprints) < 3:
return {"trend": "insufficient_data", "confidence": 0.0}
# Use recent sprints for trend analysis
recent_sprints = sprints[-lookback_sprints:] if len(sprints) > lookback_sprints else sprints
velocities = [sprint.velocity for sprint in recent_sprints]
# Calculate linear trend
n = len(velocities)
x_values = list(range(n))
x_mean = sum(x_values) / n
y_mean = sum(velocities) / n
# Linear regression slope
numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, velocities))
denominator = sum((x - x_mean) ** 2 for x in x_values)
if denominator == 0:
slope = 0
else:
slope = numerator / denominator
# Calculate correlation coefficient for trend strength
if n > 2:
try:
correlation = statistics.correlation(x_values, velocities)
except statistics.StatisticsError:
correlation = 0.0
else:
correlation = 0.0
# Determine trend direction and strength
avg_velocity = statistics.mean(velocities)
relative_slope = slope / max(avg_velocity, 1) # Normalize by average velocity
thresholds = VELOCITY_THRESHOLDS["trend_detection"]
if relative_slope > thresholds["strong_improvement"]:
trend = "strong_improvement"
elif relative_slope > thresholds["improvement"]:
trend = "improvement"
elif relative_slope > -thresholds["stable"]:
trend = "stable"
elif relative_slope > thresholds["decline"]:
trend = "decline"
else:
trend = "strong_decline"
return {
"trend": trend,
"slope": slope,
"relative_slope": relative_slope,
"correlation": abs(correlation),
"confidence": abs(correlation),
"recent_sprints_analyzed": len(recent_sprints),
"average_velocity": avg_velocity,
}
def calculate_volatility(sprints: List[SprintData]) -> Dict[str, Any]:
"""Calculate velocity volatility and stability metrics."""
if len(sprints) < 2:
return {"volatility": "insufficient_data"}
velocities = [sprint.velocity for sprint in sprints]
mean_velocity = statistics.mean(velocities)
if mean_velocity == 0:
return {"volatility": "no_velocity"}
# Coefficient of Variation (CV)
std_dev = statistics.stdev(velocities) if len(velocities) > 1 else 0
cv = std_dev / mean_velocity
# Classify volatility
thresholds = VELOCITY_THRESHOLDS["volatility"]
if cv <= thresholds["low"]:
volatility_level = "low"
elif cv <= thresholds["moderate"]:
volatility_level = "moderate"
elif cv <= thresholds["high"]:
volatility_level = "high"
else:
volatility_level = "very_high"
# Calculate additional stability metrics
velocity_range = max(velocities) - min(velocities)
range_ratio = velocity_range / mean_velocity if mean_velocity > 0 else 0
return {
"volatility": volatility_level,
"coefficient_of_variation": cv,
"standard_deviation": std_dev,
"mean_velocity": mean_velocity,
"velocity_range": velocity_range,
"range_ratio": range_ratio,
"min_velocity": min(velocities),
"max_velocity": max(velocities),
}
def detect_anomalies(sprints: List[SprintData]) -> List[Dict[str, Any]]:
"""Detect velocity anomalies using statistical methods."""
if len(sprints) < 3:
return []
velocities = [sprint.velocity for sprint in sprints]
mean_velocity = statistics.mean(velocities)
std_dev = statistics.stdev(velocities) if len(velocities) > 1 else 0
anomalies = []
threshold = VELOCITY_THRESHOLDS["anomaly_detection"]["outlier_threshold"]
extreme_threshold = VELOCITY_THRESHOLDS["anomaly_detection"]["extreme_outlier"]
for i, sprint in enumerate(sprints):
if std_dev == 0:
continue
z_score = abs(sprint.velocity - mean_velocity) / std_dev
if z_score >= extreme_threshold:
anomaly_type = "extreme_outlier"
elif z_score >= threshold:
anomaly_type = "outlier"
else:
continue
anomalies.append({
"sprint_number": sprint.sprint_number,
"sprint_name": sprint.sprint_name,
"velocity": sprint.velocity,
"expected_range": (mean_velocity - 2 * std_dev, mean_velocity + 2 * std_dev),
"z_score": z_score,
"anomaly_type": anomaly_type,
"deviation_percentage": ((sprint.velocity - mean_velocity) / mean_velocity) * 100,
})
return anomalies
def monte_carlo_forecast(sprints: List[SprintData], sprints_ahead: int = 6) -> Dict[str, Any]:
"""Generate probabilistic velocity forecasts using Monte Carlo simulation."""
if len(sprints) < FORECASTING_CONFIG["min_sprints_for_forecast"]:
return {"error": "insufficient_historical_data"}
# Use recent sprints for forecasting
lookback = min(len(sprints), FORECASTING_CONFIG["max_sprints_lookback"])
recent_sprints = sprints[-lookback:]
velocities = [sprint.velocity for sprint in recent_sprints]
if not velocities:
return {"error": "no_velocity_data"}
mean_velocity = statistics.mean(velocities)
std_dev = statistics.stdev(velocities) if len(velocities) > 1 else 0
# Monte Carlo simulation
iterations = FORECASTING_CONFIG["monte_carlo_iterations"]
confidence_levels = FORECASTING_CONFIG["confidence_levels"]
simulated_totals = []
for _ in range(iterations):
total_points = 0
for _ in range(sprints_ahead):
# Sample from normal distribution
if std_dev > 0:
simulated_velocity = max(0, random_normal(mean_velocity, std_dev))
else:
simulated_velocity = mean_velocity
total_points += simulated_velocity
simulated_totals.append(total_points)
# Calculate percentiles for confidence intervals
simulated_totals.sort()
forecasts = {}
for confidence in confidence_levels:
percentile_index = int(confidence * iterations)
percentile_index = min(percentile_index, iterations - 1)
forecasts[f"{int(confidence * 100)}%"] = simulated_totals[percentile_index]
return {
"sprints_ahead": sprints_ahead,
"historical_sprints_used": lookback,
"mean_velocity": mean_velocity,
"velocity_std_dev": std_dev,
"forecasted_totals": forecasts,
"average_per_sprint": mean_velocity,
"expected_total": mean_velocity * sprints_ahead,
}
def random_normal(mean: float, std_dev: float) -> float:
"""Generate a random number from a normal distribution using Box-Muller transform."""
import random
import math
# Box-Muller transformation
u1 = random.random()
u2 = random.random()
z0 = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
return mean + z0 * std_dev
def generate_recommendations(analysis: VelocityAnalysis) -> List[str]:
"""Generate actionable recommendations based on velocity analysis."""
recommendations = []
# Trend-based recommendations
trend = analysis.trend_analysis.get("trend", "")
if trend == "strong_decline":
recommendations.append("URGENT: Address strong declining velocity trend. Review impediments, team capacity, and story complexity.")
elif trend == "decline":
recommendations.append("Monitor declining velocity. Consider impediment removal and capacity planning review.")
elif trend == "strong_improvement":
recommendations.append("Excellent improvement trend! Document successful practices to maintain momentum.")
# Volatility-based recommendations
volatility = analysis.summary.get("volatility", {}).get("volatility", "")
if volatility == "very_high":
recommendations.append("HIGH PRIORITY: Reduce velocity volatility. Review story sizing, definition of done, and sprint planning process.")
elif volatility == "high":
recommendations.append("Work on consistency. Review estimation practices and sprint commitment process.")
elif volatility == "low":
recommendations.append("Good velocity stability. Continue current practices.")
# Anomaly-based recommendations
if len(analysis.anomalies) > 0:
extreme_anomalies = [a for a in analysis.anomalies if a["anomaly_type"] == "extreme_outlier"]
if extreme_anomalies:
recommendations.append(f"Investigate {len(extreme_anomalies)} extreme velocity anomalies for root causes.")
# Commitment ratio recommendations
commitment_ratios = analysis.summary.get("commitment_analysis", {})
avg_commitment = commitment_ratios.get("average_commitment_ratio", 1.0)
if avg_commitment < 0.8:
recommendations.append("Low sprint commitment achievement. Review capacity planning and story complexity estimation.")
elif avg_commitment > 1.2:
recommendations.append("Consistently over-committing. Consider more realistic sprint planning.")
return recommendations
# ---------------------------------------------------------------------------
# Main Analysis Function
# ---------------------------------------------------------------------------
def analyze_velocity(data: Dict[str, Any]) -> VelocityAnalysis:
"""Perform comprehensive velocity analysis."""
analysis = VelocityAnalysis()
try:
# Parse sprint data
sprint_records = data.get("sprints", [])
sprints = [SprintData(record) for record in sprint_records]
if not sprints:
raise ValueError("No sprint data found")
# Sort by sprint number
sprints.sort(key=lambda s: s.sprint_number)
# Basic summary statistics
velocities = [sprint.velocity for sprint in sprints]
commitment_ratios = [sprint.commitment_ratio for sprint in sprints]
scope_change_ratios = [sprint.scope_change_ratio for sprint in sprints]
analysis.summary = {
"total_sprints": len(sprints),
"velocity_stats": {
"mean": statistics.mean(velocities),
"median": statistics.median(velocities),
"min": min(velocities),
"max": max(velocities),
"total_points": sum(velocities),
},
"commitment_analysis": {
"average_commitment_ratio": statistics.mean(commitment_ratios),
"commitment_consistency": statistics.stdev(commitment_ratios) if len(commitment_ratios) > 1 else 0,
"sprints_under_committed": sum(1 for r in commitment_ratios if r < 1.0),
"sprints_over_committed": sum(1 for r in commitment_ratios if r > 1.0),
},
"scope_change_analysis": {
"average_scope_change": statistics.mean(scope_change_ratios),
"scope_change_volatility": statistics.stdev(scope_change_ratios) if len(scope_change_ratios) > 1 else 0,
},
"rolling_averages": calculate_rolling_averages(sprints),
"volatility": calculate_volatility(sprints),
}
# Trend analysis
analysis.trend_analysis = detect_trend(sprints)
# Forecasting
analysis.forecasting = monte_carlo_forecast(sprints, sprints_ahead=6)
# Anomaly detection
analysis.anomalies = detect_anomalies(sprints)
# Generate recommendations
analysis.recommendations = generate_recommendations(analysis)
except Exception as e:
analysis.summary = {"error": str(e)}
return analysis
# ---------------------------------------------------------------------------
# Output Formatting
# ---------------------------------------------------------------------------
def format_text_output(analysis: VelocityAnalysis) -> str:
"""Format analysis results as readable text report."""
lines = []
lines.append("="*60)
lines.append("SPRINT VELOCITY ANALYSIS REPORT")
lines.append("="*60)
lines.append("")
if "error" in analysis.summary:
lines.append(f"ERROR: {analysis.summary['error']}")
return "\n".join(lines)
# Summary section
summary = analysis.summary
lines.append("VELOCITY SUMMARY")
lines.append("-"*30)
lines.append(f"Total Sprints Analyzed: {summary['total_sprints']}")
velocity_stats = summary.get("velocity_stats", {})
lines.append(f"Average Velocity: {velocity_stats.get('mean', 0):.1f} points")
lines.append(f"Median Velocity: {velocity_stats.get('median', 0):.1f} points")
lines.append(f"Velocity Range: {velocity_stats.get('min', 0)} - {velocity_stats.get('max', 0)} points")
lines.append(f"Total Points Completed: {velocity_stats.get('total_points', 0)}")
lines.append("")
# Volatility analysis
volatility = summary.get("volatility", {})
lines.append("VELOCITY STABILITY")
lines.append("-"*30)
lines.append(f"Volatility Level: {volatility.get('volatility', 'Unknown').replace('_', ' ').title()}")
lines.append(f"Coefficient of Variation: {volatility.get('coefficient_of_variation', 0):.2%}")
lines.append(f"Standard Deviation: {volatility.get('standard_deviation', 0):.1f} points")
lines.append("")
# Trend analysis
trend_analysis = analysis.trend_analysis
lines.append("TREND ANALYSIS")
lines.append("-"*30)
lines.append(f"Trend Direction: {trend_analysis.get('trend', 'Unknown').replace('_', ' ').title()}")
lines.append(f"Trend Confidence: {trend_analysis.get('confidence', 0):.1%}")
lines.append(f"Velocity Change Rate: {trend_analysis.get('relative_slope', 0):.1%} per sprint")
lines.append("")
# Forecasting
forecasting = analysis.forecasting
lines.append("CAPACITY FORECAST (Next 6 Sprints)")
lines.append("-"*30)
if "error" not in forecasting:
lines.append(f"Expected Total: {forecasting.get('expected_total', 0):.0f} points")
lines.append(f"Average Per Sprint: {forecasting.get('average_per_sprint', 0):.1f} points")
forecasted_totals = forecasting.get("forecasted_totals", {})
lines.append("Confidence Intervals:")
for confidence, total in forecasted_totals.items():
lines.append(f" {confidence}: {total:.0f} points")
else:
lines.append(f"Forecast unavailable: {forecasting.get('error', 'Unknown error')}")
lines.append("")
# Anomalies
if analysis.anomalies:
lines.append("VELOCITY ANOMALIES")
lines.append("-"*30)
for anomaly in analysis.anomalies:
lines.append(f"Sprint {anomaly['sprint_number']} ({anomaly['sprint_name']})")
lines.append(f" Velocity: {anomaly['velocity']} points")
lines.append(f" Deviation: {anomaly['deviation_percentage']:.1f}%")
lines.append(f" Type: {anomaly['anomaly_type'].replace('_', ' ').title()}")
lines.append("")
# Recommendations
if analysis.recommendations:
lines.append("RECOMMENDATIONS")
lines.append("-"*30)
for i, rec in enumerate(analysis.recommendations, 1):
lines.append(f"{i}. {rec}")
return "\n".join(lines)
def format_json_output(analysis: VelocityAnalysis) -> Dict[str, Any]:
"""Format analysis results as JSON."""
return {
"summary": analysis.summary,
"trend_analysis": analysis.trend_analysis,
"forecasting": analysis.forecasting,
"anomalies": analysis.anomalies,
"recommendations": analysis.recommendations,
}
# ---------------------------------------------------------------------------
# CLI Interface
# ---------------------------------------------------------------------------
def main() -> int:
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Analyze sprint velocity data with trend detection and forecasting"
)
parser.add_argument(
"data_file",
help="JSON file containing sprint data"
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)"
)
args = parser.parse_args()
try:
# Load and validate data
with open(args.data_file, 'r') as f:
data = json.load(f)
# Perform analysis
analysis = analyze_velocity(data)
# Output results
if args.format == "json":
output = format_json_output(analysis)
print(json.dumps(output, indent=2))
else:
output = format_text_output(analysis)
print(output)
return 0
except FileNotFoundError:
print(f"Error: File '{args.data_file}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.data_file}': {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main()) Install this Skill
Skills give your AI agent a consistent, structured approach to this task — better output than a one-off prompt.
npx skills add alirezarezvani/claude-skills --skill project-management/scrum-master Community skill by @alirezarezvani. Need a walkthrough? See the install guide →
Works with
Prefer no terminal? Download the ZIP and place it manually.
Details
- Category
- Productivity
- License
- MIT
- Author
- @alirezarezvani
- Source
- GitHub →
- Source file
-
show path
project-management/scrum-master/SKILL.md
People who install this also use
Atlassian Jira Expert
JQL query writing, custom workflow design, automation rules, dashboard configuration, and Jira best practices for software and project teams.
@alirezarezvani
Product Manager Toolkit
RICE prioritization, customer interview frameworks, PRD writing, and product discovery workflows — a complete PM toolkit in one skill.
@alirezarezvani