pyschedule can be used for employee scheduling. The following example is motivated by instances from:
http://www.cs.nott.ac.uk/~tec/NRP/#new_instances
We simplified these instances a little bit for the sake of exposition. First load some instance:
In [3]:
    
employee_names = ['A','B','C','D','E','F','G','H']
n_days = 14 # number of days
days = list(range(n_days))
max_seq = 5 # max number of consecutive shifts
min_seq = 2 # min sequence without gaps
max_work = 10 # max total number of shifts
min_work = 7 # min total number of shifts
max_weekend = 3 # max number of weekend shifts
# number of required shifts for each day
shift_requirements =\
{
0: 5,
1: 7,
2: 6,
3: 4,
4: 5,
5: 5,
6: 5,
7: 6,
8: 7,
9: 4,
10: 2,
11: 5,
12: 6,
13: 4
}
# specific shift requests by employees for days
shift_requests =\
[
('A',0),
('B',5),
('C',8),
('D',2),
('E',9),
('F',5),
('G',1),
('H',7),
('A',3),
('B',4),
('C',4),
('D',9),
('F',1),
('F',2),
('F',3),
('F',5),
('F',7),
('H',13)
]
    
In [ ]:
    
from pyschedule import Scenario, solvers, plotters, alt
# Create employee scheduling scenari
S = Scenario('employee_scheduling',horizon=n_days)
# Create enployees as resources indexed by namesc
employees = { name : S.Resource(name) for name in employee_names }
# Create shifts as tasks
shifts = { (day,i) : S.Task('S_%s_%s'%(str(day),str(i))) 
          for day in shift_requirements if day in days
          for i in range(shift_requirements[day]) }
# distribute shifts to days
for day,i in shifts:
    # Assign shift to its day
    S += shifts[day,i] >= day
    # The shifts on each day are interchangeable, so add them to the same group
    shifts[day,i].group = day
    # Weekend shifts get attribute week_end
    if day % 7 in {5,6}:
        shifts[day,i].week_end = 1
# There are no restrictions, any shift can be done by any employee
for day,i in shifts:
    shifts[day,i] += alt( S.resources() )
    
# Capacity restrictions
for name in employees:
    # Maximal number of shifts
    S += employees[name] <= max_work
    # Minimal number of shifts
    S += employees[name] >= min_work
    # Maximal number of weekend shifts using attribute week_end
    S += employees[name]['week_end'] <= max_weekend
# Max number of consecutive shifts
for name in employees:
    for day in range(n_days):
        S += employees[name][day:day+max_seq+1] <= max_seq
# Min sequence without gaps
for name in employees:
    # No increase in last periods
    S += employees[name][n_days-min_seq:].inc <= 0
    # No decrease in first periods
    S += employees[name][:min_seq].dec <= 0
    # No diff during time horizon
    for day in days[:-min_seq]:
        S += employees[name][day:day+min_seq+1].diff <= 1
        
# Solve and plot scenario
if solvers.mip.solve(S,kind='CBC',msg=1,random_seed=6):
    %matplotlib inline
    plotters.matplotlib.plot(S,fig_size=(12,5))
else:
    print('no solution found')
    
    
    
In [ ]:
    
import random
import time
time_limit = 10 # time limit for each run
repeats = 5 # repeated random runs because CBC might get stuck
# Iteratively add shift requests until no solution exists
for name,day in shift_requests:
    S += employees[name][day] >= 1
    for i in range(repeats):
        random_seed = random.randint(0,10000)
        start_time = time.time()
        status = solvers.mip.solve(S,kind='CBC',time_limit=time_limit,
                                   random_seed=random_seed,msg=0)
        # Break when solution found
        if status:
            break
    print(name,day,'compute time:', time.time()-start_time)
    # Break if all computed solution runs fail
    if not status:
        S -= employees[name][day] >= 1
        print('cant fit last shift request')
# Plot the last computed solution
%matplotlib inline
plotters.matplotlib.plot(S,fig_size=(12,5))