Skip to content

Instantly share code, notes, and snippets.

@veryeli
Last active May 11, 2022 18:07
Show Gist options
  • Save veryeli/f42cfb8a4d42b7a5258025d985d31ffe to your computer and use it in GitHub Desktop.
Save veryeli/f42cfb8a4d42b7a5258025d985d31ffe to your computer and use it in GitHub Desktop.
NUM_SHIFTS_PER_DAY_OF_WEEK = {
0: 5,
1: 4,
2: 7,
3: 5,
4: 4,
}
SHIFTNAME = {
0: 'LOW 1',
1: 'LOW 2',
2: 'MED',
3: 'CAP',
4: 'OUT',
5: 'LOW 3',
6: 'OUT2'
}
CAP_SHIFT = 3
MED_SHIFT = 2
OUT_SHIFT = 4
ATTORNEYS = {
# censored for privacy
}
# These attorneys could solo a homicide, possibly with supervision
CAP_STAFF = {
# censored for privacy
}
# these attorneys could solo a carjacking, possibly with supervision
MID_STAFF = {
# censored for privacy
}
LOW_STAFF = {
# censored for privacy
}
MODIFIED_MAX_SHIFTS = {
# censored for privacy
}
MAX_SHIFTS = 6
MAX_SHIFTS_PER_ATTY = {}
for anum, aname in ATTORNEYS.items():
MAX_SHIFTS_PER_ATTY[anum] = MODIFIED_MAX_SHIFTS.get(aname, 4)
# 0: Sunday, 6: Saturday, etc
DAY_OF_FIRST_OF_MONTH = 0
Wednesday, Jun 1, 2022
LOW 1: Richards, Nancy
LOW 2: O'Donnell, James
MED: Noakes, William
CAP: Riggins, Ken
OUT: Bickerdt, Tyrone
LOW 3: Washington, Earl
Thursday, Jun 2, 2022
LOW 1: Riggins, Ken
LOW 2: Nelson, Sophia
MED: Pilcowitz, Jackie
CAP: Noakes, William
OUT: Elijah, Emily
Friday, Jun 3, 2022
LOW 1: Elijah, Emily
LOW 2: Riggins, Ken
MED: Anderson, Lauren
CAP: Basen-Michon, Kim
Monday, Jun 6, 2022
LOW 1: Knoche, Jeffrey
LOW 2: Burton-Harris, Robert
MED: Longstreet, Kristine
CAP: Richards, Nancy
OUT: O'Donnell, James
Tuesday, Jun 7, 2022
LOW 1: De Mott Grady, Cait
LOW 2: Nelson, Sophia
MED: Kearney, Blase
CAP: Longstreet, Kristine
Wednesday, Jun 8, 2022
LOW 1: Parker, James
LOW 2: De Mott Grady, Cait
MED: Gagniuk, Brian
CAP: Procida, Mark
OUT: Burton-Harris, Robert
LOW 3: New, Emily
Thursday, Jun 9, 2022
LOW 1: Bickerdt, Tyrone
LOW 2: Baron, Hannah
MED: Sullivan, Andrew
CAP: Young, Elizabeth
OUT: Anderson, Lauren
Friday, Jun 10, 2022
LOW 1: New, Emily
LOW 2: Gagniuk, Brian
MED: Pilcowitz, Jackie
CAP: Robinson, Brandy
Monday, Jun 13, 2022
LOW 1: Parker, James
LOW 2: De Mott Grady, Cait
MED: Kastaw, Nanci
CAP: Quick, Cheryl
OUT: Oh, Glen
Tuesday, Jun 14, 2022
LOW 1: Kearney, Blase
LOW 2: Kirkland, Keshava A
MED: Sullivan, Andrew
CAP: Knoche, Jeffrey
Wednesday, Jun 15, 2022
LOW 1: O'Donnell, James
LOW 2: Nelson, Sophia
MED: Anderson, Lauren
CAP: Kearney, Blase
OUT: Phelps, Natalie
LOW 3: Kastaw, Nanci
Thursday, Jun 16, 2022
LOW 1: Knoche, Jeffrey
LOW 2: Elijah, Emily
MED: Burton-Harris, Robert
CAP: Bickerdt, Tyrone
OUT: Baron, Hannah
Friday, Jun 17, 2022
LOW 1: Pilcowitz, Jackie
LOW 2: Parker, James
MED: Knoche, Jeffrey
CAP: O'Donnell, James
Monday, Jun 20, 2022
LOW 1: Kastaw, Nanci
LOW 2: Washington, Earl
MED: New, Emily
CAP: Procida, Mark
OUT: Anderson, Lauren
Tuesday, Jun 21, 2022
LOW 1: Oh, Glen
LOW 2: Dial, Dezha
MED: De Mott Grady, Cait
CAP: Washington, Earl
Wednesday, Jun 22, 2022
LOW 1: Oh, Glen
LOW 2: Riggins, Ken
MED: Baron, Hannah
CAP: Young, Elizabeth
OUT: Phelps, Natalie
LOW 3: Dial, Dezha
Thursday, Jun 23, 2022
LOW 1: Dial, Dezha
LOW 2: Kirkland, Keshava A
MED: Elijah, Emily
CAP: Basen-Michon, Kim
OUT: Baron, Hannah
Friday, Jun 24, 2022
LOW 1: Noakes, William
LOW 2: Pilcowitz, Jackie
MED: Sullivan, Andrew
CAP: Parker, James
Monday, Jun 27, 2022
LOW 1: Bickerdt, Tyrone
LOW 2: Epps, Roeiah
MED: Sullivan, Andrew
CAP: Gagniuk, Brian
OUT: Young, Elizabeth
Tuesday, Jun 28, 2022
LOW 1: Kirkland, Keshava A
LOW 2: Epps, Roeiah
MED: Phelps, Natalie
CAP: Noakes, William
Wednesday, Jun 29, 2022
LOW 1: Epps, Roeiah
LOW 2: Oh, Glen
MED: Robinson, Brandy
CAP: Richards, Nancy
OUT: Quick, Cheryl
LOW 3: Nelson, Sophia
Thursday, Jun 30, 2022
LOW 1: Kastaw, Nanci
LOW 2: Washington, Earl
MED: New, Emily
CAP: Gagniuk, Brian
OUT: Burton-Harris, Robert
Procida, Mark
CAP Wednesday, Jun 8, 2022
CAP Monday, Jun 20, 2022
Longstreet, Kristine
MED Monday, Jun 6, 2022
CAP Tuesday, Jun 7, 2022
Robinson, Brandy
CAP Friday, Jun 10, 2022
MED Wednesday, Jun 29, 2022
Quick, Cheryl
CAP Monday, Jun 13, 2022
OUT Wednesday, Jun 29, 2022
Kearney, Blase
MED Tuesday, Jun 7, 2022
LOW 1 Tuesday, Jun 14, 2022
CAP Wednesday, Jun 15, 2022
Richards, Nancy
LOW 1 Wednesday, Jun 1, 2022
CAP Monday, Jun 6, 2022
CAP Wednesday, Jun 29, 2022
O'Donnell, James
LOW 2 Wednesday, Jun 1, 2022
OUT Monday, Jun 6, 2022
LOW 1 Wednesday, Jun 15, 2022
CAP Friday, Jun 17, 2022
Kastaw, Nanci
MED Monday, Jun 13, 2022
LOW 3 Wednesday, Jun 15, 2022
LOW 1 Monday, Jun 20, 2022
LOW 1 Thursday, Jun 30, 2022
Oh, Glen
OUT Monday, Jun 13, 2022
LOW 1 Tuesday, Jun 21, 2022
LOW 1 Wednesday, Jun 22, 2022
LOW 2 Wednesday, Jun 29, 2022
Riggins, Ken
CAP Wednesday, Jun 1, 2022
LOW 1 Thursday, Jun 2, 2022
LOW 2 Friday, Jun 3, 2022
LOW 2 Wednesday, Jun 22, 2022
Parker, James
LOW 1 Wednesday, Jun 8, 2022
LOW 1 Monday, Jun 13, 2022
LOW 2 Friday, Jun 17, 2022
CAP Friday, Jun 24, 2022
Basen-Michon, Kim
CAP Friday, Jun 3, 2022
CAP Thursday, Jun 23, 2022
Gagniuk, Brian
MED Wednesday, Jun 8, 2022
LOW 2 Friday, Jun 10, 2022
CAP Monday, Jun 27, 2022
CAP Thursday, Jun 30, 2022
Knoche, Jeffrey
LOW 1 Monday, Jun 6, 2022
CAP Tuesday, Jun 14, 2022
LOW 1 Thursday, Jun 16, 2022
MED Friday, Jun 17, 2022
Noakes, William
MED Wednesday, Jun 1, 2022
CAP Thursday, Jun 2, 2022
LOW 1 Friday, Jun 24, 2022
CAP Tuesday, Jun 28, 2022
Pilcowitz, Jackie
MED Thursday, Jun 2, 2022
MED Friday, Jun 10, 2022
LOW 1 Friday, Jun 17, 2022
LOW 2 Friday, Jun 24, 2022
Burton-Harris, Robert
LOW 2 Monday, Jun 6, 2022
OUT Wednesday, Jun 8, 2022
MED Thursday, Jun 16, 2022
OUT Thursday, Jun 30, 2022
De Mott Grady, Cait
LOW 1 Tuesday, Jun 7, 2022
LOW 2 Wednesday, Jun 8, 2022
LOW 2 Monday, Jun 13, 2022
MED Tuesday, Jun 21, 2022
Washington, Earl
LOW 3 Wednesday, Jun 1, 2022
LOW 2 Monday, Jun 20, 2022
CAP Tuesday, Jun 21, 2022
LOW 2 Thursday, Jun 30, 2022
Young, Elizabeth
CAP Thursday, Jun 9, 2022
CAP Wednesday, Jun 22, 2022
OUT Monday, Jun 27, 2022
New, Emily
LOW 3 Wednesday, Jun 8, 2022
LOW 1 Friday, Jun 10, 2022
MED Monday, Jun 20, 2022
MED Thursday, Jun 30, 2022
Anderson, Lauren
MED Friday, Jun 3, 2022
OUT Thursday, Jun 9, 2022
MED Wednesday, Jun 15, 2022
OUT Monday, Jun 20, 2022
Phelps, Natalie
OUT Wednesday, Jun 15, 2022
OUT Wednesday, Jun 22, 2022
MED Tuesday, Jun 28, 2022
Baron, Hannah
LOW 2 Thursday, Jun 9, 2022
OUT Thursday, Jun 16, 2022
MED Wednesday, Jun 22, 2022
OUT Thursday, Jun 23, 2022
Bickerdt, Tyrone
OUT Wednesday, Jun 1, 2022
LOW 1 Thursday, Jun 9, 2022
CAP Thursday, Jun 16, 2022
LOW 1 Monday, Jun 27, 2022
Elijah, Emily
OUT Thursday, Jun 2, 2022
LOW 1 Friday, Jun 3, 2022
LOW 2 Thursday, Jun 16, 2022
MED Thursday, Jun 23, 2022
Dial, Dezha
LOW 2 Tuesday, Jun 21, 2022
LOW 3 Wednesday, Jun 22, 2022
LOW 1 Thursday, Jun 23, 2022
Sullivan, Andrew
MED Thursday, Jun 9, 2022
MED Tuesday, Jun 14, 2022
MED Friday, Jun 24, 2022
MED Monday, Jun 27, 2022
Kirkland, Keshava A
LOW 2 Tuesday, Jun 14, 2022
LOW 2 Thursday, Jun 23, 2022
LOW 1 Tuesday, Jun 28, 2022
Epps, Roeiah
LOW 2 Monday, Jun 27, 2022
LOW 2 Tuesday, Jun 28, 2022
LOW 1 Wednesday, Jun 29, 2022
Nelson, Sophia
LOW 2 Thursday, Jun 2, 2022
LOW 2 Tuesday, Jun 7, 2022
LOW 2 Wednesday, Jun 15, 2022
LOW 3 Wednesday, Jun 29, 2022
from datetime import date
from pulp import *
from consts import NUM_SHIFTS_PER_DAY_OF_WEEK, SHIFTNAME, CAP_STAFF
from consts import MID_STAFF, LOW_STAFF, MAX_SHIFTS, MODIFIED_MAX_SHIFTS
from consts import MAX_SHIFTS_PER_ATTY, DAY_OF_FIRST_OF_MONTH, ATTORNEYS
from consts import CAP_SHIFT, MED_SHIFT, OUT_SHIFT
from utils import day_shifts
def get_choices(month, year=2022):
shifts_per_day = day_shifts(month, year)
choices = LpVariable.dicts("Choice", (ATTORNEYS, shifts_per_day, range(MAX_SHIFTS)), cat="Binary")
return choices
def schedule_badness(choices, attorney, shifts_per_day):
badness = 0
out1_shifts = lpSum(choices[attorney][day][0] for day in shifts_per_day)
out2_shifts = lpSum(choices[attorney][day][1] for day in shifts_per_day)
return min(out1_shifts, out2_shifts)
def set_up_problem(choices, month, year=2022):
prob = LpProblem("Scheduling Problem")
shifts_per_day = day_shifts(month, year)
choices = LpVariable.dicts("Choice", (ATTORNEYS, shifts_per_day, range(MAX_SHIFTS)), cat="Binary")
# one attorney per shift
for day, num_shifts in shifts_per_day.items():
for shift in range(num_shifts):
prob += lpSum([choices[attorney][day][shift]for attorney in ATTORNEYS]) == 1
# no one is scheduled where there's no shifts
for attorney in ATTORNEYS:
for day, num_shifts in shifts_per_day.items():
for shift in range(num_shifts, MAX_SHIFTS):
prob += choices[attorney][day][shift] == 0
# max shifts per month per attorney
for attorney in ATTORNEYS:
prob += lpSum([choices[attorney][day][shift]
for day in shifts_per_day
for shift in range(shifts_per_day[day])]) <= MAX_SHIFTS_PER_ATTY[attorney]
# min shifts per month per attorney (max - 1)
for attorney in ATTORNEYS:
prob += lpSum([choices[attorney][day][shift]
for day in shifts_per_day
for shift in range(shifts_per_day[day])]) >= (MAX_SHIFTS_PER_ATTY[attorney] - 1)
# max one shift per atty per day
for attorney in ATTORNEYS:
for day, num_shifts in shifts_per_day.items():
prob += lpSum([choices[attorney][day][shift]
for shift in range(num_shifts)]) <= 1
# only schedule people to the correct level of shift
for attorney in LOW_STAFF:
for day in shifts_per_day:
for shift in [2, 3, 4]:
prob += choices[attorney][day][shift] == 0
for attorney in MID_STAFF:
for day in shifts_per_day:
for shift in [3]:
prob += choices[attorney][day][shift] == 0
# all cap attorneys get a cap shift
for attorney in CAP_STAFF:
prob += lpSum([choices[attorney][day][shift]
for day in shifts_per_day
for shift in [CAP_SHIFT]]) >= 1
# all med attorneys get a med+ shift
for attorney in {**CAP_STAFF, **MID_STAFF}:
prob += lpSum([choices[attorney][day][shift]
for day in shifts_per_day
for shift in [CAP_SHIFT, MED_SHIFT, OUT_SHIFT]]) >= 1
# The problem data is written to an .lp file
prob.writeLP("Schedule.lp")
prob.solve()
print_results(choices, month, year)
return prob
def print_results(choices, month, year):
shifts_per_day = day_shifts(month, year)
for day, num_shifts in shifts_per_day.items():
if num_shifts == 0:
continue
print (date(year, month, day).strftime("%A, %b %-d, %Y"))
for shift in range(num_shifts):
print ('\t', SHIFTNAME[shift], end=': ')
for a, name in ATTORNEYS.items():
if value(choices[a][day][shift]) == 1:
print ('\t\t', name)
print()
for a, name in ATTORNEYS.items():
print(name)
for day, num_shifts in shifts_per_day.items():
if num_shifts == 0:
continue
for shift in range(num_shifts):
if value(choices[a][day][shift]) == 1:
print (SHIFTNAME[shift], date(year, month, day).strftime("%A, %b %-d, %Y"))
print()
if __name__ == '__main__':
choices = get_choices(2)
prob = set_up_problem(2)
# The problem is solved using PuLP's choice of Solver
prob.solve()
for d in shifts_per_day:
if SHIFTS_PER_DAY[d] == 0:
continue
print ('DAY: %d' % d)
for shift in range(MAX_SHIFTS):
if shift >= SHIFTS_PER_DAY[d]:
continue
print ('\t', SHIFTNAME[shift])
for a in ATTORNEYS:
if value(choices[a][d][shift]) == 1:
print ('\t\t', STAFF[a])
from calendar import monthrange
from consts import NUM_SHIFTS_PER_DAY_OF_WEEK
def day_shifts(month, year=2022):
day_of_first_of_month, days_in_month = monthrange(year, month)
days = range(1, days_in_month + 1)
shifts_per_day = {}
for day in days:
shifts_per_day[day] = NUM_SHIFTS_PER_DAY_OF_WEEK.get((day + day_of_first_of_month - 1) % 7, 0)
return shifts_per_day
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment