diff --git a/run.py b/run.py index 39d5146..8235dfa 100644 --- a/run.py +++ b/run.py @@ -33,7 +33,8 @@ rota_collections = { "no weekends": deepcopy(Rota), "no twighlights": deepcopy(Rota), "no nights": deepcopy(Rota), - "nights + proc twighlights + normal weekends": deepcopy(Rota), + "nights + proc twighlights3 + normal weekends": deepcopy(Rota), + "nights + proc twighlights4 + normal weekends": deepcopy(Rota), "nights + proc twighlights + proc weekends": deepcopy(Rota), } @@ -211,7 +212,7 @@ rota_collections['no nights'].add_shifts( #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), ) -rota_collections['nights + proc twighlights + normal weekends'].add_shifts( +rota_collections['nights + proc twighlights4 + normal weekends'].add_shifts( SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=4), SingleShift(("exeter", ), "weekend_exeter", @@ -257,17 +258,55 @@ rota_collections['nights + proc twighlights + normal weekends'].add_shifts( constraints=["night"]), ) -rota_collections['nights + proc twighlights + proc weekends'].add_shifts( - SingleShift((sites), - "proc_twilight", - 12.5, - days[:5], - workers_required=3), - SingleShift((sites), - "proc_weekends", +rota_collections['nights + proc twighlights3 + normal weekends'].add_shifts( + SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=3), + SingleShift(("exeter", ), + "weekend_exeter", 12.5, days[5:], - workers_required=3), + assign_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("torquay", ), + "weekend_torquay", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("plymouth", ), + "weekend_plymouth", + 8, + days[5:], + workers_required=2, + assign_as_block=True), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night_weekday", + 12.25, + days[:4], + balance_offset=5, + balance_weighting=1, + workers_required=3, + force_as_block=False, + rota_on_nwds=True, + constraints=["night"]), + SingleShift((sites), + "night_weekend", + 12.25, + days[4:], + balance_offset=4, + balance_weighting=1, + workers_required=3, + force_as_block=False, + rota_on_nwds=True, + constraints=["night"]), +) + +rota_collections['nights + proc twighlights + proc weekends'].add_shifts( + SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=3), + SingleShift((sites), "proc_weekends", 12.5, days[5:], workers_required=3), #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), SingleShift((sites), "night_weekday", diff --git a/s.js b/s.js index a7a5201..6663ec5 100644 --- a/s.js +++ b/s.js @@ -74,7 +74,11 @@ tables.each((n, table) => { whole_weekends_worked = whole_weekends_worked - shifts.count(el) / 3 } // 0.5 for twilights - if(el.includes("twilight")) { days_lost = days_lost + shifts.count(el) / 2 } + if(el.includes("twilight")) { + twilight_days_lost = shifts.count(el) / 2 + days_lost = days_lost + twilight_days_lost + + } }); diff --git a/shifts.py b/shifts.py index 265e74c..2fd1c2b 100644 --- a/shifts.py +++ b/shifts.py @@ -21,6 +21,14 @@ days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] # weeks_days_product = list(itertools.product(weeks, days)) +from govuk_bank_holidays.bank_holidays import BankHolidays + +bank_holidays = BankHolidays() +bank_holiday_map = {} +for bank_holiday in bank_holidays.get_holidays(): + bank_holiday_map[bank_holiday['date']] = bank_holiday['title'] +# see BankHolidays source file for more methods and arguments… + class SingleShift(object): """Class to hold all details for a shift""" @@ -89,6 +97,11 @@ class RotaBuilder(object): self.weeks_days_product = list(itertools.product(self.weeks, days)) + if start_date.weekday() != 0: + raise ValueError( + "Start date {} must be a Mon (not a {})".format( + start_date.isoformat(), days[start_date.weekday()])) + self.start_date = start_date self.rota_days_length = len(self.weeks) * 7 self.rota_end_date = self.start_date + datetime.timedelta( @@ -96,6 +109,8 @@ class RotaBuilder(object): self.unavailable_to_work = set() self.pref_not_to_work = {} + self.work_requests = set() + self.work_requests_map = {} self.workers = [] @@ -338,14 +353,25 @@ class RotaBuilder(object): return 0 return 1 - #print(self.unavailable_to_work) - self.model.available = Param( ((worker.id, week, day) for worker in self.workers for week, day in self.get_week_day_combinations()), initialize=availability_init, ) + def work_request_init(model, wid, week, day, shift): + if (wid, week, day, shift) in self.work_requests: + self.work_requests_map[(wid, week, day)] = shift + print((wid, week, day)) + return 1 + return 0 + + self.model.work_requests = Param( + ((worker.id, week, day, shift) for worker in self.workers + for week, day, shift in self.get_all_shiftname_combinations()), + initialize=work_request_init, + ) + def pref_init(model, wid, week, day): if (wid, week, day) in self.pref_not_to_work: return self.pref_not_to_work[(wid, week, day)] @@ -943,13 +969,21 @@ class RotaBuilder(object): weekend_shift_balancing = 0 preference_constant = 10 - # Preferences + # Preferences (not to work) preferences = sum( self.model.pref_not_to_work[worker.id, week, day] * self.model.works[worker.id, week, day, shift] * preference_constant for worker in self.workers for week, day, shift in self.get_all_shiftname_combinations()) + # Work requsets + work_request_constant = 1000 + work_requests = sum( + self.model.work_requests[worker.id, week, day, shift] * + self.model.works[worker.id, week, day, shift] * + work_request_constant for worker in self.workers + for week, day, shift in self.get_all_shiftname_combinations()) + # # Spread nights if self.constraint_options["balance_nights_across_sites"]: @@ -974,7 +1008,7 @@ class RotaBuilder(object): #return weekend_shift_balancing + blocks_balancing #return shift_balancing + preferences + blocks_balancing #return shift_balancing + preferences + nights_site_balancing + blocks_balancing - return shift_balancing + night_shift_balancing + preferences + nights_site_balancing + blocks_balancing + return shift_balancing + night_shift_balancing + preferences + nights_site_balancing + blocks_balancing - work_requests # add objective function to the model. rule (pass function) or expr (pass expression directly) self.model.obj = Objective(rule=obj_rule, sense=minimize) @@ -1406,27 +1440,42 @@ class RotaResults(object): n = 0 for week, day in self.rota.get_week_day_combinations(): d = self.rota.start_date + datetime.timedelta(n) + n = n + 1 a = "-" shift_name = "" + + title = "{} ({})".format(shift_name, d) for shift in self.rota.get_shift_names_by_day(day): if model.works[worker.id, week, day, shift].value == 1: shifts.append(shift) a = shift[0] shift_name = shift break - css_class = "" + css_class = day if model.available[worker.id, week, day] > 0: available = True else: available = False css_class = "unavailable" - if worker.name == "Delilah Trimmer": - pass + + bank_holiday = "" + if d in bank_holiday_map: + css_class = " ".join((css_class, "bank-holiday")) + bank_holiday = " data-bank-holiday='{}'".format( + bank_holiday_map[d]) + + requests = "" + if (worker.id, week, day) in self.rota.work_requests_map: + css_class = " ".join((css_class, "shift-requested")) + title = " ".join((title, "[REQUESTED]")) + requests = " data-shift-request='{}'".format( + self.rota.work_requests_map[(worker.id, week, day)]) + w.append( - "