import datetime from collections import defaultdict # from shifts import RotaBuilder, days, sites class Worker: def __init__( self, Rota, id, name, site, grade, fte=100, nwd=None, end_date=None, oop=None, not_available_to_work=None, pref_not_to_work=None, work_requests=None, night_site="plymouth", # We set a default night_site previous_shifts={}, pair=0, ): self.id = id self.name = name self.site = site # Grade are equivalent to roles, by keeping them integer defining model # rules is easier. self.grade = grade self.fte = fte self.nwd = nwd self.proportion_rota_to_work = 1 self.pair = pair self.night_site = night_site self.previous_shifts = previous_shifts self.shift_target_number = defaultdict(int) days_to_work = Rota.rota_days_length if end_date is None: self.end_date = None else: self.end_date = datetime.datetime.strptime(end_date, "%d/%m/%y").date() if self.end_date > Rota.rota_end_date: self.end_date = Rota.rota_end_date else: if self.end_date > Rota.start_date: days_to_work = (self.end_date - Rota.start_date).days # add unavalabilities for weeks_days in Rota.weeks_days_product[days_to_work:]: week, day = weeks_days Rota.unavailable_to_work.add((self.id, week, day)) Rota.unavailable_to_work_reason[ (self.id, week, day) ] = "END DATE" if oop is not None: start_oop, end_oop = oop start_oop_date = datetime.datetime.strptime(start_oop, "%d/%m/%y").date() end_oop_date = datetime.datetime.strptime(end_oop, "%d/%m/%y").date() # ignore oops if they finish before the rota start date print(end_oop_date, Rota.start_date) if end_oop_date > Rota.start_date: if start_oop_date > Rota.rota_end_date: pass else: if end_oop_date > Rota.rota_end_date: end_oop_date = Rota.rota_end_date if start_oop_date < Rota.start_date: start_oop_date = Rota.start_date oop_length = (end_oop_date - start_oop_date).days days_to_work = days_to_work - oop_length days_until_oop = (start_oop_date - Rota.start_date).days for weeks_days in Rota.weeks_days_product[ days_until_oop : days_until_oop + oop_length ]: week, day = weeks_days Rota.unavailable_to_work.add((self.id, week, day)) Rota.unavailable_to_work_reason[ (self.id, week, day) ] = "OOP ()".format(oop) if pref_not_to_work is not None: # loop throught dates converting to week / day combination for date in pref_not_to_work: days_from_start = ( datetime.datetime.strptime(date, "%d/%m/%y").date() - Rota.start_date ).days week = days_from_start // 7 + 1 day = Rota.days[(days_from_start % 7)] # Weight the value to take into account the number of preferences # 1 is added to the total number of requests to ensure they do not outweight # a single (or fewer) request(s) Rota.pref_not_to_work[(self.id, week, day)] = 1 / ( len(pref_not_to_work) + 1 ) if not_available_to_work is not None: # print(not_available_to_work) # loop throught dates converting to week / day combination for date, reason in not_available_to_work: days_from_start = ( datetime.datetime.strptime(date, "%d/%m/%y").date() - Rota.start_date ).days week = days_from_start // 7 + 1 day = Rota.days[(days_from_start % 7)] Rota.unavailable_to_work.add((self.id, week, day)) Rota.unavailable_to_work_reason[(self.id, week, day)] = reason if work_requests is not None: for date, shift in work_requests: days_from_start = ( datetime.datetime.strptime(date, "%d/%m/%y").date() - Rota.start_date ).days week = days_from_start // 7 + 1 day = Rota.days[(days_from_start % 7)] Rota.work_requests.add((self.id, week, day, shift)) # Calculate the proportion of the rota that is being worked self.proportion_rota_to_work = days_to_work / Rota.rota_days_length # We have to adjust the full time equivalent for people who CCT / leave the rota early self.fte_adj = self.fte * self.proportion_rota_to_work if self.fte_adj > 100: raise ValueError("{} : fte_ajd = {}".format(self.name, self.fte_adj)) # print(self.name, self.nwd) def __lt__(self, other): return (self.site, self.grade, self.fte_adj, self.name) < ( other.site, other.grade, other.fte_adj, other.name, ) def __str__(self): nwd = ", ".join([str(i) for i in self.nwd]) if self.nwd is not None else "" return "{} {} [{}] PROC SITE:{} ({})".format( self.name, (self.site, self.grade, self.fte_adj), nwd, self.night_site, ) def get_details(self): return "{} {} {}".format(self.id, self.site[0], self.grade) def get_full_details(self): return "{}/{}/{}".format(self.site, self.grade, self.name)