capacity-planning

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Capacity Planning

产能规划

Overview

概述

Capacity planning ensures teams have sufficient resources to deliver work at sustainable pace, prevents burnout, and enables accurate commitment to stakeholders.
产能规划确保团队拥有充足资源以可持续的节奏交付工作,防止人员倦怠,并能向利益相关者做出准确的承诺。

When to Use

适用场景

  • Annual or quarterly planning cycles
  • Allocating people to projects
  • Adjusting team size
  • Planning for holidays and absences
  • Forecasting resource needs
  • Balancing multiple projects
  • Identifying bottlenecks
  • 年度或季度规划周期
  • 为项目分配人员
  • 调整团队规模
  • 规划假期与缺勤情况
  • 预测资源需求
  • 平衡多项目工作
  • 识别瓶颈

Instructions

操作指南

1. Capacity Assessment

1. 产能评估

python
undefined
python
undefined

Team capacity calculation and planning

Team capacity calculation and planning

class CapacityPlanner: # Standard work hours per week STANDARD_WEEK_HOURS = 40
# Activities that reduce available capacity
OVERHEAD_HOURS = {
    'meetings': 5,           # standups, 1-on-1s, planning
    'training': 2,           # learning new tech
    'administrative': 2,     # emails, approvals
    'support': 2,            # helping teammates
    'contingency': 2         # interruptions, emergencies
}

def __init__(self, team_size, sprint_duration_weeks=2):
    self.team_size = team_size
    self.sprint_duration_weeks = sprint_duration_weeks
    self.members = []

def calculate_team_capacity(self):
    """Calculate available capacity hours"""
    # Base capacity
    base_hours = self.team_size * self.STANDARD_WEEK_HOURS * self.sprint_duration_weeks

    # Subtract overhead
    overhead = sum(self.OVERHEAD_HOURS.values()) * self.team_size * self.sprint_duration_weeks

    # Subtract absences
    absence_hours = self.calculate_absences()

    # Available capacity
    available_capacity = base_hours - overhead - absence_hours

    return {
        'base_hours': base_hours,
        'overhead_hours': overhead,
        'absence_hours': absence_hours,
        'available_capacity': available_capacity,
        'utilization_target': '85%',  # Leave 15% buffer
        'target_commitment': available_capacity * 0.85
    }

def calculate_absences(self):
    """Account for vacation, sick, etc."""
    absence_days = 0

    # Standard absences
    vacation_days = 15  # annual
    sick_days = 5       # annual
    holidays = 10       # annual

    # Convert to per-sprint
    absence_days = (vacation_days + sick_days + holidays) / 52 * self.sprint_duration_weeks

    absence_hours = absence_days * 8 * self.team_size
    return absence_hours

def allocate_to_projects(self, projects, team):
    """Allocate capacity across multiple projects"""
    allocation = {}
    total_allocation = 0

    # Allocate by priority
    for project in sorted(projects, key=lambda p: p.priority):
        required_hours = project.effort_hours
        available = self.calculate_team_capacity()['available_capacity'] - total_allocation

        if available >= required_hours:
            allocation[project.id] = {
                'project': project.name,
                'allocated': required_hours,
                'team_members': int(required_hours / (self.STANDARD_WEEK_HOURS * self.sprint_duration_weeks)),
                'allocation_percent': (required_hours / available * 100)
            }
            total_allocation += required_hours
        else:
            allocation[project.id] = {
                'project': project.name,
                'allocated': available,
                'status': 'Insufficient capacity',
                'shortfall': required_hours - available,
                'recommendation': 'Add resources or defer scope'
            }
            total_allocation = available

    return allocation

def identify_bottlenecks(self, skills, projects):
    """Find skill constraints"""
    bottlenecks = []

    for skill in skills:
        people_with_skill = sum(1 for p in self.members if skill in p.skills)
        projects_needing_skill = sum(1 for p in projects if skill in p.required_skills)

        utilization = (projects_needing_skill / people_with_skill * 100) if people_with_skill > 0 else 0

        if utilization > 100:
            bottlenecks.append({
                'skill': skill,
                'people_available': people_with_skill,
                'projects_needing': projects_needing_skill,
                'utilization': utilization,
                'severity': 'Critical',
                'actions': ['Cross-train team', 'Hire specialist', 'Adjust scope']
            })

    return bottlenecks
undefined
class CapacityPlanner: # Standard work hours per week STANDARD_WEEK_HOURS = 40
# Activities that reduce available capacity
OVERHEAD_HOURS = {
    'meetings': 5,           # standups, 1-on-1s, planning
    'training': 2,           # learning new tech
    'administrative': 2,     # emails, approvals
    'support': 2,            # helping teammates
    'contingency': 2         # interruptions, emergencies
}

def __init__(self, team_size, sprint_duration_weeks=2):
    self.team_size = team_size
    self.sprint_duration_weeks = sprint_duration_weeks
    self.members = []

def calculate_team_capacity(self):
    """Calculate available capacity hours"""
    # Base capacity
    base_hours = self.team_size * self.STANDARD_WEEK_HOURS * self.sprint_duration_weeks

    # Subtract overhead
    overhead = sum(self.OVERHEAD_HOURS.values()) * self.team_size * self.sprint_duration_weeks

    # Subtract absences
    absence_hours = self.calculate_absences()

    # Available capacity
    available_capacity = base_hours - overhead - absence_hours

    return {
        'base_hours': base_hours,
        'overhead_hours': overhead,
        'absence_hours': absence_hours,
        'available_capacity': available_capacity,
        'utilization_target': '85%',  # Leave 15% buffer
        'target_commitment': available_capacity * 0.85
    }

def calculate_absences(self):
    """Account for vacation, sick, etc."""
    absence_days = 0

    # Standard absences
    vacation_days = 15  # annual
    sick_days = 5       # annual
    holidays = 10       # annual

    # Convert to per-sprint
    absence_days = (vacation_days + sick_days + holidays) / 52 * self.sprint_duration_weeks

    absence_hours = absence_days * 8 * self.team_size
    return absence_hours

def allocate_to_projects(self, projects, team):
    """Allocate capacity across multiple projects"""
    allocation = {}
    total_allocation = 0

    # Allocate by priority
    for project in sorted(projects, key=lambda p: p.priority):
        required_hours = project.effort_hours
        available = self.calculate_team_capacity()['available_capacity'] - total_allocation

        if available >= required_hours:
            allocation[project.id] = {
                'project': project.name,
                'allocated': required_hours,
                'team_members': int(required_hours / (self.STANDARD_WEEK_HOURS * self.sprint_duration_weeks)),
                'allocation_percent': (required_hours / available * 100)
            }
            total_allocation += required_hours
        else:
            allocation[project.id] = {
                'project': project.name,
                'allocated': available,
                'status': 'Insufficient capacity',
                'shortfall': required_hours - available,
                'recommendation': 'Add resources or defer scope'
            }
            total_allocation = available

    return allocation

def identify_bottlenecks(self, skills, projects):
    """Find skill constraints"""
    bottlenecks = []

    for skill in skills:
        people_with_skill = sum(1 for p in self.members if skill in p.skills)
        projects_needing_skill = sum(1 for p in projects if skill in p.required_skills)

        utilization = (projects_needing_skill / people_with_skill * 100) if people_with_skill > 0 else 0

        if utilization > 100:
            bottlenecks.append({
                'skill': skill,
                'people_available': people_with_skill,
                'projects_needing': projects_needing_skill,
                'utilization': utilization,
                'severity': 'Critical',
                'actions': ['Cross-train team', 'Hire specialist', 'Adjust scope']
            })

    return bottlenecks
undefined

2. Capacity Planning Template

2. 产能规划模板

yaml
Capacity Plan for Q1 2025:

Team: Platform Engineering (12 people)
Period: January 1 - March 31, 2025
Planned Duration: 13 weeks

---
yaml
Capacity Plan for Q1 2025:

Team: Platform Engineering (12 people)
Period: January 1 - March 31, 2025
Planned Duration: 13 weeks

---

Team Composition

Team Composition

Engineers:
  • Senior Engineers: 3 (1.2 FTE each)
  • Mid-Level Engineers: 6 (0.95 FTE each)
  • Junior Engineers: 2 (0.8 FTE each)
  • DevOps: 1 (1.0 FTE)
