skills/workforce-scheduling/SKILL.md
When the user wants to optimize workforce scheduling, create shift plans, or balance labor demand. Also use when the user mentions "staff scheduling," "labor planning," "shift optimization," "crew scheduling," "roster optimization," or "employee scheduling." For task assignment, see task-assignment-problem. For wave planning labor, see wave-planning-optimization.
npx skillsauth add kishorkukreja/awesome-supply-chain workforce-schedulingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in workforce scheduling and labor optimization for warehouses and supply chain operations. Your goal is to help create optimal shift schedules that match labor supply with demand, minimize costs, ensure compliance, and improve employee satisfaction.
Before optimizing workforce scheduling, understand:
Labor Demand
Labor Supply
Business Constraints
Current State
Primary Goals:
Key Metrics:
1. Fixed Shifts
2. Rotating Shifts
3. Flexible/Variable Shifts
4. Compressed Workweeks
5. On-Call/Flex Pool
Decision Variables:
Parameters:
Objective Function:
Minimize:
Total Labor Cost = Regular + Overtime + Temp + Penalties
Formally:
Σ Σ Σ (C_reg × H[s] × x[i,s,d]) # Regular time
+ Σ Σ (C_ot × o[i,d]) # Overtime
+ Σ Σ (C_temp × temp_hours[s,d]) # Temporary workers
+ α × (Σ schedule_disruption_penalty) # Preference violations
+ β × (Σ understaffing_penalty) # Demand shortfall
Constraints:
# 1. Meet demand (with possible understaffing penalty)
for s in shifts:
for d in days:
Σ (H[s] × x[i,s,d]) + temp_hours[s,d] >= D[s,d]
# 2. Each employee works at most one shift per day
for i in employees:
for d in days:
Σ x[i,s,d] <= 1 for all s
# 3. Respect employee availability
for i in employees:
for s in shifts:
for d in days:
x[i,s,d] <= A[i,s,d]
# 4. Maximum hours per week
for i in employees:
for week in weeks:
Σ Σ (H[s] × x[i,s,d]) <= Max_hours[i] for d in week, s in shifts
# 5. Minimum rest between shifts
for i in employees:
for d in days[:-1]:
if x[i, evening_shift, d] = 1:
x[i, morning_shift, d+1] = 0 # Example: no evening then morning
# 6. Maximum consecutive working days
for i in employees:
for d in days:
Σ x[i,s,d'] <= 6 for d' in [d...d+6], s in shifts
# 7. Minimum employees per shift (coverage)
for s in shifts:
for d in days:
Σ x[i,s,d] >= Min_coverage[s,d] for all i
# 8. Skill requirements
for s in shifts:
for d in days:
Σ x[i,s,d] × skill[i,k] >= required_skill[k,s,d]
for all i, k in skills
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def greedy_workforce_scheduling(demand, employees, shifts, days):
"""
Greedy heuristic for workforce scheduling
Algorithm:
1. Sort days by demand (highest first)
2. For each day, assign employees to meet demand
3. Prioritize employees with availability and low weekly hours
Parameters:
-----------
demand : dict
{(shift, day): required_hours}
employees : DataFrame
Columns: employee_id, max_hours_per_week, availability
shifts : list
Shift identifiers
days : list
Day identifiers (e.g., dates)
Returns:
--------
Schedule assignments
"""
# Initialize schedule
schedule = []
employee_hours = {emp['employee_id']: 0 for _, emp in employees.iterrows()}
# Sort (shift, day) pairs by demand
demand_sorted = sorted(demand.items(), key=lambda x: x[1], reverse=True)
for (shift, day), required_hours in demand_sorted:
assigned_hours = 0
# Get available employees for this shift/day
available_employees = employees[
employees['availability'].apply(lambda x: (shift, day) in x)
].copy()
# Sort by current hours worked (assign to those with fewer hours first)
available_employees['current_hours'] = available_employees['employee_id'].map(employee_hours)
available_employees = available_employees.sort_values('current_hours')
# Assign employees until demand met
for idx, emp in available_employees.iterrows():
emp_id = emp['employee_id']
shift_length = 8 # Assume 8-hour shifts
# Check if employee can work (not exceeding max hours)
if employee_hours[emp_id] + shift_length <= emp['max_hours_per_week']:
# Assign employee
schedule.append({
'employee_id': emp_id,
'shift': shift,
'day': day,
'hours': shift_length
})
employee_hours[emp_id] += shift_length
assigned_hours += shift_length
if assigned_hours >= required_hours:
break
# Check if demand met
if assigned_hours < required_hours:
print(f"Warning: Understaffed on {day}, shift {shift} "
f"({assigned_hours}/{required_hours} hours)")
return pd.DataFrame(schedule)
# Example usage
employees = pd.DataFrame({
'employee_id': [f'EMP{i:03d}' for i in range(1, 21)],
'max_hours_per_week': [40] * 15 + [20] * 5, # 15 full-time, 5 part-time
'availability': [
[(s, d) for s in ['morning', 'afternoon', 'evening']
for d in range(7)] # Available all shifts/days
for _ in range(20)
]
})
shifts = ['morning', 'afternoon', 'evening']
days = list(range(7)) # Monday=0, Sunday=6
# Demand varies by day and shift
demand = {
(shift, day): np.random.randint(40, 120)
for shift in shifts for day in days
}
# Higher demand on weekdays, mornings and afternoons
for day in range(5): # Mon-Fri
demand[('morning', day)] *= 1.5
demand[('afternoon', day)] *= 1.3
schedule = greedy_workforce_scheduling(demand, employees, shifts, days)
print("Workforce Schedule:")
print(f"Total Scheduled Hours: {schedule['hours'].sum()}")
print(f"Employees Scheduled: {schedule['employee_id'].nunique()}")
print(f"\nSchedule by Shift:")
print(schedule.groupby('shift')['hours'].sum())
from pulp import *
def optimize_workforce_schedule(demand, employees, shifts, days,
cost_regular=20, cost_overtime=30):
"""
Optimal workforce scheduling using MIP
Parameters:
-----------
demand : dict
{(shift, day): hours_needed}
employees : DataFrame
Employee data with availability and constraints
shifts : list
Available shifts
days : list
Days to schedule
cost_regular : float
Regular hourly cost
cost_overtime : float
Overtime hourly cost
Returns:
--------
Optimal schedule
"""
prob = LpProblem("Workforce_Scheduling", LpMinimize)
# Decision variables
# x[emp, shift, day] = 1 if employee works this shift on this day
x = LpVariable.dicts("assign",
[(emp['employee_id'], shift, day)
for _, emp in employees.iterrows()
for shift in shifts
for day in days],
cat='Binary')
# Overtime hours variables
overtime = LpVariable.dicts("overtime",
[(emp['employee_id'], day)
for _, emp in employees.iterrows()
for day in days],
lowBound=0,
cat='Continuous')
# Understaffing variables (soft constraint)
understaffed = LpVariable.dicts("understaffed",
[(shift, day) for shift in shifts for day in days],
lowBound=0,
cat='Continuous')
# Objective: minimize cost
shift_hours = 8 # Assume 8-hour shifts
prob += (
# Regular time cost
cost_regular * shift_hours * lpSum([
x[emp['employee_id'], shift, day]
for _, emp in employees.iterrows()
for shift in shifts
for day in days
]) +
# Overtime cost
cost_overtime * lpSum([
overtime[emp['employee_id'], day]
for _, emp in employees.iterrows()
for day in days
]) +
# Understaffing penalty (high cost)
1000 * lpSum([
understaffed[shift, day]
for shift in shifts
for day in days
])
), "Total_Cost"
# Constraints
# 1. Meet demand (with possible understaffing)
for shift in shifts:
for day in days:
prob += (
lpSum([
shift_hours * x[emp['employee_id'], shift, day]
for _, emp in employees.iterrows()
]) + understaffed[shift, day] >= demand.get((shift, day), 0)
), f"Demand_{shift}_{day}"
# 2. Each employee works at most one shift per day
for _, emp in employees.iterrows():
for day in days:
prob += lpSum([
x[emp['employee_id'], shift, day]
for shift in shifts
]) <= 1, f"OneShift_{emp['employee_id']}_{day}"
# 3. Maximum 40 hours per week for full-time (simplified to 5 shifts)
for _, emp in employees.iterrows():
max_shifts = emp['max_hours_per_week'] // shift_hours
prob += lpSum([
x[emp['employee_id'], shift, day]
for shift in shifts
for day in days
]) <= max_shifts, f"MaxHours_{emp['employee_id']}"
# 4. Calculate overtime (hours beyond 40)
for _, emp in employees.iterrows():
total_hours = lpSum([
shift_hours * x[emp['employee_id'], shift, day]
for shift in shifts
for day in days
])
prob += (
overtime[emp['employee_id'], days[0]] >= total_hours - emp['max_hours_per_week']
), f"Overtime_{emp['employee_id']}"
# Solve
prob.solve(PULP_CBC_CMD(msg=0))
# Extract solution
schedule = []
for _, emp in employees.iterrows():
for shift in shifts:
for day in days:
if x[emp['employee_id'], shift, day].varValue > 0.5:
schedule.append({
'employee_id': emp['employee_id'],
'shift': shift,
'day': day,
'hours': shift_hours
})
return {
'status': LpStatus[prob.status],
'total_cost': value(prob.objective),
'schedule': pd.DataFrame(schedule)
}
result = optimize_workforce_schedule(demand, employees, shifts, days)
print(f"\nOptimization Status: {result['status']}")
print(f"Total Cost: ${result['total_cost']:,.2f}")
print(f"\nSchedule Summary:")
print(result['schedule'].groupby('shift').size())
class PreferenceBasedScheduler:
"""
Schedule based on employee preferences and seniority
"""
def __init__(self, employees, shifts, days):
self.employees = employees
self.shifts = shifts
self.days = days
self.schedule = []
def collect_preferences(self):
"""
Collect employee shift preferences (1-10 scale)
In practice, would come from employee input system
"""
preferences = {}
for _, emp in self.employees.iterrows():
emp_id = emp['employee_id']
preferences[emp_id] = {}
for shift in self.shifts:
for day in self.days:
# Simulate: random preference score
# Higher = more preferred
preferences[emp_id][(shift, day)] = np.random.randint(1, 11)
return preferences
def schedule_with_preferences(self, demand, preferences, seniority):
"""
Create schedule considering preferences and seniority
Algorithm:
1. Sort employees by seniority
2. Senior employees pick preferred shifts first
3. Fill remaining shifts with junior employees
4. Balance to meet demand
Parameters:
-----------
demand : dict
Required staffing
preferences : dict
{employee_id: {(shift, day): preference_score}}
seniority : dict
{employee_id: years_of_service}
Returns:
--------
Schedule with preference satisfaction
"""
# Sort employees by seniority
employees_sorted = sorted(
self.employees['employee_id'],
key=lambda emp_id: seniority.get(emp_id, 0),
reverse=True
)
remaining_demand = demand.copy()
employee_assignments = {emp: 0 for emp in employees_sorted}
schedule = []
# Round 1: Senior employees pick top preferences
for emp_id in employees_sorted:
emp_prefs = preferences[emp_id]
# Get top 5 preferred shifts
top_prefs = sorted(emp_prefs.items(),
key=lambda x: x[1],
reverse=True)[:5]
for (shift, day), pref_score in top_prefs:
# Check if this slot still needs staffing
if remaining_demand.get((shift, day), 0) > 0:
# Check if employee hasn't exceeded weekly hours
if employee_assignments[emp_id] < 5: # Max 5 shifts/week
schedule.append({
'employee_id': emp_id,
'shift': shift,
'day': day,
'hours': 8,
'preference_score': pref_score
})
employee_assignments[emp_id] += 1
remaining_demand[(shift, day)] -= 8
break # Move to next employee
# Round 2: Fill remaining demand with available employees
for (shift, day), needed_hours in remaining_demand.items():
if needed_hours > 0:
# Find employees not yet at capacity
available = [
emp for emp in employees_sorted
if employee_assignments[emp] < 5
]
for emp_id in available:
if needed_hours <= 0:
break
schedule.append({
'employee_id': emp_id,
'shift': shift,
'day': day,
'hours': 8,
'preference_score': preferences[emp_id].get((shift, day), 0)
})
employee_assignments[emp_id] += 1
needed_hours -= 8
schedule_df = pd.DataFrame(schedule)
# Calculate satisfaction metrics
avg_preference = schedule_df['preference_score'].mean()
pref_above_7 = (schedule_df['preference_score'] >= 7).sum() / len(schedule_df) * 100
return {
'schedule': schedule_df,
'avg_preference_score': avg_preference,
'percent_preferred_shifts': pref_above_7
}
# Example
scheduler = PreferenceBasedScheduler(employees, shifts, days)
preferences = scheduler.collect_preferences()
seniority = {f'EMP{i:03d}': np.random.randint(1, 15) for i in range(1, 21)}
result_pref = scheduler.schedule_with_preferences(demand, preferences, seniority)
print("\nPreference-Based Scheduling:")
print(f"Average Preference Score: {result_pref['avg_preference_score']:.2f}/10")
print(f"Preferred Shifts (7+): {result_pref['percent_preferred_shifts']:.1f}%")
class DynamicScheduleManager:
"""
Manage real-time schedule adjustments
Handle call-outs, demand surges, schedule swaps
"""
def __init__(self, base_schedule, employee_pool):
self.base_schedule = base_schedule
self.employee_pool = employee_pool
self.adjustments = []
def handle_callout(self, employee_id, shift, day):
"""
Handle employee call-out and find replacement
Priority:
1. Overtime for already-scheduled employees
2. On-call employees
3. Temporary workers
"""
print(f"Call-out: {employee_id} for {shift} on day {day}")
# Option 1: Ask already-scheduled employees if they can extend/add shift
same_day_workers = self.base_schedule[
(self.base_schedule['day'] == day) &
(self.base_schedule['employee_id'] != employee_id)
]
if len(same_day_workers) > 0:
# Offer overtime to current workers
replacement = same_day_workers.iloc[0]['employee_id']
print(f" Replacement: {replacement} (overtime)")
self.adjustments.append({
'type': 'replacement',
'original': employee_id,
'replacement': replacement,
'shift': shift,
'day': day,
'cost': 'overtime'
})
return replacement
# Option 2: Call on-call employee
on_call = self.employee_pool[self.employee_pool['type'] == 'on_call']
if len(on_call) > 0:
replacement = on_call.iloc[0]['employee_id']
print(f" Replacement: {replacement} (on-call)")
self.adjustments.append({
'type': 'replacement',
'original': employee_id,
'replacement': replacement,
'shift': shift,
'day': day,
'cost': 'regular'
})
return replacement
# Option 3: Hire temp worker
print(f" Replacement: TEMP (temporary agency)")
self.adjustments.append({
'type': 'replacement',
'original': employee_id,
'replacement': 'TEMP',
'shift': shift,
'day': day,
'cost': 'temp_agency'
})
return 'TEMP'
def handle_demand_surge(self, shift, day, additional_hours_needed):
"""
Handle unexpected demand increase
Options:
1. Extend shifts (overtime)
2. Call in off-duty employees
3. Hire temps
"""
print(f"Demand surge: +{additional_hours_needed} hours needed for {shift} on day {day}")
# Option 1: Extend current shift
current_workers = self.base_schedule[
(self.base_schedule['shift'] == shift) &
(self.base_schedule['day'] == day)
]
if len(current_workers) > 0:
# Ask workers to extend shift
overtime_per_worker = additional_hours_needed / len(current_workers)
print(f" Solution: Extend shift for {len(current_workers)} workers "
f"({overtime_per_worker:.1f} OT hours each)")
self.adjustments.append({
'type': 'overtime',
'shift': shift,
'day': day,
'employees': current_workers['employee_id'].tolist(),
'ot_hours': overtime_per_worker
})
return 'overtime'
# Option 2: Call in off-duty
# (implementation similar to call-out)
return 'temp'
# Example usage
manager = DynamicScheduleManager(schedule, employees)
# Simulate call-out
manager.handle_callout('EMP005', 'morning', 2)
# Simulate demand surge
manager.handle_demand_surge('afternoon', 3, 24)
print(f"\nTotal Adjustments: {len(manager.adjustments)}")
Specialized Scheduling:
WMS with Labor Management:
# Optimization
from pulp import *
from ortools.sat.python import cp_model
# Scheduling
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# Constraint Programming
from constraint import Problem, AllDifferentConstraint
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Weekly Schedule - Week of January 15-21, 2024
Schedule Summary:
| Day | Shift | Employees | Total Hours | Demand (hrs) | Utilization | |-----|-------|-----------|-------------|--------------|-------------| | Mon | Morning | 12 | 96 | 92 | 96% | | Mon | Afternoon | 10 | 80 | 78 | 98% | | Mon | Evening | 6 | 48 | 45 | 94% | | Tue | Morning | 11 | 88 | 85 | 97% | | ... | ... | ... | ... | ... | ... |
Employee Assignments:
Employee: EMP001 (John Smith)
Mon: Morning (6:00-14:00)
Tue: Morning (6:00-14:00)
Wed: Morning (6:00-14:00)
Thu: Morning (6:00-14:00)
Fri: Afternoon (14:00-22:00)
Total: 40 hours
Employee: EMP002 (Jane Doe)
Mon: Afternoon (14:00-22:00)
Wed: Afternoon (14:00-22:00)
Thu: Evening (22:00-6:00)
Sat: Morning (6:00-14:00)
Total: 32 hours (Part-time)
...
Cost Analysis:
| Category | Hours | Rate | Cost | |----------|-------|------|------| | Regular Time | 1,520 | $20/hr | $30,400 | | Overtime | 85 | $30/hr | $2,550 | | Temporary | 40 | $25/hr | $1,000 | | Total | 1,645 | - | $33,950 |
Performance Metrics:
Schedule Compliance:
If you need more context:
documentation
When the user wants to optimize yard operations, manage trailer parking, or improve dock door utilization. Also use when the user mentions "yard management," "trailer tracking," "yard jockey," "drop trailer program," "trailer pool," "dock scheduling," or "gate management." For cross-dock operations, see cross-docking. For warehouse design, see warehouse-design.
testing
When the user wants to optimize pick wave planning, schedule warehouse operations, or improve order fulfillment efficiency. Also use when the user mentions "wave management," "batch picking," "pick wave scheduling," "order release optimization," "wave design," or "pick wave strategy." For order batching, see order-batching-optimization. For workforce scheduling, see workforce-scheduling.
testing
When the user wants to optimize warehouse slot assignments, improve pick efficiency, or design warehouse layouts. Also use when the user mentions "slotting optimization," "slot assignment," "ABC slotting," "pick path optimization," "storage location assignment," "warehouse layout optimization," or "forward pick locations." For picker routing, see picker-routing-optimization. For warehouse design, see warehouse-design.
testing
When the user wants to optimize warehouse locations, design warehouse networks, or determine optimal warehouse placement for distribution. Also use when the user mentions "warehouse siting," "warehouse network design," "storage facility location," "fulfillment center location," "regional warehouse optimization," "warehouse consolidation," or "distribution warehouse placement." For general facility location, see facility-location-problem. For distribution centers, see distribution-center-network.