From 85c1344737b7606467cc255aef5d0ecc22d8e8e5 Mon Sep 17 00:00:00 2001 From: Ross Date: Sat, 15 Jan 2022 08:45:43 +0000 Subject: [PATCH] . --- leave.csv | 24 +++--- leave.py | 14 +++- rota.csv | 155 ++++++++++++++++++++++-------------- shifts.py | 225 +++++++++++++++++++++++++++++++---------------------- test.py | 60 ++++++++------ workers.py | 24 ++++-- 6 files changed, 305 insertions(+), 197 deletions(-) diff --git a/leave.csv b/leave.csv index e703237..f6be13c 100644 --- a/leave.csv +++ b/leave.csv @@ -1,25 +1,25 @@ Rota 2021/2022,,, (POSSIBLE IDT),,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,80% until dec 1st,,,,,,,,,,,,,,,, Name,,Stephanie Bailey,Luciana Calovi Motschenbacher,Layla Bell,Seren Peters,Alex wood,Salma Aslam,Max Ireland,Charles Finan,Paul Ward,Tom Welsh,Delilah Trimmer,Max Finzel,Zainab Sharaf,Maththew O'Brien,Kyaw Tint,Nicolas Dziadulewicz,Jean Skumar,Michael Tomek,Vilim Kalamar,Dhuvresh Patel,Khalil Madbak,Georgina Edwards,Jenn Haw fong,Paul Jenkins,Jenna Millington,Amoolya Mannava,Ross Kruger,Sophie McGlade,Rebecca Murphy,Fiona Lyall,Joel Lim,Wijhdan Abusrewil,Mark Haley,Andrew MacCormick,Alexander Sanchez - cabello,Sayed Hashim Alqarooni,Chong Yew Ng,Matthew Thorley,Harriet Conley,Jon Skinner,Cameron Bullock,Alexander Wijnberg,Christian Greer,Alan Eccles,Hannah Lewis,Sarath Vennam,Amina Odeh,Mo Babsail,Richard Chaytor,Sherafghan PROC,Madalina Drumea,Mark Fellows,Clement Leung -Rotation,,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford Twilights,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay Twilights,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro no PROC,Barnstaple,Taunton,Taunton +Rotation,,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford no proc,Derriford,Derriford,Derriford,Derriford,Derriford Twilights,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay Twilights,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro no PROC,Barnstaple,Taunton,Taunton Grade,,ST2,ST2,ST2,ST3,ST3,ST3,ST3,ST4,ST4,ST5,ST5,ST2,ST2,ST2,ST2,ST3,ST3,ST3,ST3,ST3,ST4,ST4,ST4,ST4,ST4,ST5,ST5,ST5,ST5,ST6,ST2,ST2,ST2,ST2,ST3,ST4,ST4,ST4,ST5,ST6,ST2,ST2,ST3,ST3,ST3,ST4,ST4,ST5,ST5,ST5 (until dec),ST3,ST2,ST4 PROC site,,Derriford,,exeter,Torbay,Exeter,,Derriford,Derriford,Exeter,Exeter,Derriford,Derriford,torbay,Torbay,derriford,,derriford,exeter,,Exeter,,,derriford,Derriford,,derriford,Derriford,Derriford,Exeter,NA,derriford,Torbay,Derriford,Derriford,,torbay,Derriford,Derriford,Torbay,NA,Exeter,Derriford,Truro,Truro,Derriford,Truro,Truro,Derriford,derriford,Derriford,Barnstaple,NA,NA -%FTE,,100,0,100,100,100,60,100,100,100,100,80,100,100,100,100,100,60,80,100,100,0,80,100,100,60,100,100,80,100,60,100,100,100,100,100,100,100,100,60,100,100,100,100,80,100,100,100,100,100,100,80,0,0 +%FTE,,0,0,100,100,100,0,100,100,100,100,80,100,100,100,100,100,60,80,100,100,0,80,100,100,0,100,100,0,100,60,100,100,100,100,100,100,100,100,60,100,100,100,100,80,100,100,100,100,100,100,80,0,0 NWD,,,,,,,"Thursday,Friday",,"Tuesday,Thursday",,,wednesday,,,,,,"Wednesday,thursday",Wednesday,,,,Friday,,,"thursday, friday",,,Friday,,"monday,friday",,"thursday, friday",,,,,,,"monday,thursday",,,,,"wednesday, thursday,Friday",,,,,,,,, Flexible NWD,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, End Date,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -OOP,,,,,,,6/9/21-31/3/22,,,,,,,,,,,,,,,,,,,6/9/21-2/5/22,,,1/1/22-5/9/22,6/9/21-28/2/22,,,,,,,,,,6/9/21-20/9/21,,,,,,,,,,,,,, +OOP,,,,,,,6/9/21-31/3/22,,,,,,,,,,,,,,,,,,,6/9/21-2/5/22,,,1/1/22-5/9/22,6/9/21-28/2/22,,,1/3/22-31/3/22,,,,,,,6/9/21-20/9/21,,,,,,,,,1/8/22-4/9/22,23/7/22-4/9/22,,,, Group,,Y1G1,Y1G1,Y1G2,Y3G1,Y3G1,Y3G2,Y3G2,,,,,Y2G1,Y2G2,Y2G2,Y2G2,Y3G1,Y3G1,Y3G2,Y3G2,Y3G2,,,,,,,,,,,Y2G1,Y2G1,Y2G2,Y2G2,Y3G2,,,,,,Y2G1,Y2G2,Y3G1,Y3G1,Y3G2,,,,,,,Y2G1, ?PROC,,,,,,,,,,,,,,,,,,,,,,NO,,,,WEEKEND ONLY,,,,,No,,,,,,,,,,No,,,,,,,,,,Yes - until ST6 in dec,???,NO,NO Pair,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,2,,, -Extra_night_weekend,,3.72,,-0.28,-0.28,-0.28,,-0.28,-0.28,-0.28,-0.28,0.63,-0.28,-0.28,-0.28,-0.28,-0.28,4.63,2.17,-0.28,3.72,,2.17,-0.28,-0.28,,-0.28,-0.28,,0.3,0,-0.28,-0.28,-0.28,3.72,-0.28,-0.28,-1.06,-0.28,0.27,,-0.28,-0.28,-0.28,-1.83,-0.28,-0.28,-0.28,-0.28,-0.28,0,7.72,, -Extra_night_weekday,,-0.21,,-0.21,-0.21,-0.21,,-0.21,-0.21,-0.21,-0.21,0.47,-0.21,2.79,2.79,2.79,-0.21,-2.53,-1.37,-0.21,-0.21,,-1.37,-0.21,-0.21,,-0.21,-0.21,,0.22,0,-0.21,-0.21,-0.21,-0.21,-0.21,-0.21,2.21,-0.21,0.21,,-0.21,-0.21,-0.21,1.63,-0.21,-0.21,-0.21,-0.21,-0.21,0,5.79,, -Extra_twilight,,-1.97,,-0.97,1.03,1.03,,-0.97,-1.97,1.03,1.03,-0.38,0.47,0.47,-1.53,0.47,-0.53,-3.92,-2.23,0.47,-2.53,,-2.23,0.47,0.47,,0.47,0.47,,0.33,0.08,1.95,-0.05,1.95,-1.05,1.95,1.95,1.56,3.95,1.73,,-1.39,-1.39,0.61,0.49,-1.39,-0.39,1.61,2.61,-1.39,0.61,0,, -Extra_weekend,,-0.73,,2.27,-0.73,-0.73,,2.27,2.27,-0.73,-0.73,-1.04,,,,,,,,,,,,,,,,,,,,1.46,3.46,1.46,0.46,1.46,1.46,0.42,0.46,-0.2,,1.96,-1.04,-1.04,-0.63,1.96,0.96,-1.04,-2.04,1.96,-1.04,0,, +Extra_night_weekday,,3.72,,-0.28,-0.28,-0.28,,-0.28,-0.28,-0.28,-0.28,0.63,-0.28,-0.28,-0.28,-0.28,-0.28,4.63,2.17,-0.28,3.72,,2.17,-0.28,-0.28,,-0.28,-0.28,,0.3,0,-0.28,-0.28,-0.28,3.72,-0.28,-0.28,-1.06,-0.28,0.27,,-0.28,-0.28,-0.28,-1.83,-0.28,-0.28,-0.28,-0.28,-0.28,0,-0.28,, +Extra_night_weekend,,-0.21,,-0.21,-0.21,-0.21,,-0.21,-0.21,-0.21,-0.21,0.47,-0.21,2.79,2.79,2.79,-0.21,-2.53,-1.37,-0.21,-0.21,,-1.37,-0.21,-0.21,,-0.21,-0.21,,0.22,0,-0.21,-0.21,-0.21,-0.21,-0.21,-0.21,2.21,-0.21,0.21,,-0.21,-0.21,-0.21,1.63,-0.21,-0.21,-0.21,-0.21,-0.21,0,2.79,, +Extra_twilight,,-1.97,,-0.97,1.03,1.03,,-0.97,-1.97,1.03,1.03,-0.38,0.47,0.47,-1.53,0.47,-0.53,-3.92,-2.23,0.47,-2.53,,-2.23,0.47,0.47,,0.47,0.47,,0.33,0.08,,,,,,,,,,,-1.39,-1.39,0.61,0.49,-1.39,-0.39,1.61,2.61,-1.39,0.61,0,, +Extra_weekend,,-0.73,,2.27,-0.73,-0.73,,2.27,2.27,-0.73,-0.73,-1.04,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1.96,-1.04,-1.04,-0.63,1.96,0.96,-1.04,-2.04,1.96,-1.04,0,, Extra_weekend_plymouth1,,,,,,,,,,,,,-2.22,-0.22,-2.22,-0.22,-0.22,2.27,-0.98,-2.22,-0.22,,1.02,1.78,-0.22,,1.78,1.78,,0.15,0,,,,,,,,,,,,,,,,,,,,,,, Extra_weekend_plymouth2,,,,,,,,,,,,,1.78,-2.22,1.78,-2.22,-0.22,0.27,3.02,1.78,-0.22,,1.02,-2.22,-0.22,,-2.22,-0.22,,0.15,0,,,,,,,,,,,,,,,,,,,,,,, Extra_plymouth_bank_holiday,,,,,,,,,,,,,0.2,0.2,0.2,0.2,0.2,0.12,-0.84,0.2,0.2,,0.16,0.2,0.2,,0.2,-1.8,,0.01,0.12,,,,,,,,,,,,,,,,,,,,,,, -bank_holidays_worked,,,,1,,,,1,,2,1,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,2,1,2,,2,1,,3,,,,,,2,2,,,,2,,2,,1,,1,2,,,,2,2,,,,2,,,1,,, +bank_holidays_worked,,,,1,,,,1,,2,1,3,,2,1,2,,2,1,,3,,,,,,2,2,,,,2,,2,,1,,1,2,,,,2,2,,,,2,,,1,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Date,Exams,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 6/9/21,,,NO ONCALL,,,POST ONCALL,Mat leave,,exeter_twilight,,exeter_twilight,,,A/L,,,,,A/L,,,No Oncall,POST ONCALL,POST ONCALL,POST ONCALL,Mat leave,,,plymouth_twilight,OOPT,NO oncals,,POST ONCALL,,,,A/L,torbay_twilight,torbay_twilight,Mat leave,POST ONCALL,,,,,truro_twilight,truro_twilight,,A/L,POST ONCALL,,,, 7/9/21,,,NO ONCALL,,,POST ONCALL,Mat leave,,exeter_twilight,,exeter_twilight,,,A/L,,,,,A/L,,,No Oncall,POST ONCALL,POST ONCALL,POST ONCALL,Mat leave,,,plymouth_twilight,OOPT,NO oncals,,POST ONCALL,,,,A/L,torbay_twilight,torbay_twilight,Mat leave,POST ONCALL,,,,,truro_twilight,truro_twilight,,A/L,POST ONCALL,,,, @@ -205,14 +205,14 @@ Date,Exams,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 6/3/22,,,NO ONCALL,,,,Mat leave,,,,OOPT,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,IR oncall,,OOPC,,,,,,A/L,,,,,,,,,A/L,A/L,A/L,,,, 7/3/22,,OFF ONCALL,IDT,POST ONCALL,POST ONCALL,,OOPT,,,,OOPT,,,,,,,POST ONCALL,,,,No Oncall,,POST ONCALL,,Mat leave,,POST ONCALL,OOPT,,,,OOPC,,,POST ONCALL,POST ONCALL,,A/L,,,,,A/L,POST ONCALL,,,,,A/L,,,, 8/3/22,,OFF ONCALL,IDT,POST ONCALL,POST ONCALL,,OOPT,,,,OOPT,,,,,,,POST ONCALL,,,,No Oncall,,POST ONCALL,,Mat leave,,POST ONCALL,OOPT,,,,OOPC,,,POST ONCALL,POST ONCALL,,A/L,,,,,A/L,POST ONCALL,,,,,A/L,,,, -9/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,, +9/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,POST ONCALL,,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,, 10/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,, 11/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,, -12/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,A/L,,,,,A/L,,A/L,,,,A/L,,,, +12/3/22,,OFF ONCALL,IDT,POST ONCALL,,,OOPT,,,,OOPT,A/L,,,,,,POST ONCALL,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,POST ONCALL,,A/L,,,,,A/L,,A/L,,,,A/L,,,, 13/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,A/L,,,,,A/L,,A/L,,,,A/L,,,, 14/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,, 15/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,, -16/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,A/L,,,,, +16/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,POST ONCALL,A/L,,A/L,,pre 2b,A/L,,,,, 17/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,A/L,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,, 18/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,, 19/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,A/L,,,,,IR ONCALL,,,A/L,,A/L,,pre 2b,,A/L,,,, diff --git a/leave.py b/leave.py index 24a2dd7..45db3b6 100644 --- a/leave.py +++ b/leave.py @@ -35,7 +35,8 @@ def load_leave(Rota, use_live_rota): workers[name]["leave"] = [] workers[name]["requests"] = [] workers[name]["site_pref"] = [] - workers[name]["shift_balance_extra"] + workers[name]["shift_balance_extra"] = {} + workers[name]["bank_holiday_extra"] = 0 for i in range(len(r)): try: @@ -88,7 +89,16 @@ def load_leave(Rota, use_live_rota): elif row_title.startswith("Extra_"): if lower_item: shift = row_title[len("Extra_") :] - worker["shift_balance_extra"] = shift + if shift == "twilight": + shift = f"{worker['site']}_{shift}" + elif shift == "weekend": + shift = f"weekend_{worker['site']}" + worker["shift_balance_extra"][shift] = float(lower_item) + + elif row_title == "bank_holidays_worked": + if lower_item: + print("BH", lower_item) + worker["bank_holiday_extra"] = int(lower_item) elif re.match(date_re, row_title) is not None: diff --git a/rota.csv b/rota.csv index 8b47c3c..9953c6e 100644 --- a/rota.csv +++ b/rota.csv @@ -1,60 +1,95 @@ -"Name","Madalina Drumea","Layla Bell","Stephanie Bailey","Salma Aslam","Alex wood","Max Ireland","Seren Peters","Charles Finan","Paul Ward","Delilah Trimmer","Matt Murphey","Tom Welsh","Kyaw Tint","Maththew O'Brien","Max Finzel","Zainab Sharaf","Jean Skumar","Michael Tomek","Dhuvresh Patel","Nicolas Dziadulewicz","Vilim Kalamar","Jenna Millington","Georgina Edwards","Jenn Haw fong","Paul Jenkins","Rebecca Murphy","Sophie McGlade","Amoolya Mannava","Ross Kruger","Fiona Lyall","Jon Skinner","Andrew MacCormick","Mark Haley","Wijhdan Abusrewil","Alexander Sanchez - cabello","Chong Yew Ng","Matthew Thorley","Sayed Hashim Alqarooni","Harriet Conley","Alexander Wijnberg","Cameron Bullock","Alan Eccles","Christian Greer","Hannah Lewis","Amina Odeh","Sarath Vennam","Mo Babsail","Richard Chaytor","Sherafghan PROC" -"Site","barnstaple","exeter","exeter","exeter","exeter","exeter","exeter","exeter","exeter","exeter","exeter","exeter","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth_twilights","plymouth_twilights","torbay","torbay","torbay","torbay","torbay","torbay","torbay","torbay","truro","truro","truro","truro","truro","truro","truro","truro","truro","truro no proc" -"Grade","3","2","2","3","3","3","3","4","4","5","5","5","2","2","2","2","3","3","3","3","3","4","4","4","4","5","5","5","5","6","6","2","2","2","3","4","4","4","5","2","2","3","3","3","4","4","5","5","5" -"FTE","100","100","100","60","100","100","100","100","100","60","100","100","100","100","100","100","60","80","100","100","100","60","80","100","100","100","80","100","100","60","100","100","100","100","100","90","100","100","60","100","100","80","100","100","100","100","100","100","100" -"Week 1 Day Mon","night_weekday","","","","","","","exeter_twilight","","","","","","","","","","","","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","torbay_twilight","","","","","night_weekday","","","","truro_twilight","","","" -"Week 1 Day Tue","night_weekday","","","","","","","","","","exeter_twilight","","","","","","","","","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","torbay_twilight","","","","","night_weekday","","truro_twilight","","","","","" -"Week 1 Day Wed","night_weekday","","","","","","","","","exeter_twilight","","","","","","","","","","","","","","","plymouth_twilight","","","night_weekday","","","","torbay_twilight","","","","","","","","","","night_weekday","truro_twilight","","","","","","" -"Week 1 Day Thu","night_weekday","","","","exeter_twilight","","","","","","","","plymouth_twilight","","","","","","","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","","","","","truro_twilight","night_weekday","","","","","","","" -"Week 1 Day Fri","","","","","","","","","weekend_exeter","","","","","plymouth_twilight","","","","","night_weekend","","","","","","","","","","","","","","","","","night_weekend","","weekend_torbay","","","","","","","night_weekend","weekend_truro","","","" -"Week 1 Day Sat","","","","","","","","","weekend_exeter","","","","","","weekend_plymouth1","","","","night_weekend","","","","","weekend_plymouth2","","","","","","","","","","","","night_weekend","","weekend_torbay","","","","","","","night_weekend","weekend_truro","","","" -"Week 1 Day Sun","","","","","","","","","weekend_exeter","","","","","","weekend_plymouth1","","","","night_weekend","","","","","weekend_plymouth2","","","","","","","","","","","","night_weekend","","weekend_torbay","","","","","","","night_weekend","weekend_truro","","","" -"Week 2 Day Mon","","","","","","night_weekday","","exeter_twilight","","","","","","","","plymouth_twilight","","","","","","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","night_weekday","","","","truro_twilight","","" -"Week 2 Day Tue","","","exeter_twilight","","","night_weekday","","","","","","","plymouth_twilight","","","","","","","","","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","truro_twilight","","","night_weekday","","","","","","" -"Week 2 Day Wed","","","","","","night_weekday","","exeter_twilight","","","","","","","plymouth_twilight","","","","","","","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","night_weekday","","truro_twilight","","","","" -"Week 2 Day Thu","","","","","","night_weekday","","","exeter_twilight","","","","","","plymouth_twilight","","","","","","","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","night_weekday","","truro_twilight","","","","" -"Week 2 Day Fri","","","weekend_exeter","","","","","","","","","","","","","plymouth_twilight","","","","","","","night_weekend","","night_weekend","","","","","","","","","weekend_torbay","night_weekend","","","","","","","","","weekend_truro","","","","","" -"Week 2 Day Sat","","","weekend_exeter","","","","","","","","","","weekend_plymouth2","","","","","","","","","","night_weekend","","night_weekend","","","weekend_plymouth1","","","","","","weekend_torbay","night_weekend","","","","","","","","","weekend_truro","","","","","" -"Week 2 Day Sun","","","weekend_exeter","","","","","","","","","","weekend_plymouth2","","","","","","","","","","night_weekend","","night_weekend","","","weekend_plymouth1","","","","","","weekend_torbay","night_weekend","","","","","","","","","weekend_truro","","","","","" -"Week 3 Day Mon","","","","","","exeter_twilight","night_weekday","","","","","","","","plymouth_twilight","","","","night_weekday","","","","","","","","","","","","","torbay_twilight","","","","","","","","","truro_twilight","","","","","","night_weekday","","" -"Week 3 Day Tue","","","","","exeter_twilight","","night_weekday","","","","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","","","","","torbay_twilight","","","","","","","","truro_twilight","","","","","","","night_weekday","","" -"Week 3 Day Wed","","exeter_twilight","","","","","night_weekday","","","","","","","","","plymouth_twilight","","","night_weekday","","","","","","","","","","","","","torbay_twilight","","","","","","","","truro_twilight","","","","","","","night_weekday","","" -"Week 3 Day Thu","","","exeter_twilight","","","","night_weekday","","","","","","","","","","","","night_weekday","","","","plymouth_twilight","","","","","","","","","torbay_twilight","","","","","","","","","","","","","","","night_weekday","truro_twilight","" -"Week 3 Day Fri","","","","","night_weekend","weekend_exeter","","","","","","","","","","plymouth_twilight","night_weekend","","","","","","","","","","night_weekend","","","","","","weekend_torbay","","","","","","","","weekend_truro","","","","","","","","" -"Week 3 Day Sat","","","","","night_weekend","weekend_exeter","","","","","","","","","","","night_weekend","","","","weekend_plymouth2","","","","","","night_weekend","","weekend_plymouth1","","","","weekend_torbay","","","","","","","","weekend_truro","","","","","","","","" -"Week 3 Day Sun","","","","","night_weekend","weekend_exeter","","","","","","","","","","","night_weekend","","","","weekend_plymouth2","","","","","","night_weekend","","weekend_plymouth1","","","","weekend_torbay","","","","","","","","weekend_truro","","","","","","","","" -"Week 4 Day Mon","","","exeter_twilight","","","","","","","","night_weekday","","","plymouth_twilight","","","","","","night_weekday","","","","","","","","","","","","","","","torbay_twilight","","","","","","","","","night_weekday","","","","","truro_twilight" -"Week 4 Day Tue","","","exeter_twilight","","","","","","","","night_weekday","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","","","","","","","torbay_twilight","","","","","","","","","night_weekday","","","","truro_twilight","" -"Week 4 Day Wed","","exeter_twilight","","","","","","","","","night_weekday","","","","","","","","","night_weekday","","","","","","","","","","plymouth_twilight","","","","","torbay_twilight","","","","","","truro_twilight","","","night_weekday","","","","","" -"Week 4 Day Thu","","","","","","","exeter_twilight","","","","night_weekday","","","","","plymouth_twilight","","","","night_weekday","","","","","","","","","","","","","","","torbay_twilight","","","","","","","","","night_weekday","","","","truro_twilight","" -"Week 4 Day Fri","night_weekend","weekend_exeter","","","","","","","","","","","","","","","","","","","","","","","","","","night_weekend","plymouth_twilight","","","","","weekend_torbay","","","","","","","","","night_weekend","","","","weekend_truro","","" -"Week 4 Day Sat","night_weekend","weekend_exeter","","","","","","","","","","","weekend_plymouth2","","","weekend_plymouth1","","","","","","","","","","","","night_weekend","","","","","","weekend_torbay","","","","","","","","","night_weekend","","","","weekend_truro","","" -"Week 4 Day Sun","night_weekend","weekend_exeter","","","","","","","","","","","weekend_plymouth2","","","weekend_plymouth1","","","","","","","","","","","","night_weekend","","","","","","weekend_torbay","","","","","","","","","night_weekend","","","","weekend_truro","","" -"Week 5 Day Mon","","","night_weekday","","","","","","","","exeter_twilight","","","","plymouth_twilight","","","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","","","","","","","","","","night_weekday","truro_twilight" -"Week 5 Day Tue","","","night_weekday","","","","","","","","","exeter_twilight","","","","","plymouth_twilight","","","","","","","","night_weekday","","","","","","","","","","torbay_twilight","","","","","truro_twilight","","","","","","","","night_weekday","" -"Week 5 Day Wed","","exeter_twilight","night_weekday","","","","","","","","","","","","","","","","","","","","","","night_weekday","","plymouth_twilight","","","","","","","","torbay_twilight","","","","","","","","","truro_twilight","","","","night_weekday","" -"Week 5 Day Thu","","exeter_twilight","night_weekday","","","","","","","","","","","","","","","","","","","","","","night_weekday","","plymouth_twilight","","","","","","","","torbay_twilight","","","","","","","","","","","","truro_twilight","night_weekday","" -"Week 5 Day Fri","","","","","weekend_exeter","","","","","","","night_weekend","","plymouth_twilight","","","","night_weekend","","","","","","","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","","","","" -"Week 5 Day Sat","","","","","weekend_exeter","","","","","","","night_weekend","","","","","","night_weekend","","weekend_plymouth2","","","","weekend_plymouth1","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","","","","" -"Week 5 Day Sun","","","","","weekend_exeter","","","","","","","night_weekend","","","","","","night_weekend","","weekend_plymouth2","","","","weekend_plymouth1","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","","","","" -"Week 6 Day Mon","","","","","","","","night_weekday","exeter_twilight","","","","night_weekday","","","","","","","","","","","","","","","plymouth_twilight","","","","","","torbay_twilight","","","","night_weekday","","","","","truro_twilight","","","","","","" -"Week 6 Day Tue","","","","","","","","night_weekday","","","exeter_twilight","","night_weekday","","","","","","","","","","plymouth_twilight","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","truro_twilight","","","","" -"Week 6 Day Wed","","","","","","","","night_weekday","exeter_twilight","","","","night_weekday","","","","plymouth_twilight","","","","","","","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","truro_twilight","","","" -"Week 6 Day Thu","","","","","","","","night_weekday","","exeter_twilight","","","night_weekday","","","","","","","","plymouth_twilight","","","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","truro_twilight","","","","","" -"Week 6 Day Fri","","night_weekend","","","","","","","","","weekend_exeter","","","","","","","","","","","","","","","","","","night_weekend","","plymouth_twilight","","","","weekend_torbay","","night_weekend","","","","","","","","","","","","weekend_truro" -"Week 6 Day Sat","","night_weekend","","","","","","","","","weekend_exeter","","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","","","","","night_weekend","","","","","","weekend_torbay","","night_weekend","","","","","","","","","","","","weekend_truro" -"Week 6 Day Sun","","night_weekend","","","","","","","","","weekend_exeter","","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","","","","","night_weekend","","","","","","weekend_torbay","","night_weekend","","","","","","","","","","","","weekend_truro" -"Week 7 Day Mon","","","","","exeter_twilight","","","","","","","","","","","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","","","night_weekday","truro_twilight","","","" -"Week 7 Day Tue","","","","","","","exeter_twilight","","","","","","","","","","","plymouth_twilight","","","","","","night_weekday","","","","","","","","","","night_weekday","","","","torbay_twilight","","truro_twilight","","","","","night_weekday","","","","" -"Week 7 Day Wed","","","","","","","","","","","","exeter_twilight","","plymouth_twilight","","","","","","","","","","night_weekday","","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","truro_twilight","","night_weekday","","","","" -"Week 7 Day Thu","","","","","","","","","","","","exeter_twilight","","","","","","plymouth_twilight","","","","","","night_weekday","","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","truro_twilight","","night_weekday","","","","" -"Week 7 Day Fri","","","","","","","weekend_exeter","","","","","","","","","","","","plymouth_twilight","night_weekend","","","","","","","","","","","","weekend_torbay","","","","night_weekend","","","","night_weekend","","weekend_truro","","","","","","","" -"Week 7 Day Sat","","","","","","","weekend_exeter","","","","","","","","weekend_plymouth1","","","","","night_weekend","weekend_plymouth2","","","","","","","","","","","weekend_torbay","","","","night_weekend","","","","night_weekend","","weekend_truro","","","","","","","" -"Week 7 Day Sun","","","","","","","weekend_exeter","","","","","","","","weekend_plymouth1","","","","","night_weekend","weekend_plymouth2","","","","","","","","","","","weekend_torbay","","","","night_weekend","","","","night_weekend","","weekend_truro","","","","","","","" -"Week 8 Day Mon","","","","","","","","","night_weekday","","exeter_twilight","","","","","","","","","","","","plymouth_twilight","","","","","","","","","","night_weekday","torbay_twilight","","","","","","","","","","truro_twilight","","night_weekday","","","" -"Week 8 Day Tue","","","","","","exeter_twilight","","","night_weekday","","","","","","","","","","","","","","","","","","","","","","plymouth_twilight","","night_weekday","","","","","","torbay_twilight","","","","","","","night_weekday","","","truro_twilight" -"Week 8 Day Wed","","","","","","","","exeter_twilight","night_weekday","","","","","","","","","","","","","","","","","","","","","","plymouth_twilight","","night_weekday","","","torbay_twilight","","","","","","","","","","night_weekday","truro_twilight","","" -"Week 8 Day Thu","","","","","","exeter_twilight","","","night_weekday","","","","","","","","","plymouth_twilight","","","","","","","","","","","","","","","night_weekday","","","torbay_twilight","","","","","","truro_twilight","","","","night_weekday","","","" -"Week 8 Day Fri","","","","","","","","weekend_exeter","","night_weekend","","","","night_weekend","","","","","","","","","","plymouth_twilight","","","","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro","" -"Week 8 Day Sat","","","","","","","","weekend_exeter","","night_weekend","","","","night_weekend","","","","","weekend_plymouth1","","","","","","weekend_plymouth2","","","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro","" -"Week 8 Day Sun","","","","","","","","weekend_exeter","","night_weekend","","","","night_weekend","","","","","weekend_plymouth1","","","","","","weekend_plymouth2","","","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro","" +"Name","Madalina Drumea","Layla Bell","Alex wood","Max Ireland","Seren Peters","Charles Finan","Paul Ward","Delilah Trimmer","Tom Welsh","Kyaw Tint","Maththew O'Brien","Max Finzel","Zainab Sharaf","Jean Skumar","Michael Tomek","Dhuvresh Patel","Nicolas Dziadulewicz","Vilim Kalamar","Georgina Edwards","Jenn Haw fong","Paul Jenkins","Amoolya Mannava","Rebecca Murphy","Ross Kruger","Fiona Lyall","Wijhdan Abusrewil","Andrew MacCormick","Joel Lim","Mark Haley","Alexander Sanchez - cabello","Chong Yew Ng","Matthew Thorley","Sayed Hashim Alqarooni","Harriet Conley","Jon Skinner","Alexander Wijnberg","Cameron Bullock","Alan Eccles","Christian Greer","Hannah Lewis","Amina Odeh","Sarath Vennam","Mo Babsail","Richard Chaytor","Sherafghan PROC" +"Site","barnstaple","exeter","exeter","exeter","exeter","exeter","exeter","exeter","exeter","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth","plymouth_twilights","torbay","torbay","torbay","torbay","torbay","torbay","torbay","torbay","torbay","torbay twilights","truro","truro","truro","truro","truro","truro","truro","truro","truro","truro no proc" +"Grade","3","2","3","3","3","4","4","5","5","2","2","2","2","3","3","3","3","3","4","4","4","5","5","5","6","2","2","2","2","3","4","4","4","5","6","2","2","3","3","3","4","4","5","5","5" +"FTE","80","100","100","100","100","100","100","80","100","100","100","100","100","60","80","100","100","100","80","100","100","100","100","100","60","100","100","100","100","100","100","100","100","60","100","100","100","80","100","100","100","100","100","100","100" +"Week 1 Day Mon","","","night_weekday","","","exeter_twilight","","","","","","","","","","","","","night_weekday","","","plymouth_twilight","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","truro_twilight","","","","","" +"Week 1 Day Tue","","","night_weekday","","","","","exeter_twilight","","","","","","","","","plymouth_twilight","","night_weekday","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","","","truro_twilight","","" +"Week 1 Day Wed","","","night_weekday","","","exeter_twilight","","","","","","","","","","","","","night_weekday","","","","plymouth_twilight","","","","night_weekday","","","","","","","","torbay_twilight","","","","","","","truro_twilight","","","" +"Week 1 Day Thu","","exeter_twilight","night_weekday","","","","","","","","","","plymouth_twilight","","","","","","night_weekday","","","","","","","","night_weekday","","","","","","","","torbay_twilight","","","","","truro_twilight","","","","","" +"Week 1 Day Fri","","","","","night_weekend","","weekend_exeter","","","","","night_weekend","","","","plymouth_twilight","","","","","","","","","","","","","weekend_torbay","","","","","","","","","","","","weekend_truro","","night_weekend","","" +"Week 1 Day Sat","","","","","night_weekend","","weekend_exeter","","","weekend_plymouth1","","night_weekend","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_torbay","","","","","","","","","","","","weekend_truro","","night_weekend","","" +"Week 1 Day Sun","","","","","night_weekend","","weekend_exeter","","","weekend_plymouth1","","night_weekend","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_torbay","","","","","","","","","","","","weekend_truro","","night_weekend","","" +"Week 2 Day Mon","","","exeter_twilight","night_weekday","","","","","","","","","","","","night_weekday","","","","","","","plymouth_twilight","","","","torbay_twilight","","","","","","","night_weekday","","","","","","","","","","","truro_twilight" +"Week 2 Day Tue","","exeter_twilight","","night_weekday","","","","","","","","","plymouth_twilight","","","night_weekday","","","","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","truro_twilight","","","","","","","" +"Week 2 Day Wed","","","","night_weekday","exeter_twilight","","","","","","","","","","","night_weekday","","plymouth_twilight","","","","","","","","","torbay_twilight","","","","","","","night_weekday","","","","","","","","","","truro_twilight","" +"Week 2 Day Thu","","","","night_weekday","","","exeter_twilight","","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","","","torbay_twilight","","","","","","","night_weekday","","","","","","","","","truro_twilight","","" +"Week 2 Day Fri","","","","","","weekend_exeter","","","","","","","night_weekend","","","","","","","plymouth_twilight","","","","","","","","","","","weekend_torbay","night_weekend","","","","","weekend_truro","","","","","night_weekend","","","" +"Week 2 Day Sat","","","","","","weekend_exeter","","","","","","","night_weekend","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","","","","","","weekend_torbay","night_weekend","","","","","weekend_truro","","","","","night_weekend","","","" +"Week 2 Day Sun","","","","","","weekend_exeter","","","","","","","night_weekend","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","","","","","","weekend_torbay","night_weekend","","","","","weekend_truro","","","","","night_weekend","","","" +"Week 3 Day Mon","","","exeter_twilight","","","","","","","","","","","","","","","","","plymouth_twilight","night_weekday","","","","","","night_weekday","torbay_twilight","","","","","","","","","","night_weekday","","truro_twilight","","","","","" +"Week 3 Day Tue","","exeter_twilight","","","","","","","","","","","","","","","","","","","night_weekday","","","","plymouth_twilight","","night_weekday","torbay_twilight","","","","","","","","","","night_weekday","","truro_twilight","","","","","" +"Week 3 Day Wed","","","","","","exeter_twilight","","","","","","","","","","","","","plymouth_twilight","","night_weekday","","","","","","night_weekday","","","torbay_twilight","","","","","","","truro_twilight","night_weekday","","","","","","","" +"Week 3 Day Thu","","","exeter_twilight","","","","","","","","","plymouth_twilight","","","","","","","","","night_weekday","","","","","","night_weekday","torbay_twilight","","","","","","","","","","night_weekday","","","","","truro_twilight","","" +"Week 3 Day Fri","night_weekend","weekend_exeter","","","","","","","","","","","","","","","","","","","","plymouth_twilight","","night_weekend","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro" +"Week 3 Day Sat","night_weekend","weekend_exeter","","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_plymouth1","","","","night_weekend","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro" +"Week 3 Day Sun","night_weekend","weekend_exeter","","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_plymouth1","","","","night_weekend","","","","","","","","","","weekend_torbay","","night_weekend","","","","","","","","","weekend_truro" +"Week 4 Day Mon","","","night_weekday","","exeter_twilight","","","","","","","","night_weekday","","","","","plymouth_twilight","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","","","","truro_twilight","","" +"Week 4 Day Tue","","","night_weekday","","","","","","exeter_twilight","","plymouth_twilight","","night_weekday","","","","","","","","","","","","","","","","","","","night_weekday","","","torbay_twilight","","truro_twilight","","","","","","","","" +"Week 4 Day Wed","","","night_weekday","exeter_twilight","","","","","","plymouth_twilight","","","night_weekday","","","","","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","","","","","","truro_twilight" +"Week 4 Day Thu","","","night_weekday","exeter_twilight","","","","","","","","","night_weekday","","","","","","","","","","","","plymouth_twilight","","","","","torbay_twilight","","night_weekday","","","","","","","","","","","","","truro_twilight" +"Week 4 Day Fri","","","","","","night_weekend","","","weekend_exeter","","night_weekend","","","","","","plymouth_twilight","","","","","","","","","","","","","","weekend_torbay","","","","","","weekend_truro","","","","","","","night_weekend","" +"Week 4 Day Sat","","","","","","night_weekend","","","weekend_exeter","","night_weekend","","","weekend_plymouth1","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_torbay","","","","","","weekend_truro","","","","","","","night_weekend","" +"Week 4 Day Sun","","","","","","night_weekend","","","weekend_exeter","","night_weekend","","","weekend_plymouth1","","","","","","","","","weekend_plymouth2","","","","","","","","weekend_torbay","","","","","","weekend_truro","","","","","","","night_weekend","" +"Week 5 Day Mon","","","","","exeter_twilight","","","","","","","","","","","","plymouth_twilight","","","night_weekday","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","","","","truro_twilight","night_weekday","","","" +"Week 5 Day Tue","","","","","","","","exeter_twilight","","","","","","","","","","","","night_weekday","","plymouth_twilight","","","","","","","night_weekday","","","","torbay_twilight","","","","","truro_twilight","","","","night_weekday","","","" +"Week 5 Day Wed","","","","","exeter_twilight","","","","","","","","","","","","","","","night_weekday","","","","plymouth_twilight","","","","","night_weekday","","","","torbay_twilight","","","","","","","","","night_weekday","","truro_twilight","" +"Week 5 Day Thu","","","","","","","","","exeter_twilight","","","","","","","","plymouth_twilight","","","night_weekday","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","","","","","night_weekday","","truro_twilight","" +"Week 5 Day Fri","","","","weekend_exeter","","","","","","plymouth_twilight","","","","","","","","night_weekend","","","","","","","","","","night_weekend","","weekend_torbay","","","","","","","","","","weekend_truro","night_weekend","","","","" +"Week 5 Day Sat","","","","weekend_exeter","","","","","","","","","","","weekend_plymouth1","","","night_weekend","","","","","","weekend_plymouth2","","","","night_weekend","","weekend_torbay","","","","","","","","","","weekend_truro","night_weekend","","","","" +"Week 5 Day Sun","","","","weekend_exeter","","","","","","","","","","","weekend_plymouth1","","","night_weekend","","","","","","weekend_plymouth2","","","","night_weekend","","weekend_torbay","","","","","","","","","","weekend_truro","night_weekend","","","","" +"Week 6 Day Mon","","exeter_twilight","","","night_weekday","","","","","night_weekday","","","","","","","","","plymouth_twilight","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","","","","","truro_twilight","","" +"Week 6 Day Tue","","","exeter_twilight","","night_weekday","","","","","night_weekday","","","","","","","","","","","","","","","plymouth_twilight","","","","torbay_twilight","","night_weekday","","","","","","","truro_twilight","","","","","","","" +"Week 6 Day Wed","","","","","night_weekday","","exeter_twilight","","","night_weekday","plymouth_twilight","","","","","","","","","","","","","","","","","","","","night_weekday","","","","torbay_twilight","","","","","","","","truro_twilight","","" +"Week 6 Day Thu","","","","","night_weekday","","exeter_twilight","","","night_weekday","","","","","","","","","","","","plymouth_twilight","","","","","","","torbay_twilight","","night_weekday","","","","","","","","truro_twilight","","","","","","" +"Week 6 Day Fri","night_weekend","weekend_exeter","","","","","","","night_weekend","","plymouth_twilight","","","","plymouth_bank_holidays","","night_weekend","","","","","","","","","","weekend_torbay","","","","","","","","","","","weekend_truro","","","","","","","" +"Week 6 Day Sat","night_weekend","weekend_exeter","","","","","","","night_weekend","","","","","","","","night_weekend","","","weekend_plymouth1","","","weekend_plymouth2","","","","weekend_torbay","","","","","","","","","","","weekend_truro","","","","","","","" +"Week 6 Day Sun","night_weekend","weekend_exeter","","","","","","","night_weekend","","","","","","","","night_weekend","","","weekend_plymouth1","","","weekend_plymouth2","","","","weekend_torbay","","","","","","","","","","","weekend_truro","","","","","","","" +"Week 7 Day Mon","","","exeter_twilight","","","night_weekday","","","","","night_weekday","","","","","","","","","","","","","plymouth_twilight","","","","","","","","torbay_twilight","","","","","","","","","","night_weekday","truro_twilight","","" +"Week 7 Day Tue","","","","","","night_weekday","","exeter_twilight","","","night_weekday","plymouth_twilight","","","","","","","","","","","","","","","","","","","","torbay_twilight","","","","truro_twilight","","","","","","night_weekday","","","" +"Week 7 Day Wed","","","exeter_twilight","","","night_weekday","","","","","night_weekday","","","","","","","","","","","plymouth_twilight","","","","","","","","","","torbay_twilight","","","","","","","truro_twilight","","","night_weekday","","","" +"Week 7 Day Thu","","exeter_twilight","","","","night_weekday","","","","","night_weekday","","","","","","","","","","","","","plymouth_twilight","","","","","","","","torbay_twilight","","","","truro_twilight","","","","","","night_weekday","","","" +"Week 7 Day Fri","","","","weekend_exeter","","","night_weekend","","","","","","","","","","","night_weekend","","","","","plymouth_twilight","","","","","weekend_torbay","","","","","","","","","","","","night_weekend","","","","weekend_truro","" +"Week 7 Day Sat","","","","weekend_exeter","","","night_weekend","","","","","","","","","","","night_weekend","weekend_plymouth2","","","weekend_plymouth1","","","","","","weekend_torbay","","","","","","","","","","","","night_weekend","","","","weekend_truro","" +"Week 7 Day Sun","","","","weekend_exeter","","","night_weekend","","","","","","","","","","","night_weekend","weekend_plymouth2","","","weekend_plymouth1","","","","","","weekend_torbay","","","","","","","","","","","","night_weekend","","","","weekend_truro","" +"Week 8 Day Mon","","","","","","","","night_weekday","exeter_twilight","","","","","night_weekday","","","","","","","","","","plymouth_twilight","","","","","","","torbay_twilight","","night_weekday","","","","","","","","","truro_twilight","","","" +"Week 8 Day Tue","","","","","","","","night_weekday","exeter_twilight","plymouth_twilight","","","","night_weekday","","","","","","","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","truro_twilight","","","","" +"Week 8 Day Wed","","","","","","exeter_twilight","","night_weekday","","","","","","night_weekday","","","","","","plymouth_twilight","","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","","","","truro_twilight","","","" +"Week 8 Day Thu","","","","","exeter_twilight","","","night_weekday","","","","","","night_weekday","","","","","","","plymouth_twilight","","","","","","","","","","torbay_twilight","","night_weekday","","","","","","truro_twilight","","","","","","" +"Week 8 Day Fri","","","weekend_exeter","","","","","","","night_weekend","","","","","","","","","","","plymouth_twilight","","","","","","","","","night_weekend","","weekend_torbay","","","","","weekend_truro","","","","night_weekend","","","","" +"Week 8 Day Sat","","","weekend_exeter","","","","","","","night_weekend","","","","","weekend_plymouth2","","","","","","","","","weekend_plymouth1","","","","","","night_weekend","","weekend_torbay","","","","","weekend_truro","","","","night_weekend","","","","" +"Week 8 Day Sun","","","weekend_exeter","","","","","","","night_weekend","","","","","weekend_plymouth2","","","","","","","","","weekend_plymouth1","","","","","","night_weekend","","weekend_torbay","","","","","weekend_truro","","","","night_weekend","","","","" +"Week 9 Day Mon","night_weekday","","","","","exeter_twilight","","","","","","plymouth_twilight","","","","","","","","","plymouth_bank_holidays","","night_weekday","","","","","","","","","","torbay_twilight","","","","","truro_twilight","","","","","","night_weekday","" +"Week 9 Day Tue","night_weekday","","","","exeter_twilight","","","","","","plymouth_twilight","","","","","","","","","","","","night_weekday","","","","","","","","","","torbay_twilight","","","truro_twilight","","","","","","","","night_weekday","" +"Week 9 Day Wed","night_weekday","","","exeter_twilight","","","","","","","","","","","","","","","","","","","night_weekday","plymouth_twilight","","","","","","","","","torbay_twilight","","","","","","","","truro_twilight","","","night_weekday","" +"Week 9 Day Thu","night_weekday","","exeter_twilight","","","","","","","","","","","","","plymouth_twilight","","","","","","","night_weekday","","","","","","","","","","torbay_twilight","","","","","","","","truro_twilight","","","night_weekday","" +"Week 9 Day Fri","","weekend_exeter","","","","","","","","","","","","","","","","plymouth_twilight","","","","night_weekend","","","","weekend_torbay","","","night_weekend","","","","","","","","","","night_weekend","","","weekend_truro","","","" +"Week 9 Day Sat","","weekend_exeter","","","","","","","","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","night_weekend","","","","weekend_torbay","","","night_weekend","","","","","","","","","","night_weekend","","","weekend_truro","","","" +"Week 9 Day Sun","","weekend_exeter","","","","","","","","","weekend_plymouth2","","weekend_plymouth1","","","","","","","","","night_weekend","","","","weekend_torbay","","","night_weekend","","","","","","","","","","night_weekend","","","weekend_truro","","","" +"Week 10 Day Mon","","","","","exeter_twilight","","night_weekday","","","","","","","","","night_weekday","","plymouth_twilight","","","","","","","","","","","","torbay_twilight","","","","","","night_weekday","","","","","truro_twilight","","","","" +"Week 10 Day Tue","","","","","exeter_twilight","","night_weekday","","","","","","","","","night_weekday","","","","plymouth_twilight","","","","","","","","","","torbay_twilight","","","","","","night_weekday","","","","","","","truro_twilight","","" +"Week 10 Day Wed","","","","","","","night_weekday","","exeter_twilight","","","","","","","night_weekday","","","","","","","plymouth_twilight","","","torbay_twilight","","","","","","","","","","night_weekday","","","truro_twilight","","","","","","" +"Week 10 Day Thu","","","","","","","night_weekday","exeter_twilight","","","","","","","plymouth_twilight","night_weekday","","","","","","","","","","","","","","","torbay_twilight","","","","","night_weekday","","","","","","","truro_twilight","","" +"Week 10 Day Fri","","","weekend_exeter","","","","","","","","","","","","","","","","","night_weekend","plymouth_twilight","","","","","","night_weekend","","","","","","","weekend_torbay","","","","","","night_weekend","","","","weekend_truro","" +"Week 10 Day Sat","","","weekend_exeter","","","","","","","","","","","weekend_plymouth2","","","","","weekend_plymouth1","night_weekend","","","","","","","night_weekend","","","","","","","weekend_torbay","","","","","","night_weekend","","","","weekend_truro","" +"Week 10 Day Sun","","","weekend_exeter","","","","","","","","","","","weekend_plymouth2","","","","","weekend_plymouth1","night_weekend","","","","","","","night_weekend","","","","","","","weekend_torbay","","","","","","night_weekend","","","","weekend_truro","" +"Week 11 Day Mon","","","","exeter_twilight","","","","night_weekday","","","","","","","night_weekday","","plymouth_twilight","","","","","","","","","","","night_weekday","torbay_twilight","","","","","","","","","","","","truro_twilight","","","","" +"Week 11 Day Tue","","","","exeter_twilight","","","","night_weekday","","","","","plymouth_twilight","","night_weekday","","","","","","","","","","","","","night_weekday","torbay_twilight","","","","","","","","","","","","","truro_twilight","","","" +"Week 11 Day Wed","","","","","exeter_twilight","","","night_weekday","","","","plymouth_twilight","","","night_weekday","","","","","","","","","","","","","night_weekday","torbay_twilight","","","","","","","","truro_twilight","","","","","","","","" +"Week 11 Day Thu","","","","","","","","night_weekday","exeter_twilight","","","plymouth_twilight","","","night_weekday","","","","","","","","","","","","","night_weekday","torbay_twilight","","","","","","","","truro_twilight","","","","","","","","" +"Week 11 Day Fri","","night_weekend","","","weekend_exeter","","","","","","","","","","","","","","","plymouth_twilight","","night_weekend","","","","","","","","","","","weekend_torbay","","","weekend_truro","","","","","","","night_weekend","","" +"Week 11 Day Sat","","night_weekend","","","weekend_exeter","","","","","","weekend_plymouth2","","","","","","","","","","","night_weekend","weekend_plymouth1","","","","","","","","","","weekend_torbay","","","weekend_truro","","","","","","","night_weekend","","" +"Week 11 Day Sun","","night_weekend","","","weekend_exeter","","","","","","weekend_plymouth2","","","","","","","","","","","night_weekend","weekend_plymouth1","","","","","","","","","","weekend_torbay","","","weekend_truro","","","","","","","night_weekend","","" +"Week 12 Day Mon","","","","","","","","exeter_twilight","night_weekday","","","plymouth_twilight","","","","","","","","","","","","night_weekday","","night_weekday","","torbay_twilight","","","","","","","","","","","","","","","","","truro_twilight" +"Week 12 Day Tue","","","","","","","exeter_twilight","","night_weekday","","","","plymouth_twilight","","","","","","","","","","","night_weekday","","night_weekday","","torbay_twilight","","","","","","","","","","","truro_twilight","","","","","","" +"Week 12 Day Wed","","exeter_twilight","","","","","","","night_weekday","","","","","","","","","","","","plymouth_twilight","","","night_weekday","","night_weekday","","torbay_twilight","","","","","","","","","","","","truro_twilight","","","","","" +"Week 12 Day Thu","","","","","","","exeter_twilight","","night_weekday","","","","","","","","","","","","","plymouth_twilight","","night_weekday","","night_weekday","","torbay_twilight","","","","","","","","","","","","","","","","","truro_twilight" +"Week 12 Day Fri","","","","night_weekend","","","","weekend_exeter","","plymouth_twilight","","night_weekend","","","","","","","","","","","","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","" +"Week 12 Day Sat","","","","night_weekend","","","","weekend_exeter","","","","night_weekend","","","","","","weekend_plymouth2","weekend_plymouth1","","","","","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","" +"Week 12 Day Sun","","","","night_weekend","","","","weekend_exeter","","","","night_weekend","","","","","","weekend_plymouth2","weekend_plymouth1","","","","","","","","","","","","night_weekend","weekend_torbay","","","","","","","weekend_truro","","","","","","" +"Week 13 Day Mon","","","","","night_weekday","","exeter_twilight","","","plymouth_twilight","","","","","","","","","","","","","night_weekday","","","","torbay_twilight","","","night_weekday","","","","","","","","truro_twilight","","","","","","","" +"Week 13 Day Tue","","","","","night_weekday","","","","exeter_twilight","","","","","","","","","","","","","","night_weekday","plymouth_twilight","","","torbay_twilight","","","night_weekday","","","","","","","","","","","","","","","truro_twilight" +"Week 13 Day Wed","","","","","night_weekday","","exeter_twilight","","","","","","","","","plymouth_twilight","","","","","","","night_weekday","","","","torbay_twilight","","","night_weekday","","","","","","","","","truro_twilight","","","","","","" +"Week 13 Day Thu","","","","exeter_twilight","night_weekday","","","","","","","","","","","","","plymouth_twilight","","plymouth_bank_holidays","","","night_weekday","","","","torbay_twilight","","","night_weekday","","","","","","","","","","","","","","","truro_twilight" +"Week 13 Day Fri","","","","","","weekend_exeter","","","","","","","","","","","night_weekend","plymouth_twilight","plymouth_bank_holidays","","","","","","","weekend_torbay","","","","","","","night_weekend","","","","night_weekend","","","","","weekend_truro","","","" +"Week 13 Day Sat","","","","","","weekend_exeter","","","","","","","","","","weekend_plymouth2","night_weekend","","","","weekend_plymouth1","","","","","weekend_torbay","","","","","","","night_weekend","","","","night_weekend","","","","","weekend_truro","","","" +"Week 13 Day Sun","","","","","","weekend_exeter","","","","","","","","","","weekend_plymouth2","night_weekend","","","","weekend_plymouth1","","","","","weekend_torbay","","","","","","","night_weekend","","","","night_weekend","","","","","weekend_truro","","","" diff --git a/shifts.py b/shifts.py index 125b0f3..bcf31f4 100644 --- a/shifts.py +++ b/shifts.py @@ -89,12 +89,7 @@ class SingleShift(object): Returns a text summary of the shift """ - return "{}: {} workers for sites ({}) on days ({})".format( - self.name, - self.workers_required, - ", ".join(self.site), - ", ".join(self.shift_days), - ) + return f"{self.name}: {self.workers_required} workers for sites ({', '.join(self.site)}) on days ({', '.join(self.shift_days)})" class RotaBuilder(object): @@ -102,18 +97,21 @@ class RotaBuilder(object): def __init__( self, - start_date, - weeks_to_rota=26, - balance_offset_modifier=1, - ltft_balance_offset=2, - max_night_frequency=2, - max_weekend_frequency=2, - use_previous_shifts=False, + start_date: datetime.datetime, + weeks_to_rota: int = 26, + balance_offset_modifier: int = 1, + ltft_balance_offset: int = 1, + max_night_frequency: int = 2, + max_weekend_frequency: int = 2, + use_previous_shifts: bool = False, + use_shift_balance_extra: bool = False, + use_bank_holiday_extra: bool = False, ): - print("Start time: {}".format(datetime.datetime.now())) - print("Weeks to rota: {}".format(weeks_to_rota)) - print("Use previous shifts {}".format(use_previous_shifts)) + print(f"Start time: {datetime.datetime.now()}") + print(f"Weeks to rota: {weeks_to_rota}") + print(f"Use previous shifts {use_previous_shifts}") + print(f"Use previous shifts (balance_extra) {use_shift_balance_extra}") self.shifts = [] # type List[SingleShift] @@ -124,12 +122,12 @@ class RotaBuilder(object): self.weeks_days_product = list(itertools.product(self.weeks, days)) self.use_previous_shifts = use_previous_shifts + self.use_shift_balance_extra = use_shift_balance_extra + self.use_bank_holiday_extra = use_bank_holiday_extra if start_date.weekday() != 0: raise ValueError( - "Start date {} must be a Mon (not a {})".format( - start_date.isoformat(), days[start_date.weekday()] - ) + f"Start date {start_date.isoformat()} must be a Mon (not a {days[start_date.weekday()]})" ) self.start_date = start_date @@ -166,6 +164,7 @@ class RotaBuilder(object): "balance_bank_holidays": True, "balance_blocks": True, "balance_shifts": True, + "minimise_shift_diffs": True, "balance_weekends": True, "max_weekends": False, "max_shifts_per_week": 4, @@ -353,6 +352,22 @@ class RotaBuilder(object): initialize=0, ) + self.model.shift_count_diff = Var( + ( + (worker.id, shift) + for worker in self.workers + for shift in self.get_shift_names() + ), + within=Reals, + initialize=0, + ) + + self.model.shift_count_diff_summed = Var( + (worker.id for worker in self.workers), + within=Reals, + initialize=0, + ) + if self.constraint_options["balance_shifts"]: self.model.shift_count_t1 = Var( @@ -799,6 +814,15 @@ class RotaBuilder(object): # Most of our constraints apply per worker for worker in self.workers: + if self.constraint_options["minimise_shift_diffs"]: + self.model.constraints.add( + self.model.shift_count_diff_summed[worker.id] + == sum( + self.model.shift_count_diff[worker.id, shift.name] + for shift in self.get_shifts() + ) + ) + for week_blocks in self.get_week_block_iterator(4): # Prevent weekend shifts more than once every n weeks self.model.constraints.add( @@ -868,6 +892,11 @@ class RotaBuilder(object): target_shifts + float(allocated) - float(worked) ) + if self.use_shift_balance_extra: + if shift.name in worker.shift_balance_extra: + extra = worker.shift_balance_extra[shift.name] + target_shifts = target_shifts + extra + # print(worker.name, shift.name, target_shifts) worker.shift_target_number[shift.name] = target_shifts @@ -917,6 +946,12 @@ class RotaBuilder(object): ) ) + self.model.constraints.add( + self.model.shift_count_diff[worker.id, shift.name] + == self.model.shift_count[worker.id, shift.name] + - worker.shift_target_number[shift.name] + ) + # Define shift_count_t1 and shift_count_t2 constraints for the object # Thus bypassing the need for a quadratic solver # t1-t2 is the target @@ -928,8 +963,7 @@ class RotaBuilder(object): - self.model.shift_count_t2[worker.id] == sum( ( - self.model.shift_count[worker.id, shift.name] - - worker.shift_target_number[shift.name] + self.model.shift_count_diff[worker.id, shift.name] ) * shift.balance_weighting for shift in self.get_shifts() @@ -938,6 +972,12 @@ class RotaBuilder(object): # if "night" not in shift.constraints)) if self.constraint_options["balance_bank_holidays"]: + + extra_bank_holiday = 0 + if self.use_bank_holiday_extra: + print(worker.name, worker.bank_holiday_extra) + extra_bank_holiday = worker.bank_holiday_extra + self.model.constraints.add( self.model.bank_holiday_count[worker.id] == sum( @@ -945,6 +985,7 @@ class RotaBuilder(object): for week, day, shift in self.get_all_shiftname_combinations() if self.week_day_date_map[(week, day)] in bank_holiday_map ) + + extra_bank_holiday ) xU = len(self.get_bank_holiday_week_days()) + 1 @@ -1501,6 +1542,16 @@ class RotaBuilder(object): else: shift_balancing = 0 + if self.constraint_options["minimise_shift_diffs"]: + shift_diff_modifier_constant = 10 + shift_diff_balancing = sum( + shift_diff_modifier_constant + * self.model.shift_count_diff_summed[(worker.id)] + for worker in self.workers + ) + else: + shift_diff_balancing = 0 + if self.constraint_options["balance_nights"]: night_balance_modifier_constant = 10 night_shift_balancing = sum( @@ -1598,6 +1649,7 @@ class RotaBuilder(object): weekend_shift_balancing + shift_balancing + night_shift_balancing + + shift_diff_balancing + preferences + nights_site_balancing + bank_holiday_balancing @@ -1882,11 +1934,9 @@ class RotaBuilder(object): l = [] for site in w: - l.append( - "{}: {}".format(site, sum(worker.fte_adj for worker in w[site]) / 100) - ) + l.append(f"{site}: {sum(worker.fte_adj for worker in w[site]) / 100}") t = "\n".join(l) - return "Full time equivalent trainees by site:\n{}".format(t) + return f"Full time equivalent trainees by site:\n{t}" def get_bank_holiday_week_days(self): return [ @@ -1902,12 +1952,12 @@ class RotaResults(object): # self.results = results def export_rota_to_html(self, filename="rota"): - with open("{}.html".format(filename), "w") as f: + with open(f"{filename}.html", "w") as f: f.write(self.get_worker_timetable_html(True)) def export_rota_to_csv(self, filename="rota"): works = self.rota.model.works - with open("{}.csv".format(filename), "w", newline="") as f: + with open(f"{filename}.csv", "w", newline="") as f: wr = csv.writer(f, quoting=csv.QUOTE_ALL) l = ["Name"] l.extend([worker.name for worker in self.rota.workers]) @@ -1926,7 +1976,7 @@ class RotaResults(object): wr.writerow(l4) for week, day in self.rota.get_week_day_combinations(): - d = ["Week {} Day {}".format(week, day)] + d = [f"Week {week} Day {day}"] for worker in self.rota.workers: i = "" @@ -1971,16 +2021,14 @@ class RotaResults(object): self, show_prefs=False, marker_every=30, show_unavailable=False ): model = self.rota.model - week_string = "{:20}".format("-Week-") + "".join( - [7 * str("{}".format(w))[-1:] for w in self.rota.weeks] - ) - days_string = "{:20}".format("-Day-") + "".join( - "MTWTFSS" * len(self.rota.weeks) + week_string = f"{'-Week-':20}" + "".join( + [7 * str(f"{w}")[-1:] for w in self.rota.weeks] ) + days_string = f"{'-Day-':20}" + "".join("MTWTFSS" * len(self.rota.weeks)) timetable = [] for worker in self.rota.workers: shifts = [] - w = ["{:20}".format(worker.name)] + w = [f"{worker.name:20}"] for week, day in self.rota.get_week_day_combinations(): a = "-" for shift in self.rota.get_shift_names_by_week_day(week, day): @@ -1991,16 +2039,17 @@ class RotaResults(object): shift_count = "" for s in set(shifts): - shift_count = shift_count + "{}: {}, ".format(s, shifts.count(s)) + shift_count = shift_count + f"{s}: {shifts.count(s)}, " - shift_count = shift_count + "#weekends_worked: {}\\#".format( - model.worker_weekend_count[worker.id].value + shift_count = ( + shift_count + + f"#weekends_worked: {model.worker_weekend_count[worker.id].value}\\#" ) - timetable.append("{} {}".format("".join(w), shift_count)) + timetable.append(f"{''.join(w)} {shift_count}") if show_prefs: # prefs - w = ["{:20}".format("Preferences")] + w = [f"{'Preferences':20}"] for week, day in self.rota.get_week_day_combinations(): if model.pref_not_to_work[worker.id, week, day] > 0: w.append("Y") @@ -2010,7 +2059,7 @@ class RotaResults(object): if show_unavailable: # prefs - w = ["{:20}".format("Unavailable")] + w = [f"{'Unavailable':20}"] for week, day in self.rota.get_week_day_combinations(): if model.available[worker.id, week, day] > 0: w.append("A") @@ -2037,9 +2086,7 @@ class RotaResults(object): timetable = [] timetable.append( - "