Total Available FTE: 11.1 (accounting for overhead, absences) Total Available Hours: 11.1 * 40 * 13 = 5,772 hours

Engineers:
  • Senior Engineers: 3 (1.2 FTE each)
  • Mid-Level Engineers: 6 (0.95 FTE each)
  • Junior Engineers: 2 (0.8 FTE each)
  • DevOps: 1 (1.0 FTE)
Total Available FTE: 11.1 (accounting for overhead, absences) Total Available Hours: 11.1 * 40 * 13 = 5,772 hours

Planned Absences

Planned Absences

Vacation: 8 weeks across team (estimated) Sick/Personal: 2 weeks across team Holiday: 1 week (MLK, Presidents Day) Total: ~480 hours

Vacation: 8 weeks across team (estimated) Sick/Personal: 2 weeks across team Holiday: 1 week (MLK, Presidents Day) Total: ~480 hours

Capacity Allocation

Capacity Allocation

Project A: Critical Infrastructure Allocation: 60% (6,600 hours needed) Team: 3 senior, 3 mid-level engineers FTE: 6.6 Status: Committed
Project B: Feature Development Allocation: 30% (3,300 hours needed) Team: 2 mid-level, 2 junior engineers FTE: 3.3 Status: Committed
Infrastructure & Maintenance: Allocation: 10% (1,100 hours) Team: DevOps, 1 senior engineer FTE: 1.1 Status: Operational capacity
Total: 100% allocation, 0% buffer

Project A: Critical Infrastructure Allocation: 60% (6,600 hours needed) Team: 3 senior, 3 mid-level engineers FTE: 6.6 Status: Committed
Project B: Feature Development Allocation: 30% (3,300 hours needed) Team: 2 mid-level, 2 junior engineers FTE: 3.3 Status: Committed
Infrastructure & Maintenance: Allocation: 10% (1,100 hours) Team: DevOps, 1 senior engineer FTE: 1.1 Status: Operational capacity
Total: 100% allocation, 0% buffer

Risk Assessment

Risk Assessment

Risks:
  1. Zero buffer capacity (100% allocation) Impact: Any absence/issue creates crisis Mitigation: Cross-training, automation
  2. Junior engineer ramp-up time Impact: Mid-level engineers pulled for mentoring Mitigation: Assign 1 mentoring hour/week
  3. Infrastructure bottleneck (1 DevOps) Impact: Scaling limitations Mitigation: Hire additional DevOps by Feb 1

Risks:
  1. Zero buffer capacity (100% allocation) Impact: Any absence/issue creates crisis Mitigation: Cross-training, automation
  2. Junior engineer ramp-up time Impact: Mid-level engineers pulled for mentoring Mitigation: Assign 1 mentoring hour/week
  3. Infrastructure bottleneck (1 DevOps) Impact: Scaling limitations Mitigation: Hire additional DevOps by Feb 1

Recommendations

Recommendations

  1. Reduce capacity planning from 100% to 85%
  2. Hire 1 additional DevOps engineer
  3. Cross-train 2 engineers on critical systems
  4. Schedule vacations strategically (not during Phase 2)
  5. Build 15% buffer for emergencies
undefined
  1. Reduce capacity planning from 100% to 85%
  2. Hire 1 additional DevOps engineer
  3. Cross-train 2 engineers on critical systems
  4. Schedule vacations strategically (not during Phase 2)
  5. Build 15% buffer for emergencies
undefined

3. Resource Leveling

3. 资源平衡

javascript
// Balance workload across team members

class ResourceLeveling {
  levelWorkload(team, tasks) {
    const workloadByPerson = {};

    // Initialize team member workload
    team.forEach(person => {
      workloadByPerson[person.id] = {
        name: person.name,
        skills: person.skills,
        capacity: person.capacity_hours,
        assigned: [],
        utilization: 0
      };
    });

    // Assign tasks to balance workload
    const sortedTasks = tasks.sort((a, b) => b.effort - a.effort); // Largest first

    sortedTasks.forEach(task => {
      const suitable = team.filter(p =>
        this.hasSufficientSkills(p.skills, task.required_skills) &&
        this.hasCapacity(workloadByPerson[p.id].utilization, p.capacity_hours)
      );

      if (suitable.length > 0) {
        const leastUtilized = suitable.reduce((a, b) =>
          workloadByPerson[a.id].utilization < workloadByPerson[b.id].utilization ? a : b
        );

        workloadByPerson[leastUtilized.id].assigned.push(task);
        workloadByPerson[leastUtilized.id].utilization += task.effort;
      }
    });

    return {
      assignments: workloadByPerson,
      balanceMetrics: this.calculateBalance(workloadByPerson),
      unassignedTasks: tasks.filter(t => !Object.values(workloadByPerson).some(p => p.assigned.includes(t)))
    };
  }

  calculateBalance(workloadByPerson) {
    const utilizations = Object.values(workloadByPerson).map(p => p.utilization);
    const average = utilizations.reduce((a, b) => a + b) / utilizations.length;
    const variance = Math.sqrt(
      utilizations.reduce((sum, u) => sum + Math.pow(u - average, 2)) / utilizations.length
    );

    return {
      average_utilization: average.toFixed(1),
      std_deviation: variance.toFixed(1),
      balance_score: this.calculateBalanceScore(variance),
      recommendations: this.getBalancingRecommendations(variance)
    };
  }

  calculateBalanceScore(variance) {
    if (variance < 5) return 'Excellent';
    if (variance < 10) return 'Good';
    if (variance < 15) return 'Fair';
    return 'Poor - needs rebalancing';
  }
}
javascript
// Balance workload across team members

class ResourceLeveling {
  levelWorkload(team, tasks) {
    const workloadByPerson = {};

    // Initialize team member workload
    team.forEach(person => {
      workloadByPerson[person.id] = {
        name: person.name,
        skills: person.skills,
        capacity: person.capacity_hours,
        assigned: [],
        utilization: 0
      };
    });

    // Assign tasks to balance workload
    const sortedTasks = tasks.sort((a, b) => b.effort - a.effort); // Largest first

    sortedTasks.forEach(task => {
      const suitable = team.filter(p =>
        this.hasSufficientSkills(p.skills, task.required_skills) &&
        this.hasCapacity(workloadByPerson[p.id].utilization, p.capacity_hours)
      );

      if (suitable.length > 0) {
        const leastUtilized = suitable.reduce((a, b) =>
          workloadByPerson[a.id].utilization < workloadByPerson[b.id].utilization ? a : b
        );

        workloadByPerson[leastUtilized.id].assigned.push(task);
        workloadByPerson[leastUtilized.id].utilization += task.effort;
      }
    });

    return {
      assignments: workloadByPerson,
      balanceMetrics: this.calculateBalance(workloadByPerson),
      unassignedTasks: tasks.filter(t => !Object.values(workloadByPerson).some(p => p.assigned.includes(t)))
    };
  }

  calculateBalance(workloadByPerson) {
    const utilizations = Object.values(workloadByPerson).map(p => p.utilization);
    const average = utilizations.reduce((a, b) => a + b) / utilizations.length;
    const variance = Math.sqrt(
      utilizations.reduce((sum, u) => sum + Math.pow(u - average, 2)) / utilizations.length
    );

    return {
      average_utilization: average.toFixed(1),
      std_deviation: variance.toFixed(1),
      balance_score: this.calculateBalanceScore(variance),
      recommendations: this.getBalancingRecommendations(variance)
    };
  }

  calculateBalanceScore(variance) {
    if (variance < 5) return 'Excellent';
    if (variance < 10) return 'Good';
    if (variance < 15) return 'Fair';
    return 'Poor - needs rebalancing';
  }
}

4. Capacity Forecasting

4. 产能预测

yaml
12-Month Capacity Forecast:

Team Growth Plan:
  Q1 2025: 12 people (current)
  Q2 2025: 13 people (hire 1 DevOps)
  Q3 2025: 15 people (hire 2 engineers)
  Q4 2025: 15 people (stable)

Monthly Capacity (FTE):

January 2025: 10.8 FTE (below normal - ramp-up)
February 2025: 11.1 FTE (normal)
March 2025: 11.0 FTE (1 person on leave)