Rota start date: {} ({} weeks)

".format( - self.rota.start_date.isoformat(), self.rota.weeks[-1] - ) + f"

Rota start date: {self.rota.start_date.isoformat()} ({self.rota.weeks[-1]} weeks)

" ) date_row = [""] @@ -2047,9 +2094,9 @@ class RotaResults(object): n = 0 for week, day in self.rota.get_week_day_combinations(): d = self.rota.start_date + datetime.timedelta(n) - date_row.append("Week {}: {}".format(d, week, day)) + date_row.append(f"Week {week}: {day}") n = n + 1 - timetable.append("{}".format("".join(date_row))) + timetable.append(f"{''.join(date_row)}") current_site = "" @@ -2057,11 +2104,7 @@ class RotaResults(object): if worker.site != current_site: try: timetable.append( - "{} n={} fte={}".format( - worker.site, - len(self.rota.workers_at_sites[worker.site]), - self.rota.full_time_equivalent_sites[worker.site], - ) + f"{worker.site} n={len(self.rota.workers_at_sites[worker.site])} fte={self.rota.full_time_equivalent_sites[worker.site]}" ) except KeyError as e: print(e) @@ -2095,7 +2138,7 @@ class RotaResults(object): shift_name = shift break # If we haven't run just add the shift - title = "{} ({})".format(shift_name, d) + title = f"{shift_name} ({d})" css_class = day unavailable_reason = "" if model.available[worker.id, week, day] > 0: @@ -2110,37 +2153,23 @@ class RotaResults(object): except KeyError: print("Error getting reason") print(worker.id, worker.name) - print("Week {}, Day {}".format(week, day)) + print(f"Week {week}, Day {day}") unavailable_reason = "????" - title = "{} / {}".format(title, unavailable_reason) + title = f"{title} / {unavailable_reason}" 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]) + bank_holiday = f" data-bank-holiday='{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)] - ) + requests = f" data-shift-request='{self.rota.work_requests_map[worker.id, week, day]}'" shift_tds.append( - "{}".format( - title, - css_class, - shift_name, - available, - unavailable_reason, - d, - week, - day, - requests, - bank_holiday, - a, - ) + f"{a}" ) shift_count = "" @@ -2148,9 +2177,28 @@ class RotaResults(object): for s in set(shifts): c = shifts.count(s) shift_count_dict[s] = c - shift_count = shift_count + "{}: {}, ".format(s, c) + shift_count = shift_count + f"{s}: {c}, " - worker_td = "{name} ({grade}) [{fte}]".format( + + shift_diff_dict = {} + for shift in self.rota.get_shifts(): + diff = model.shift_count_diff[worker.id, shift.name].value + shift_diff_dict[shift.name] = diff + + + worker_td = """ + {name} ({grade}) [{fte}]""".format( site=worker.site, nwds=nwds, name=worker.name, @@ -2163,17 +2211,22 @@ class RotaResults(object): nights_at_derriford=worker.night_at_derriford, grade=worker.grade, pair=worker.pair, + shift_balance_extra=json.dumps(worker.shift_balance_extra), + shift_diff=json.dumps(shift_diff_dict), + shift_diff_summed=model.shift_count_diff_summed[worker.id].value, + bank_holiday_extra=worker.bank_holiday_extra, ) - shift_count = shift_count + "#weekends_worked: {}\\#".format( - model.worker_weekend_count[worker.id].value + shift_count = ( + shift_count + + f"#weekends_worked: {model.worker_weekend_count[worker.id].value}\\#" ) bank_holiday_count = model.bank_holiday_count[worker.id].value bank_holiday_count_w = model.bank_holiday_count_w[worker.id].value - 1 # print(worker.name, bank_holiday_count, bank_holiday_count_w) timetable.append( - "{}{}".format(worker_td, "".join(shift_tds)) + f"{worker_td}{''.join(shift_tds)}" ) # timetable.append("{}".format("".join(w), shift_count)) @@ -2237,15 +2290,13 @@ class RotaResults(object): """ if include_html_tag: - html = """ + html = f""" - {}""".format( - html - ) + {html}""" return html @@ -2271,15 +2322,11 @@ class RotaResults(object): for day in days ].count(1) if c > 0: - l.append("{} ({})".format(shift, c)) + l.append(f"{shift} ({c})") total_shifts = total_shifts + c # print(worker.id, shift) timetable[worker.get_details()][shift[0]] = c - t.append( - "{} [{}]: {}".format( - worker.get_full_details(), total_shifts, ", ".join(l) - ) - ) + t.append(f"{worker.get_full_details()} [{total_shifts}]: {', '.join(l)}") return "\n".join(t) def get_shift_summary_html(self): @@ -2304,15 +2351,11 @@ class RotaResults(object): for day in days ].count(1) if c > 0: - l.append("{} ({})".format(shift, c)) + l.append(f"{shift} ({c})") total_shifts = total_shifts + c # print(worker.id, shift) timetable[worker.get_details()][shift[0]] = c - t.append( - "{} [{}]: {}".format( - worker.get_full_details(), total_shifts, ", ".join(l) - ) - ) + t.append(f"{worker.get_full_details()} [{total_shifts}]: {', '.join(l)}") return "\n".join(t) # def get_no_preference(no_pref): diff --git a/test.py b/test.py index 4358931..776d868 100644 --- a/test.py +++ b/test.py @@ -13,21 +13,22 @@ import os use_neos = False use_cplex = False -solve = False +solve = True weeks_to_rota = 26 -time_to_run = 158500 +time_to_run = 258500 # allow = 5000 -ratio = 0.05 +ratio = 0.01 use_live_rota = False -suspend_on_finish = False +suspend_on_finish = True sites = ("truro", "exeter", "torbay", "barnstaple", "plymouth", "proc", "truro no proc") Rota = RotaBuilder( + # datetime.date(2022, 6, 6), datetime.date(2022, 3, 7), # Rota = RotaBuilder(datetime.date(2021, 12, 6), # Rota = RotaBuilder(datetime.date(2021, 11, 8), @@ -35,6 +36,8 @@ Rota = RotaBuilder( balance_offset_modifier=1, max_weekend_frequency=2, max_night_frequency=2, + use_shift_balance_extra=True, + use_bank_holiday_extra=True, ) Rota.constraint_options["limit_to_1_st2_on_nights"] = True @@ -44,19 +47,27 @@ Rota.constraint_options["constrain_time_off_after_nights"] = True Rota.constraint_options["balance_nights_across_sites"] = True Rota.constraint_options["balance_blocks"] = True Rota.constraint_options["balance_weekends"] = True +Rota.constraint_options["balance_shifts"] = True Rota.constraint_options["balance_bank_holidays"] = True -Rota.constraint_options["avoid_st2_first_month"] = True +Rota.constraint_options["avoid_st2_first_month"] = False Rota.constraint_options["hard_constrain_pair_separation"] = True -Rota.constraint_options["max_weekends"] = 20 +Rota.constraint_options["max_weekends"] = 7 Rota.constraint_options["prevent_monday_and_tuesday_after_full_weekends"] = [ "truro", "torbay", "exeter", "plymouth", + "plymouth no proc", "truro no proc", ] -Rota.constraint_options["prevent_monday_after_full_weekends"] = ["plymouth"] -Rota.constraint_options["prevent_fridays_before_full_weekends"] = ["plymouth"] +Rota.constraint_options["prevent_monday_after_full_weekends"] = [ + "plymouth", + "plymouth no proc", +] +Rota.constraint_options["prevent_fridays_before_full_weekends"] = [ + "plymouth", + "plymouth no proc", +] Rota.constraint_options["prevent_thursdays_before_full_weekends"] = [ "truro", "torbay", @@ -70,11 +81,12 @@ Rota.add_shifts( "exeter_twilight", 12.5, days[:4], + balance_offset=1.5, constraints=["max_2_shifts_per_week"], ), SingleShift(("truro", "truro no proc"), "truro_twilight", 12.5, days[:4]), SingleShift( - ("torbay",), + ("torbay", "torbay twilights"), "torbay_twilight", 12.5, days[:4], @@ -86,7 +98,7 @@ Rota.add_shifts( "plymouth_twilight", 12.5, days[:5], - balance_offset=3, + balance_offset=1.5, workers_required=1, constraints=["max_2_shifts_per_week"], ), @@ -95,7 +107,7 @@ Rota.add_shifts( "weekend_exeter", 12.5, days[4:], - balance_offset=3, + balance_offset=3.5, rota_on_nwds=True, force_as_block=True, ), @@ -104,7 +116,7 @@ Rota.add_shifts( "weekend_truro", 12.5, days[4:], - balance_offset=3, + balance_offset=3.5, rota_on_nwds=True, force_as_block=True, # force_as_block_unless_nwd=True @@ -114,7 +126,7 @@ Rota.add_shifts( "weekend_torbay", 12.5, days[4:], - balance_offset=3, + balance_offset=3.5, rota_on_nwds=True, force_as_block=True, # force_as_block_unless_nwd=True @@ -124,7 +136,7 @@ Rota.add_shifts( "weekend_plymouth1", 8, days[5:], - balance_offset=3, + balance_offset=2.5, workers_required=1, rota_on_nwds=True, force_as_block=True, @@ -134,7 +146,7 @@ Rota.add_shifts( "weekend_plymouth2", 8, days[5:], - balance_offset=3, + balance_offset=2.5, workers_required=1, rota_on_nwds=True, force_as_block=True, @@ -144,7 +156,7 @@ Rota.add_shifts( "plymouth_bank_holidays", 8, days[5:], - balance_offset=3, + balance_offset=1.5, workers_required=1, rota_on_nwds=True, force_as_block=False, @@ -155,7 +167,7 @@ Rota.add_shifts( "night_weekday", 12.25, days[:4], - balance_offset=4, + balance_offset=5, balance_weighting=1, # hard_constrain_shift=False, workers_required=3, @@ -168,7 +180,7 @@ Rota.add_shifts( "night_weekend", 12.25, days[4:], - balance_offset=3, + balance_offset=4, balance_weighting=1, # hard_constrain_shift=False, workers_required=3, @@ -203,10 +215,10 @@ if use_test_workers: [ Worker( Rota, - i, "Truro {}".format(i), "truro", 4, + id=i, ) for i in range(1, 9) ] @@ -298,10 +310,10 @@ else: Rota.add_worker( Worker( Rota, - n, worker_name, site.lower(), int(grade[2]), + n, int(fte), nwds, end_date, @@ -311,6 +323,8 @@ else: proc_site=site_pref, previous_shifts=previous_shifts, pair=pair, + shift_balance_extra=w["shift_balance_extra"], + bank_holiday_extra=w["bank_holiday_extra"], ) ) @@ -329,11 +343,7 @@ if not solve: ResultsHolder.export_rota_to_html() sys.exit(0) -solver_options = { - "ratio" : ratio, - "seconds" : time_to_run, - "threads" : 10 -} +solver_options = {"ratio": ratio, "seconds": time_to_run, "threads": 10} Rota.solve_model(options=solver_options) diff --git a/workers.py b/workers.py index b99da94..0759cbc 100644 --- a/workers.py +++ b/workers.py @@ -2,28 +2,36 @@ import datetime from collections import defaultdict # from shifts import RotaBuilder, days, sites +import uuid class Worker: def __init__( self, Rota, - id, name, site, - grade, - fte=100, + grade: int, + id=None, + fte: int = 100, nwd=None, end_date=None, oop=None, not_available_to_work=None, pref_not_to_work=None, work_requests=None, - proc_site="plymouth", # We set a default proc_site - previous_shifts={}, - pair=0, + proc_site: str = "plymouth", # We set a default proc_site + previous_shifts: dict = {}, + shift_balance_extra: dict = {}, + bank_holiday_extra: int = 0, + pair: int = 0, ): - self.id = id + + # We can either have a user generated ID + if id is not None: + self.id = id + else: + self.id = uuid.uuid4() self.name = name self.site = site # Grade are equivalent to roles, by keeping them integer defining model @@ -33,6 +41,8 @@ class Worker: self.nwd = nwd self.proportion_rota_to_work = 1 self.pair = pair + self.shift_balance_extra = shift_balance_extra + self.bank_holiday_extra = bank_holiday_extra self.proc_site = proc_site if proc_site == "plymouth" or proc_site == "":