Q2 Average: 12.5 FTE (new hire contributing)
Q3 Average: 14.2 FTE (2 new hires)
Q4 Average: 15.0 FTE (all at full capacity)

---

Project Commitments vs. Available Capacity:

Q1: Committed 11.0 FTE, Available 11.1 FTE (safe)
Q2: Committed 12.0 FTE, Available 12.5 FTE (buffer 4%)
Q3: Committed 13.0 FTE, Available 14.2 FTE (buffer 9%)
Q4: Committed 14.0 FTE, Available 15.0 FTE (buffer 7%)

---

Risk Alerts:
  - Q1 is tight (98% utilized)
  - Skill gap: Backend expertise in Q2
  - Attrition risk: Plan for 1 departure in Q3
yaml
12-Month Capacity Forecast:

Team Growth Plan:
  Q1 2025: 12 people (current)
  Q2 2025: 13 people (hire 1 DevOps)
  Q3 2025: 15 people (hire 2 engineers)
  Q4 2025: 15 people (stable)

Monthly Capacity (FTE):

January 2025: 10.8 FTE (below normal - ramp-up)
February 2025: 11.1 FTE (normal)
March 2025: 11.0 FTE (1 person on leave)

Q2 Average: 12.5 FTE (new hire contributing)
Q3 Average: 14.2 FTE (2 new hires)
Q4 Average: 15.0 FTE (all at full capacity)

---

Project Commitments vs. Available Capacity:

Q1: Committed 11.0 FTE, Available 11.1 FTE (safe)
Q2: Committed 12.0 FTE, Available 12.5 FTE (buffer 4%)
Q3: Committed 13.0 FTE, Available 14.2 FTE (buffer 9%)
Q4: Committed 14.0 FTE, Available 15.0 FTE (buffer 7%)

---

Risk Alerts:
  - Q1 is tight (98% utilized)
  - Skill gap: Backend expertise in Q2
  - Attrition risk: Plan for 1 departure in Q3

Best Practices

最佳实践

✅ DO

✅ 建议做法

  • Plan capacity at 85% utilization (15% buffer)
  • Account for meetings, training, and overhead
  • Include known absences (vacation, holidays)
  • Identify skill bottlenecks early
  • Balance workload fairly across team
  • Review capacity monthly
  • Adjust plans based on actual velocity
  • Cross-train on critical skills
  • Communicate realistic commitments to stakeholders
  • Build contingency for emergencies
  • 按85%的利用率规划产能(预留15%的缓冲空间)
  • 考虑会议、培训及其他日常事务耗时
  • 纳入已知的缺勤情况(假期、节假日)
  • 尽早识别技能瓶颈
  • 在团队间公平平衡工作负载
  • 每月回顾产能情况
  • 根据实际交付速度调整计划
  • 对关键技能进行交叉培训
  • 向利益相关者传达切合实际的承诺
  • 为紧急情况制定应急预案

❌ DON'T

❌ 避免做法

  • Plan at 100% utilization
  • Ignore meetings and overhead
  • Assign work without checking skills
  • Create overload with continuous surprises
  • Forget about learning/training time
  • Leave capacity planning to last minute
  • Overcommit team consistently
  • Burn out key people
  • Ignore team feedback on workload
  • Plan without considering absences
  • 按100%利用率规划产能
  • 忽略会议及日常事务耗时
  • 不核查技能就分配工作
  • 持续突发任务导致工作过载
  • 忘记预留学习/培训时间
  • 直到最后一刻才进行产能规划
  • 持续让团队过度承诺
  • 导致核心人员倦怠
  • 忽视团队对工作负载的反馈
  • 规划时不考虑缺勤情况

Capacity Planning Tips

产能规划技巧

  • Use velocity data from past sprints
  • Track actual vs. planned utilization
  • Review capacity weekly in standups
  • Maintain 15% buffer for emergencies
  • Cross-train on critical functions
  • 使用过往迭代的交付速度数据
  • 跟踪实际利用率与规划利用率的差异
  • 在每日站会上每周回顾产能情况
  • 预留15%的缓冲空间应对紧急情况
  • 对关键职能进行交叉培训