From 81cbe947e678e448a90290cd05147f4aefcbb02b Mon Sep 17 00:00:00 2001 From: ross Date: Tue, 31 Mar 2020 12:57:09 +0100 Subject: [PATCH] Further improvements --- .gitignore | 3 + rota.ipynb | 1059 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 772 insertions(+), 290 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9beb2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +rota.csv +trainees.csv +sample.txt diff --git a/rota.ipynb b/rota.ipynb index 3ea2309..75925d0 100644 --- a/rota.ipynb +++ b/rota.ipynb @@ -25,7 +25,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 112, "metadata": {}, "outputs": [], "source": [ @@ -38,86 +38,189 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 113, "metadata": {}, "outputs": [], "source": [ "night_balance_offset = 2\n", - "twighlight_balance_offset = 1\n", - "weeks_to_rota = 26" + "twilight_balance_offset = 1\n", + "weeks_to_rota = 26\n", + "use_test_workers = False" ] }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Import trainee data\n", - "#import csv\n", - "#with open('trainees.csv', newline='') as f:\n", - "# lines = csv.reader(f, delimiter=',', quotechar='|')\n", - "# for row in lines[1:]:\n", - "# name, site, grade, fte, end_date, nwds = line.split(\",\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, + "execution_count": 114, "metadata": {}, "outputs": [], "source": [ "# Define days (1 week)\n", "days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n", "\n", - "weeks = [i for i in range(1,weeks_to_rota+1)]\n", + "#weeks = [i for i in range(1,weeks_to_rota+1)]\n", "\n", - "weeks_days_product = list(itertools.product(weeks, days))\n", + "#weeks_days_product = list(itertools.product(weeks, days))\n", "\n", - "sites = (\"truro\", \"exeter\", \"torbay\", \"barnstaple\", \"plymouth\")\n", + "sites = (\"truro\", \"exeter\", \"torquay\", \"barnstaple\", \"plymouth\")\n", "\n", "class shiftsClass:\n", - " def __init__(self, sites, shift, length, days, balance_by_site=True)\n", + " def __init__(self, sites, name, length, shift_days, balance_by_site=True, workers_required=1, rota_on_nwds=False):\n", " self.site = sites\n", - " self.shift = shift\n", + " self.name = name\n", " self.length = length\n", - " self.days = days\n", + " self.shift_days = shift_days\n", + " self.balance_by_site = balance_by_site\n", + " self.workers_required = workers_required\n", + " self.rota_on_nwds = rota_on_nwds\n", "\n", - " def GetShift(self):\n", - " return self.shift\n", "\n", - "shifts = []\n", - "shifts.append(\n", - " shiftsClass((\"truro\"), \"truro_twighlight\", 12.5, days[:5]),\n", - " shiftsClass((\"plymouth\"), \"plymouth_twighlight\", 12.5, days[:5]), \n", - " shiftsClass((sites), \"night\", 12.5, days, balance_by_site=False), \n", + "class shiftCollection:\n", + " def __init__(self):\n", + " self.shifts = []\n", + "\n", + " self.weeks = [i for i in range(1,weeks_to_rota+1)]\n", + "\n", + " self.weeks_days_product = list(itertools.product(self.weeks, days))\n", + "\n", + " self.start_date = datetime.date(2020,9,7)\n", + " self.rota_days_length = len(self.weeks) * 7\n", + " self.rota_end_date = self.start_date + datetime.timedelta(self.rota_days_length)\n", + "\n", + " def AddShift(self, shift):\n", + " self.shifts.append(shift)\n", + " self.BuildShifts()\n", + "\n", + " def AddShifts(self, *shifts):\n", + " self.shifts.extend(shifts)\n", + " self.BuildShifts()\n", + " \n", + " def GetShiftNamesByDay(self, day):\n", + " return self.day_shifts_dict[day]\n", + "\n", + " def GetShiftByName(self, name):\n", + " return self.shifts_by_name[name]\n", + "\n", + " def GetShiftLengthByName(self, name):\n", + " return self.shifts_by_name[name].length\n", + "\n", + " def BuildShifts(self):\n", + " self.shifts_by_name = {}\n", + " self.shift_names = []\n", + "\n", + " self.day_shifts_dict = { day: set() for day in days }\n", + "\n", + " for s in self.shifts:\n", + " self.shifts_by_name[s.name] = s\n", + " self.shift_names.append(s.name)\n", + "\n", + " for day in s.shift_days:\n", + " self.day_shifts_dict[day].add(s.name)\n", + "\n", + " self.week_day_shift_product = []\n", + " self.week_day_shiftclass_product = []\n", + " for week, day in self.weeks_days_product:\n", + " for s in self.shifts:\n", + " if day in s.shift_days:\n", + " self.week_day_shift_product.append((week, day, s.name))\n", + " self.week_day_shiftclass_product.append((week, day, s))\n", + "\n", + " self.day_shift_product = []\n", + " self.day_shiftclass_product = []\n", + " for s in self.shifts:\n", + " for day in days:\n", + " if day in s.shift_days:\n", + " self.day_shift_product.append((day, s.name))\n", + " self.day_shiftclass_product.append((day, s))\n", + " \n", + " def GetAllDayShiftsAsNames(self):\n", + " return self.day_shift_product\n", + " \n", + " def GetAllDayShiftsAsClass(self):\n", + " return self.day_shiftclass_product\n", + "\n", + " def GetAllShiftsAsNames(self):\n", + " return self.week_day_shift_product\n", + "\n", + " def GetAllShiftsAsClass(self):\n", + " return self.week_day_shiftclass_product\n", + "\n", + " def GetAllWeeksDays(self):\n", + " return self.weeks_days_product\n", + "\n", + " def GetShiftOptions(self):\n", + " return self.shifts\n", + "\n", + " def GetShiftNames(self):\n", + " return self.shift_names\n", + "\n", + " def GetWeeksInBlocks(self, block_length):\n", + " blocks = []\n", + " for i in range(len(self.weeks)):\n", + " blocks.append(self.weeks[i:i+block_length])\n", + " return blocks\n", + "\n", + " def GetRequiredShiftsWorkersAndSites(self):\n", + " l = []\n", + " for week, day, shift in self.week_day_shiftclass_product:\n", + " if day in shift.shift_days:\n", + " l.append((week, day, shift.name, shift.workers_required, shift.site))\n", + "\n", + " return l\n", + "\n", + " def GetNotRequiredShifts(self):\n", + " l = set()\n", + "\n", + " for week in self.weeks:\n", + " for day in days:\n", + " for shift in self.shifts:\n", + " l.add((week, day, shift))\n", + "\n", + " return l - set(self.GetAllShiftsAsClass())\n", + "\n", + "Shifts = shiftCollection()\n", + "\n", + "Shifts.AddShifts(\n", + " shiftsClass((\"exeter\",), \"exeter_twilight\", 12.5, days[:5]),\n", + " shiftsClass((\"truro\",), \"truro_twilight\", 12.5, days[:5]),\n", + " shiftsClass((\"torquay\",), \"torquay_twilight\", 12.5, days[:5]),\n", + " shiftsClass((\"plymouth\",), \"plymouth_twilight\", 12.5, days[:5]), \n", + " shiftsClass((\"exeter\",), \"weekend_exeter\", 12.5, days[5:]), \n", + " shiftsClass((\"truro\",), \"weekend_truro\", 12.5, days[5:]), \n", + " shiftsClass((\"torquay\",), \"weekend_torquay\", 12.5, days[5:]), \n", + " shiftsClass((\"plymouth\",), \"weekend_plymouth\", 8, days[5:], workers_required=2), \n", + " shiftsClass((sites), \"night\", 12.5, days, balance_by_site=False, workers_required=3, rota_on_nwds=True), \n", ")\n", "\n", "# Enter shifts of each day\n", - "#shifts = ['morning', 'evening', 'night', 'truro_twighlight'] # 3 shifts of 8 hours\n", - "#shifts = ['night', 'truro_twighlight', 'plymouth_twighlight'] # 3 shifts of 8 hours\n", + "#shifts = ['morning', 'evening', 'night', 'truro_twilight'] # 3 shifts of 8 hours\n", + "#shifts = ['night', 'truro_twilight', 'plymouth_twilight'] # 3 shifts of 8 hours\n", "\n", "# Generate\n", "\n", - "#site_and_shifts_to_balance = ((\"truro\", \"truro_twighlight\"),\n", - "# (\"plymouth\", \"plymouth_twighlight\"))\n", + "#site_and_shifts_to_balance = ((\"truro\", \"truro_twilight\"),\n", + "# (\"plymouth\", \"plymouth_twilight\"))\n", "\n", "#shift_len = [12.5, 12.5, 12.5]\n", "\n", "night_blocks = [\"weekday\", \"weekend\", \"none\"]\n", "\n", - "shift_lengths = dict(zip(shifts, shift_len))\n", + "#shift_lengths = {}\n", + "#for shift in shifts:\n", + "# shift_\n", + "#days_shifts\n", + "#lengths[shift.shift] = shift.length\n", "\n", - "days_shifts = {day: shifts for day in days} # dict with day as key and list of its shifts as value\n", "\n", - "start_date = datetime.date(2020,9,7)\n", - "rota_days_length = len(weeks) * 7\n", - "rota_end_date = start_date + datetime.timedelta(rota_days_length)\n", + "#days_shifts = {day: shifts.shifts for day in days} # dict with day as key and list of its shifts as value\n", + "\n", + "#start_date = datetime.date(2020,9,7)\n", + "#rota_days_length = len(weeks) * 7\n", + "#rota_end_date = start_date + datetime.timedelta(rota_days_length)\n", "\n", "unavailable_to_work = set()\n", "\n", "class workerClass:\n", - " def __init__(self, id, name, site, grade, fte=100, nwd=\"\", end_date=None, oop=None):\n", + " def __init__(self, id, name, site, grade, fte=100, nwd=[], end_date=None, oop=None):\n", " self.id = id\n", " self.name = name\n", " self.site = site\n", @@ -126,21 +229,21 @@ " self.nwd = nwd\n", " self.proportion_rota_to_work = 1\n", "\n", - " days_to_work = rota_days_length\n", + " days_to_work = Shifts.rota_days_length\n", "\n", " if end_date is None:\n", " self.end_date = None\n", " else:\n", - " y, m, d = [int(i) for i in end_date.split(\"/\")]\n", - " self.end_date = datetime.date(y, m, d)\n", + " #y, m, d = [int(i) for i in end_date.split(\"/\")]\n", + " self.end_date = datetime.datetime.strptime(end_date, '%Y/%m/%d').date()\n", "\n", - " if self.end_date > rota_end_date:\n", - " self.end_date = rota_end_date\n", + " if self.end_date > Shifts.rota_end_date:\n", + " self.end_date = Shifts.rota_end_date\n", " else:\n", - " days_to_work = (self.end_date - start_date).days\n", + " days_to_work = (self.end_date - Shifts.start_date).days\n", "\n", " # add unavalabilities\n", - " for weeks_days in weeks_days_product[days_to_work:]:\n", + " for weeks_days in Shifts.weeks_days_product[days_to_work:]:\n", " week, day = weeks_days\n", " unavailable_to_work.add((self.id, week, day))\n", " \n", @@ -149,40 +252,34 @@ " start_oop_date = datetime.datetime.strptime(start_oop, '%Y/%m/%d').date()\n", " end_oop_date = datetime.datetime.strptime(end_oop, '%Y/%m/%d').date()\n", "\n", - " if end_oop_date > rota_end_date:\n", - " end_oop_date = rota_end_date\n", + " if end_oop_date > Shifts.rota_end_date:\n", + " end_oop_date = Shifts.rota_end_date\n", " \n", " oop_length = (end_oop_date - start_oop_date).days\n", " days_to_work = days_to_work - oop_length\n", "\n", - " days_until_oop = (start_oop_date - start_date).days\n", + " days_until_oop = (start_oop_date - Shifts.start_date).days\n", "\n", - " for weeks_days in weeks_days_product[days_until_oop:days_until_oop+oop_length]:\n", + " for weeks_days in Shifts.weeks_days_product[days_until_oop:days_until_oop+oop_length]:\n", " week, day = weeks_days\n", " unavailable_to_work.add((self.id, week, day))\n", "\n", " \n", - " self.proportion_rota_to_work = days_to_work / rota_days_length\n", + " self.proportion_rota_to_work = days_to_work / Shifts.rota_days_length\n", "\n", " # We had to adjust the full time equivalent for people who CCT / leave the rota early\n", " self.fte_adj = self.fte * self.proportion_rota_to_work\n", + "\n", + " def __lt__(self, other):\n", + " return (self.site, self.grade, self.name) < (other.site, other.grade, other.name)\n", " \n", " def GetDetails(self):\n", " return \"{} {} {}\".format(self.id, self.site[0], self.grade)\n", "\n", - "# Enter workers ids (name, number, ...)\n", - "workers = [workerClass(i, \"Test Person {}\".format(i), \"truro\", 4) for i in range(1, 20)] \n", - "workers.extend([workerClass(i, \"Test Person {}\".format(i), \"plymouth\", 4) for i in range(21, 58)])\n", - "#workers.extend([workerClass(i, \"Test Person {}\".format(i), 4, 200, \"\") for i in range(48, 49)])\n", "\n", "\n", "#unavailable_to_work.update([(\"W1\", 1, \"Mon\"),(\"W1\", 1, \"Fri\"),(\"W1\", 1, \"Thu\"),(\"W3\", 1, \"Mon\"),(\"W2\", 1, \"Mon\"),(\"W2\", 1, \"Tue\"),(\"W2\", 1, \"Wed\"),(\"W6\", 1, \"Thu\"),(\"W5\", 1, \"Fri\"),(\"W9\", 1, \"Fri\"),(\"W14\", 1, \"Fri\"),(\"W21\", 1, \"Fri\")])\n", "\n", - "full_time_equivalent = sum([w.fte_adj for w in workers])\n", - "full_time_equivalent_sites = {}\n", - "\n", - "for site in [\"truro\", \"plymouth\"]:\n", - " full_time_equivalent_sites[site] = sum([w.fte_adj for w in workers if w.site == site])\n", "\n", "\n", "#for i in range(11,30):" @@ -190,28 +287,204 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 115, "metadata": {}, "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "['Wijhdan Abusrewil', 'Exeter', 'ST2', '100', '', '', '', '']\n['Seren Peters', 'Plymouth', 'ST2', '100', '', '', '', '']\n['Hannah Lewis', 'Plymouth', 'ST2', '100', '', '', '', '']\n['Christian Greer', 'Torquay', 'ST2', '100', '', '', '', '']\n['Nicolas Dziad', 'Truro', 'ST2', '100', '', '', '', '']\n['Michal Tomek', 'Exeter', 'ST2', '100', '', '', '', '']\n['Alex Wood', 'Exeter', 'ST2', '100', '', '', '', '']\n['Alexander Sanchez- Cabello', 'Plymouth', 'ST2', '100', '', '', '', '']\n['Max Ireland', 'Torquay', 'ST2', '100', '', '', '', '']\n['Vilim Kalamar', 'Truro', 'ST2', '100', '', '', '', '']\n['Salma Aslam', 'Exeter', 'ST3', '0', '', '', '', 'IDT']\n['Adeola Omotade', 'Plymouth', 'ST3', '0', '', '', '', 'IDT']\n['Amina Odeh', 'Plymouth', 'ST3', '100', '', '', '', '']\n['Charles Finan', 'Torquay', 'ST3', '100', '', '', '', '']\n['Alan Eccles', 'Torquay', 'ST3', '80', '', '', '', '']\n['Sarath Vennam', 'Truro', 'ST3', '100', '', '', '', '']\n['Paul Ward', 'Truro', 'ST3', '100', '', '', '', '']\n['Sayed Alqarooni', 'Exeter', 'ST3', '100', '', '', '', '']\n['Jean Sukumar', 'Plymouth', 'ST3', '60', '', '', '', '']\n['Jenn Fong', 'Plymouth', 'ST3', '100', '', '', '', '']\n['Chong Ng', 'Torquay', 'ST3', '100', '', '', '', '']\n['Matthew Thorley', 'Torquay', 'ST3', '100', '', '', '', '']\n['Jenna Millington', 'Truro', 'ST3', '60', '', '', '', '']\n['Chi Leung', 'Truro', 'ST3', '100', '', '', '', '']\n['Tom Welsh', 'Exeter', 'ST4', '100', '', '', '', '']\n['Rebecca Murphy', 'Exeter', 'ST4', '100', '', '', '', '']\n['Delilah Trimmer', 'Exeter', 'ST4', '50', '', '', '', 'Back Nov 20']\n['Sophie McGlade', 'Plymouth', 'ST4', '100', '', '', '', '']\n['S Ghauri', 'Plymouth', 'ST4', '100', '', '', '', '']\n['Khalil Madbak', 'Plymouth', 'ST4', '100', '', '', '', '']\n['Natasha Hougham', 'Plymouth', 'ST4', '60', '', '', '', '']\n['Richard Chaytor', 'Plymouth', 'ST4', '100', '', '', '', '']\n['Safi Rahman', 'Plymouth', 'ST4', '0', '', '', '', 'IDT']\n['George Edwards', 'Plymouth', 'ST4', '100', '', '', '', '']\n['Neil McIntyre', 'Torquay', 'ST4', '100', '', '', '', '']\n['Mo Babsail', 'Torquay', 'ST4', '100', '', '', '', '']\n['Paul Jenkins', 'Torquay', 'ST4', '100', '', '', '', '']\n['Ross Kruger', 'Truro', 'ST4', '100', '', '', '', '']\n['Amoolya Mannava', 'Truro', 'ST4', '100', '', '', '', '']\n['Tom Davies', 'Exeter', 'ST5', '42', '', '', '2020/09/01-2021/04/12', '']\n['Tania King Mohammed', 'Exeter', 'ST5', '30', '', '', '', 'Back sep till end of Feb']\n['Jin Virdi', 'Exeter', 'ST5', '100', '', '2020/12/01', '', '']\n['Huub Witt', 'Plymouth', 'ST5', '100', '', '2021/04/01', '', '']\n['Mirna Long', 'Plymouth', 'ST5', '100', '', '2020/11/01', '', '']\n['Jon Stokes', 'Plymouth', 'ST5', '6', '', '', '', '']\n['Karis McFeely', 'Plymouth', 'ST5', '100', '', '', '', '']\n['Harriet Conley', 'Torquay', 'ST5', '0', '', '', '', '']\n['Jon Skinner', 'Torquay', 'ST5', '100', '', '', '', '']\n['Gana Kugathasan', 'Torquay', 'ST5', '100', '', '2020/12/01', '', '']\n['Jack Looker', 'Truro', 'ST5', '100', '', '', '', '']\n['Pete Abernethy', 'Truro', 'ST5', '100', '', '2020/12/01', '', '']\n" + }, { "output_type": "execute_result", "data": { - "text/plain": "{'truro': 1900.0, 'plymouth': 3700.0}" + "text/plain": "['Alex Wood',\n 'Michal Tomek',\n 'Wijhdan Abusrewil',\n 'Salma Aslam',\n 'Sayed Alqarooni',\n 'Delilah Trimmer',\n 'Rebecca Murphy',\n 'Tom Welsh',\n 'Jin Virdi',\n 'Tania King Mohammed',\n 'Tom Davies',\n 'Alexander Sanchez- Cabello',\n 'Hannah Lewis',\n 'Seren Peters',\n 'Adeola Omotade',\n 'Amina Odeh',\n 'Jean Sukumar',\n 'Jenn Fong',\n 'George Edwards',\n 'Khalil Madbak',\n 'Natasha Hougham',\n 'Richard Chaytor',\n 'S Ghauri',\n 'Safi Rahman',\n 'Sophie McGlade',\n 'Huub Witt',\n 'Jon Stokes',\n 'Karis McFeely',\n 'Mirna Long',\n 'Christian Greer',\n 'Max Ireland',\n 'Alan Eccles',\n 'Charles Finan',\n 'Chong Ng',\n 'Matthew Thorley',\n 'Mo Babsail',\n 'Neil McIntyre',\n 'Paul Jenkins',\n 'Gana Kugathasan',\n 'Harriet Conley',\n 'Jon Skinner',\n 'Nicolas Dziad',\n 'Vilim Kalamar',\n 'Chi Leung',\n 'Jenna Millington',\n 'Paul Ward',\n 'Sarath Vennam',\n 'Amoolya Mannava',\n 'Ross Kruger',\n 'Jack Looker',\n 'Pete Abernethy']" }, "metadata": {}, - "execution_count": 5 + "execution_count": 115 } ], "source": [ - "weeks_days_product = list(itertools.product(weeks, days))\n", - "full_time_equivalent_sites" + "workers = []\n", + "\n", + "if use_test_workers:\n", + " # Enter workers ids (name, number, ...)\n", + " workers.extend([workerClass(i, \"Test Person {}\".format(i), \"truro\", 2, nwd=['Fri', \"Sat\", \"Sun\"]) for i in range(1, 2)])\n", + " workers.extend([workerClass(i, \"Test Person {}\".format(i), \"truro\", 2, nwd=[\"Sat\", \"Sun\"]) for i in range(2, 10)])\n", + " workers.extend([workerClass(i, \"Test Person {}\".format(i), \"plymouth\", 4) for i in range(10, 20)])\n", + " workers.extend([workerClass(i, \"Test Person {}\".format(i), \"exeter\", 4) for i in range(20, 30)])\n", + " #workers.extend([workerClass(i, \"Test Person {}\".format(i), 4, 200, \"\") for i in range(48, 49)])\n", + "else:\n", + " # Import trainee data\n", + " import csv\n", + "\n", + " with open('trainees.csv', newline='') as f:\n", + " reader = csv.reader(f, delimiter=',', quotechar='|')\n", + " next(reader) # skip header\n", + "\n", + " n = 0\n", + " for row in reader:\n", + " n = n + 1\n", + " print(row)\n", + " name, site, grade, fte, nwd, end_date, oop, extra = row\n", + " nwds = nwd.split(\",\").title() if nwd else None\n", + " end_date = end_date if end_date else None\n", + " oop = oop.split(\"-\") if oop else None\n", + " #print(nwds, end_date, oop)\n", + " workers.append(workerClass(n, name, site.lower(), int(grade[2]), int(fte), nwds, end_date, oop))\n", + "\n", + "workers = sorted(workers)\n", + "[w.name for w in workers]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "full_time_equivalent = sum([w.fte_adj for w in workers])\n", + "full_time_equivalent_sites = {}\n", + "\n", + "for site in sites:\n", + " full_time_equivalent_sites[site] = sum([w.fte_adj for w in workers if w.site == site])\n", + "" ] }, { "cell_type": "code", "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "#Shifts.GetRequiredShiftsAndWorkers()\n", + "#Shifts.GetAllShiftsAsNames()\n", + "#Shifts.GetNotRequiredShifts()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "metadata": { "tags": [ + "outputPrepend", + "outputPrepend", + "outputPrepend" + ] + }, + "outputs": [], + "source": [ + "# Initialize model\n", + "model = ConcreteModel()\n", + "\n", + "# binary variables representing if a worker is scheduled somewhere\n", + "model.works = Var(((worker.id, week, day, shift) for worker in workers for week, day in Shifts.GetAllWeeksDays() for shift in Shifts.GetShiftNames()),\n", + " within=Binary, initialize=0)\n", + "\n", + "model.nights = Var(((worker.id, week, block) for worker in workers for week in Shifts.weeks for block in night_blocks),\n", + " within=Binary, initialize=0)\n", + "\n", + "#model.unavailable = Var(((worker, week, day) for worker in workers for week in weeks for day in days),\n", + "# within=Binary, initialize=0)\n", + "def availability_init(model, wid, week, day):\n", + " if (wid, week, day) in unavailable_to_work:\n", + " #print((wid, week, day))\n", + " return 0\n", + " return 1\n", + "model.available = Param(((worker.id, week, day) for worker in workers for week, day in Shifts.GetAllWeeksDays()), initialize=availability_init) \n", + "\n", + "# binary variables representing if a worker is necessary\n", + "model.needed = Var([worker.id for worker in workers], within=Binary, initialize=0)\n", + "\n", + "# binary variables representing if a worker worked on sunday but not on saturday (avoid if possible)\n", + "model.no_pref = Var([worker.id for worker in workers], within=Binary, initialize=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# Define an objective function with model as input, to pass later\n", + "def obj_rule(m):\n", + " c = len(workers)\n", + " return 1\n", + " #return sum(m.no_pref[worker.id] for worker in workers)\n", + " #return sum(m.no_pref[worker] for worker in workers) + sum(c * m.needed[worker] for worker in workers)\n", + "# we multiply the second term by a constant to make sure that it is the primary objective\n", + "# since sum(m.no_prefer) is at most len(workers), len(workers) + 1 is a valid constant.\n", + "\n", + "\n", + "# add objective function to the model. rule (pass function) or expr (pass expression directly)\n", + "model.obj = Objective(rule=obj_rule, sense=minimize)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "#[(worker.id, worker.fte_adj, ss) for ss in site_and_shifts for worker in workers if worker.site == ss[0]]\n", + "#[(worker.id, worker.fte_adj, shift.shift) for shift in shifts for worker in workers if worker.site == shift.site and shift.balance_by_site]\n", + "#[1, week, day, shift.name] for week, day, shift in Shifts.GetAllShiftsAsClass()]\n", + "#hifts.GetAllShiftsAsClass()\n", + "#[[1, week, day, shift.name] for week, day, shift in Shifts.GetAllShiftsAsClass() if (shift.rota_on_nwds or day not in nwds)]\n", + "#[(week, day, shift) for week, day in Shifts.GetAllWeeksDays() for shift in Shifts.GetShiftNames()]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "model.constraints = ConstraintList() # Create a set of constraints\n", + "\n", + "for week, day, shift, workers_required, site_required in Shifts.GetRequiredShiftsWorkersAndSites():\n", + " #print(week, day, shift, workers_required, site_required)\n", + " model.constraints.add(\n", + " workers_required == sum(model.works[worker.id, week, day, shift] for worker in workers if worker.site in site_required)\n", + " )\n", + "\n", + "for week, day, shift in Shifts.GetNotRequiredShifts():\n", + " model.constraints.add(\n", + " 0 == sum(model.works[worker.id, week, day, shift.name] for worker in workers)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [ + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", "outputPrepend", "outputPrepend", "outputPrepend", @@ -225,102 +498,30 @@ }, "outputs": [], "source": [ - "# Initialize model\n", - "model = ConcreteModel()\n", "\n", - "# binary variables representing if a worker is scheduled somewhere\n", - "model.works = Var(((worker.id, week, day, shift) for worker in workers for week in weeks for day in days for shift.GetShift() in days_shifts[day]),\n", - " within=Binary, initialize=0)\n", - "\n", - "model.nights = Var(((worker.id, week, block) for worker in workers for week in weeks for block in night_blocks),\n", - " within=Binary, initialize=0)\n", - "\n", - "#model.unavailable = Var(((worker, week, day) for worker in workers for week in weeks for day in days),\n", - "# within=Binary, initialize=0)\n", - "def availability_init(model, wid, week, day):\n", - " if (wid, week, day) in unavailable_to_work:\n", - " print((wid, week, day))\n", - " return 0\n", - " return 1\n", - "model.available = Param(((worker.id, week, day) for worker in workers for week in weeks for day in days), initialize=availability_init) \n", - "\n", - "# binary variables representing if a worker is necessary\n", - "model.needed = Var([worker.id for worker in workers], within=Binary, initialize=0)\n", - "\n", - "# binary variables representing if a worker worked on sunday but not on saturday (avoid if possible)\n", - "model.no_pref = Var([worker.id for worker in workers], within=Binary, initialize=0)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# Define an objective function with model as input, to pass later\n", - "def obj_rule(m):\n", - " c = len(workers)\n", - " return sum(m.no_pref[worker.id] for worker in workers)\n", - " #return sum(m.no_pref[worker] for worker in workers) + sum(c * m.needed[worker] for worker in workers)\n", - "# we multiply the second term by a constant to make sure that it is the primary objective\n", - "# since sum(m.no_prefer) is at most len(workers), len(workers) + 1 is a valid constant.\n", - "\n", - "\n", - "# add objective function to the model. rule (pass function) or expr (pass expression directly)\n", - "model.obj = Objective(rule=obj_rule, sense=minimize)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": "[(1, 100.0, ('truro', 'truro_twighlight')),\n (2, 100.0, ('truro', 'truro_twighlight')),\n (3, 100.0, ('truro', 'truro_twighlight')),\n (4, 100.0, ('truro', 'truro_twighlight')),\n (5, 100.0, ('truro', 'truro_twighlight')),\n (6, 100.0, ('truro', 'truro_twighlight')),\n (7, 100.0, ('truro', 'truro_twighlight')),\n (8, 100.0, ('truro', 'truro_twighlight')),\n (9, 100.0, ('truro', 'truro_twighlight')),\n (10, 100.0, ('truro', 'truro_twighlight')),\n (11, 100.0, ('truro', 'truro_twighlight')),\n (12, 100.0, ('truro', 'truro_twighlight')),\n (13, 100.0, ('truro', 'truro_twighlight')),\n (14, 100.0, ('truro', 'truro_twighlight')),\n (15, 100.0, ('truro', 'truro_twighlight')),\n (16, 100.0, ('truro', 'truro_twighlight')),\n (17, 100.0, ('truro', 'truro_twighlight')),\n (18, 100.0, ('truro', 'truro_twighlight')),\n (19, 100.0, ('truro', 'truro_twighlight')),\n (21, 100.0, ('plymouth', 'plymouth_twighlight')),\n (22, 100.0, ('plymouth', 'plymouth_twighlight')),\n (23, 100.0, ('plymouth', 'plymouth_twighlight')),\n (24, 100.0, ('plymouth', 'plymouth_twighlight')),\n (25, 100.0, ('plymouth', 'plymouth_twighlight')),\n (26, 100.0, ('plymouth', 'plymouth_twighlight')),\n (27, 100.0, ('plymouth', 'plymouth_twighlight')),\n (28, 100.0, ('plymouth', 'plymouth_twighlight')),\n (29, 100.0, ('plymouth', 'plymouth_twighlight')),\n (30, 100.0, ('plymouth', 'plymouth_twighlight')),\n (31, 100.0, ('plymouth', 'plymouth_twighlight')),\n (32, 100.0, ('plymouth', 'plymouth_twighlight')),\n (33, 100.0, ('plymouth', 'plymouth_twighlight')),\n (34, 100.0, ('plymouth', 'plymouth_twighlight')),\n (35, 100.0, ('plymouth', 'plymouth_twighlight')),\n (36, 100.0, ('plymouth', 'plymouth_twighlight')),\n (37, 100.0, ('plymouth', 'plymouth_twighlight')),\n (38, 100.0, ('plymouth', 'plymouth_twighlight')),\n (39, 100.0, ('plymouth', 'plymouth_twighlight')),\n (40, 100.0, ('plymouth', 'plymouth_twighlight')),\n (41, 100.0, ('plymouth', 'plymouth_twighlight')),\n (42, 100.0, ('plymouth', 'plymouth_twighlight')),\n (43, 100.0, ('plymouth', 'plymouth_twighlight')),\n (44, 100.0, ('plymouth', 'plymouth_twighlight')),\n (45, 100.0, ('plymouth', 'plymouth_twighlight')),\n (46, 100.0, ('plymouth', 'plymouth_twighlight')),\n (47, 100.0, ('plymouth', 'plymouth_twighlight')),\n (48, 100.0, ('plymouth', 'plymouth_twighlight')),\n (49, 100.0, ('plymouth', 'plymouth_twighlight')),\n (50, 100.0, ('plymouth', 'plymouth_twighlight')),\n (51, 100.0, ('plymouth', 'plymouth_twighlight')),\n (52, 100.0, ('plymouth', 'plymouth_twighlight')),\n (53, 100.0, ('plymouth', 'plymouth_twighlight')),\n (54, 100.0, ('plymouth', 'plymouth_twighlight')),\n (55, 100.0, ('plymouth', 'plymouth_twighlight')),\n (56, 100.0, ('plymouth', 'plymouth_twighlight')),\n (57, 100.0, ('plymouth', 'plymouth_twighlight'))]" - }, - "metadata": {}, - "execution_count": 8 - } - ], - "source": [ - "#[(worker.id, worker.fte_adj, ss) for ss in site_and_shifts for worker in workers if worker.site == ss[0]]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "model.constraints = ConstraintList() # Create a set of constraints\n", "\n", "# Constraint: all shifts are assigned\n", - "for week in weeks:\n", - " for day in days:\n", - " for shiftClass in days_shifts[day]:\n", - " shift = shiftClass.GetShift()\n", - " # 3 people per night\n", - " if shift in ['night']:\n", - " model.constraints.add( # to add a constraint to model.constraints set\n", - " 3 == sum(model.works[worker.id, week, day, shift] for worker in workers)\n", - " )\n", - " elif day in days[:5] and shift in ['truro_twighlight']: \n", - " model.constraints.add( # to add a constraint to model.constraints set\n", - " 1 == sum(model.works[worker.id, week, day, shift] for worker in workers if worker.site == \"truro\")\n", - " )\n", - " elif day in days[:5] and shift in ['plymouth_twighlight']: \n", - " model.constraints.add( # to add a constraint to model.constraints set\n", - " 1 == sum(model.works[worker.id, week, day, shift] for worker in workers if worker.site == \"plymouth\")\n", - " ) \n", - " # make sure twighlights are not assigned on a weekend\n", - " elif day in days[5:] and shift in ['truro_twighlight', 'plymouth_twighlight']:\n", - " model.constraints.add( # to add a constraint to model.constraints set\n", - " 0 == sum(model.works[worker.id, week, day, shift] for worker in workers)\n", - " )\n", + "#for week, day, shift in Shifts.GetAllShiftsAsNames():\n", + "# # 3 people per night\n", + "# if shift in ['night']:\n", + "# model.constraints.add( # to add a constraint to model.constraints set\n", + "# 3 == sum(model.works[worker.id, week, day, shift] for worker in workers)\n", + "# )\n", + "# elif day in days[:5] and shift in ['truro_twilight']: \n", + "# model.constraints.add( # to add a constraint to model.constraints set\n", + "# 1 == sum(model.works[worker.id, week, day, shift] for worker in workers if worker.site == \"truro\")\n", + "# )\n", + "# elif day in days[:5] and shift in ['plymouth_twilight']: \n", + "# model.constraints.add( # to add a constraint to model.constraints set\n", + "# 1 == sum(model.works[worker.id, week, day, shift] for worker in workers if worker.site == \"plymouth\")\n", + "# ) \n", + "# # make sure twilights are not assigned on a weekend\n", + "# elif day in days[5:] and shift in ['truro_twilight', 'plymouth_twilight']:\n", + "# model.constraints.add( # to add a constraint to model.constraints set\n", + "# 0 == sum(model.works[worker.id, week, day, shift] for worker in workers)\n", + "# )\n", " \n", - " #elif day in days[:-1] and shift in ['truro_twighlight', 'plymouth_twighlight']:\n", + " #elif day in days[:-1] and shift in ['truro_twilight', 'plymouth_twilight']:\n", " # # weekdays' and Saturdays' day shifts have exactly two workers\n", " # model.constraints.add( # to add a constraint to model.constraints set\n", " # 1 == sum(model.works[worker.id, week, day, shift] for worker in workers)\n", @@ -344,51 +545,64 @@ "# )\n", "\n", "def maxHoursPerWeekRule(model, wid, week):\n", - " return(72 >= sum(shift_lengths[shift] * model.works[wid, week, day, shift.GetShift()]for day in days for shift in days_shifts[day]))\n", - "model.max_hours_per_week_constraint = Constraint([worker.id for worker in workers], weeks, rule=maxHoursPerWeekRule)\n", + " return(72 >= sum(shift.length * model.works[wid, week, day, shift.name] for day, shift in Shifts.GetAllDayShiftsAsClass()))\n", + "model.max_hours_per_week_constraint = Constraint([worker.id for worker in workers], Shifts.weeks, rule=maxHoursPerWeekRule)\n", "\n", "def maxHoursRule(model, wid):\n", - " return(8000 >= sum(shift_lengths[shift] * model.works[wid, week, day, shift.GetShift()] for week in weeks for day in days for shift in days_shifts[day]))\n", + " return(8000 >= sum(shift.length * model.works[wid, week, day, shift.name] for week, day, shift in Shifts.GetAllShiftsAsClass()))\n", "model.max_hours_constraint = Constraint([worker.id for worker in workers], rule=maxHoursRule)\n", "\n", "# Shifts should not be allocated on nwds (excepting night shifts)\n", - "def nwdRule(model, wid, nwds):\n", - " if nwds != \"\":\n", - " return(1 >= sum(model.works[wid, week, day, shift.GetShift()] for week in weeks for day in nwds.split(\",\") for shift in [\"truro_twighlight\",\"plymouth_twighlight\"]) + 1)\n", - " return Constraint.Skip\n", - "model.nwd_constraint = Constraint([(worker.id, worker.nwd) for worker in workers], rule=nwdRule)\n", + "def nwdRule(model, worker):\n", + " print(worker, worker.id, worker.nwd)\n", + " wid = worker.id\n", + " nwd = worker.nwd\n", + " return(0 == sum(model.works[wid, week, day, shift.name] for week, day, shift in Shifts.GetAllShiftsAsClass() if (not shift.rota_on_nwds and day in nwd)))\n", + "\n", + "#model.nwd_constraint = Constraint([(worker) for worker in workers if worker.nwd], rule=nwdRule)\n", + "\n", + "for worker in workers:\n", + " if worker.nwd:\n", + " for week, day, shift in Shifts.GetAllShiftsAsClass():\n", + " if not shift.rota_on_nwds and day in worker.nwd:\n", + " model.constraints.add(\n", + " 0 == model.works[worker.id, week, day, shift.name]\n", + " )\n", "\n", "# set a rule to even night shifts (could also be done in blocks, which may be quicker?)\n", "# takes into accound both LTFT, OOP and leaving the training scheme\n", - "total_night_shifts = len(weeks) * 7 * 3\n", + "total_night_shifts = Shifts.rota_days_length * Shifts.GetShiftByName(\"night\").workers_required\n", "\n", "def nightShiftNumberRule(model, wid, fte_adj):\n", + " \n", " max_shifts = total_night_shifts / full_time_equivalent * fte_adj + night_balance_offset\n", " min_shifts = total_night_shifts / full_time_equivalent * fte_adj - night_balance_offset\n", - " \n", - " return inequality(min_shifts, sum(model.works[wid, week, day, \"night\"] for week in weeks for day in days), max_shifts)\n", + "\n", + " return inequality(min_shifts, sum(model.works[wid, week, day, \"night\"] for week, day in Shifts.GetAllWeeksDays()), max_shifts)\n", "model.night_shifts_constraint = Constraint([(worker.id, worker.fte_adj) for worker in workers], rule=nightShiftNumberRule)\n", "\n", "\n", - "twighlight_constraints = {}\n", + "twilight_constraints = {}\n", "\n", - "# Set a rule to even twighlight shifts:\n", - "total_twighlight_shifts = len(weeks) * 5 * 1 # One person per site\n", + "# Set a rule to even twilight shifts:\n", + "#total_twilight_shifts = len(weeks) * 5 * 1 # One person per site\n", "\n", - "def truroTwighlightShiftNumberRule(model, wid, fte_adj, site, shift):\n", - " #site, shift = site_and_shift\n", - " max_shifts = total_twighlight_shifts / full_time_equivalent_sites[site] * fte_adj + twighlight_balance_offset\n", - " min_shifts = total_twighlight_shifts / full_time_equivalent_sites[site] * fte_adj - twighlight_balance_offset\n", + "#def trurotwilightShiftNumberRule(model, wid, fte_adj, site, shift):\n", + "# #site, shift = site_and_shift\n", + "# max_shifts = total_twilight_shifts / full_time_equivalent_sites[site] * fte_adj + twilight_balance_offset\n", + "# min_shifts = total_twilight_shifts / full_time_equivalent_sites[site] * fte_adj - twilight_balance_offset\n", " \n", - " return inequality(min_shifts, sum(model.works[wid, week, day, shift.GetShift()] for week in weeks for day in days), max_shifts)\n", - "model.twighlight_shifts_truro_constraint = Constraint([(worker.id, worker.fte_adj, ss[0], ss[1]) for ss in site_and_shifts for worker in workers if worker.site == ss[0]], rule=truroTwighlightShiftNumberRule)\n", + "# return inequality(min_shifts, sum(model.works[wid, week, day, shift.GetShift()] for week in weeks for day in days), max_shifts)\n", + "#model.twilight_shifts_truro_constraint = Constraint([(worker.id, worker.fte_adj, ss[0], ss[1]) for ss in site_and_shifts for worker in workers if worker.site == ss[0]], rule=trurotwilightShiftNumberRule)\n", "\n", - "#def plymouthTwighlightShiftNumberRule(model, wid, fte_adj):\n", - "# max_shifts = total_twighlight_shifts / full_time_equivalent_sites[\"plymouth\"] * fte_adj + twighlight_balance_offset\n", - "# min_shifts = total_twighlight_shifts / full_time_equivalent_sites[\"plymouth\"] * fte_adj - twighlight_balance_offset\n", + "\n", + "\n", + "#def plymouthtwilightShiftNumberRule(model, wid, fte_adj):\n", + "# max_shifts = total_twilight_shifts / full_time_equivalent_sites[\"plymouth\"] * fte_adj + twilight_balance_offset\n", + "# min_shifts = total_twilight_shifts / full_time_equivalent_sites[\"plymouth\"] * fte_adj - twilight_balance_offset\n", "# \n", - "# return inequality(min_shifts, sum(model.works[wid, week, day, \"plymouth_twighlight\"] for week in weeks for day in days), max_shifts)\n", - "#model.twighlight_shifts_plymouth_constraint = Constraint([(worker.id, worker.fte_adj) for worker in workers if worker.site == \"plymouth\"], #rule=plymouthTwighlightShiftNumberRule)\n", + "# return inequality(min_shifts, sum(model.works[wid, week, day, \"plymouth_twilight\"] for week in weeks for day in days), max_shifts)\n", + "#model.twilight_shifts_plymouth_constraint = Constraint([(worker.id, worker.fte_adj) for worker in workers if worker.site == \"plymouth\"], #rule=plymouthtwilightShiftNumberRule)\n", "\n", "\n", "# Limit to 1 ST2 (or below) on a night shift\n", @@ -397,7 +611,7 @@ " if not single_workers:\n", " return Constraint.Skip\n", " return (sum(model.nights[w.id, week, block] for w in single_workers) <= 1)\n", - "model.night_shifts_max_st_constraint = Constraint([week for week in weeks], [block for block in [\"weekday\", \"weekend\"]], rule=nightShiftMaxSTRule)\n", + "model.night_shifts_max_st_constraint = Constraint([week for week in Shifts.weeks], [block for block in [\"weekday\", \"weekend\"]], rule=nightShiftMaxSTRule)\n", "\n", "# Enusre at least 1 ST4+ on a night shift\n", "def nightShiftMinST4Rule(model, week, block):\n", @@ -405,11 +619,167 @@ " if not single_workers:\n", " return Constraint.Skip\n", " return (sum(model.nights[w.id, week, block] for w in single_workers) >= 1)\n", - "model.night_shifts_min_st4_constraint = Constraint([week for week in weeks], [block for block in [\"weekday\", \"weekend\"]], rule=nightShiftMinST4Rule)\n", + "model.night_shifts_min_st4_constraint = Constraint([week for week in Shifts.weeks], [block for block in [\"weekday\", \"weekend\"]], rule=nightShiftMinST4Rule)\n", + "\n", + "\n", + "\n", + " \n", + "# Constraint (def of model.needed)\n", + "#for worker in workers:\n", + "# model.constraints.add(\n", + "# 10000 * model.needed[worker.id] >= sum(model.works[worker.id, week, day, shift] for week in weeks for day in days for shift in days_shifts[day])\n", + "# ) # if any model.works[worker, ·, ·] non-zero, model.needed[worker] must be one; else is zero to reduce the obj function\n", + " # 10000 is to remark, but 5 was enough since max of 40 hours yields max of 5 shifts, the maximum possible sum\n", + "\n", + "# Constraint (def of model.no_pref)\n", + "#for worker in workers:\n", + "# for week in weeks:\n", + "# model.constraints.add(\n", + "# model.no_pref[worker.id] >= sum(model.works[worker.id, week, 'Sat', shift] for shift in days_shifts['Sat'])\n", + "# - sum(model.works[worker.id, week, 'Sun', shift] for shift in days_shifts['Sun'])\n", + "# ) # if not working on sunday but working saturday model.needed must be 1; else will be zero to reduce the obj function\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n11.12950872310821 9.12950872310821\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n5.051803489243284 3.0518034892432837\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.751280222985675 4.751280222985675\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n5.60102417838854 3.60102417838854\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n9.60259362501515 7.60259362501515\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n4.4410374500060605 2.44103745000606\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n18.923156172353192 16.923156172353192\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.169262468941277 6.169262468941276\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n9.961578086176596 7.961578086176596\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n4.584631234470638 2.584631234470638\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.751280222985675 4.751280222985675\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n5.60102417838854 3.60102417838854\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n0.7518332222289559 -1.2481667777710441\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n0.9007332888915823 -1.0992667111084176\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.3769468517059575 4.3769468517059575\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n3.150778740682383 1.1507787406823828\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n9.370704805769346 7.370704805769346\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n4.348281922307739 2.3482819223077387\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n3.896707072016594 1.8967070720165942\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n3.3173656576132755 1.3173656576132755\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.5751280222985673 -0.4248719777014326\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.460102417838854 -0.539897582161146\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n10.585467038309456 8.585467038309456\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n8.668373630647567 6.668373630647567\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n1.0 -1.0\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n13.661885903885262 11.661885903885262\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.064754361554105 4.064754361554105\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.913518141924435 4.913518141924435\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n3.365407256769774 1.3654072567697741\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n15.337656041691915 13.337656041691915\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n6.735062416676766 4.735062416676766\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n7.696158041449521 5.696158041449521\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n3.6784632165798086 1.6784632165798086\nWARNING: DEPRECATED: Chained inequalities are deprecated. Use the inequality()\n function to express ranged inequality expressions.\n" + } + ], + "source": [ + "def balanceSiteShiftNumberRule(model, wid, fte_adj, shiftClass):\n", + " # TODO: make helper to retrieve\n", + " total_shifts = len(Shifts.weeks) * len(shiftClass.shift_days) * shiftClass.workers_required # One person per site\n", + "\n", + " full_time_equivalent_joined = sum([full_time_equivalent_sites[i] for i in shiftClass.site])\n", + "\n", + "\n", + " max_shifts = total_shifts / full_time_equivalent_joined * fte_adj + twilight_balance_offset\n", + " min_shifts = total_shifts / full_time_equivalent_joined * fte_adj - twilight_balance_offset\n", + " print(max_shifts, min_shifts)\n", + "\n", + " return inequality(min_shifts, sum(model.works[wid, week, day, shiftClass.name] for week, day in Shifts.GetAllWeeksDays()), max_shifts)\n", + "\n", + "#model.balance_site_shift_constraint = Constraint([(worker.id, worker.fte_adj, shift) for shift in Shifts.GetShiftOptions() for worker in workers if worker.site in shift.site and shift.balance_by_site], rule=balanceSiteShiftNumberRule)\n", "\n", - "# Constraint: rest between two shifts is of 12 hours (i.e., at least two shifts)\n", "for worker in workers:\n", - " for week in weeks:\n", + " for shift in Shifts.GetShiftOptions():\n", + " if worker.site in shift.site and shift.balance_by_site:\n", + " total_shifts = len(Shifts.weeks) * len(shift.shift_days) * shift.workers_required # One person per site\n", + "\n", + " full_time_equivalent_joined = sum([full_time_equivalent_sites[i] for i in shift.site])\n", + "\n", + "\n", + " max_shifts = total_shifts / full_time_equivalent_joined * worker.fte_adj + twilight_balance_offset\n", + " min_shifts = total_shifts / full_time_equivalent_joined * worker.fte_adj - twilight_balance_offset\n", + " print(max_shifts, min_shifts)\n", + " \n", + " model.constraints.add(\n", + " #min_shifts <= sum(model.works[worker.id, week, day, shift.name] for week, day in Shifts.GetAllWeeksDays()) <= max_shifts\n", + " return inequality(min_shifts, sum(model.works[wid, week, day, shiftClass.name] for week, day in Shifts.GetAllWeeksDays()), max_shifts)\n", + " )\n", + " #return inequality(min_shifts, sum(model.works[wid, week, day, shiftClass.name] for week, day in Shifts.GetAllWeeksDays()), max_shifts)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "['exeter_twilight',\n 'truro_twilight',\n 'torquay_twilight',\n 'plymouth_twilight',\n 'weekend_exeter',\n 'weekend_truro',\n 'weekend_torquay',\n 'weekend_plymouth',\n 'night']" + }, + "metadata": {}, + "execution_count": 13 + } + ], + "source": [ + "[s.name for s in Shifts.GetShiftOptions() ]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "[1, 6, 7, 11, 18, 25, 26, 27, 40, 41, 42]" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "source": [ + "#[[week, day, shift.name] for week, day, shift in Shifts.GetAllShiftsAsClass() if (not shift.rota_on_nwds and day in [\"Fri\", \"Wed\"])]\n", + "[worker.id for worker in workers if worker.site in (\"exeter\")]\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend", + "outputPrepend" + ] + }, + "outputs": [], + "source": [ + "# Constraint: rest between two shifts is of 12 hours (i.e., at least two shifts)\n", + "weeks_days = Shifts.GetAllWeeksDays()\n", + "for worker in workers:\n", + " for week_blocks in Shifts.GetWeeksInBlocks(2):\n", + " # Prevent nights more than once every n weeks\n", + " model.constraints.add(\n", + " 1 >= sum(model.nights[worker.id, week, block] for week in week_blocks for block in [\"weekday\", \"weekend\"])\n", + " )\n", + "\n", + " for week in Shifts.weeks:\n", " #model.constraints.add(\n", " #0 == sum(model.works[worker, week, day, shift] for day in days for shift in [\"night\"]) * (sum(model.works[worker, week, day, shift] for day in days for shift in [\"night\"]) - 4) * (sum(model.works[worker, week, day, shift] for day in days for shift in [\"night\"]) - 3)\n", " # 0 == (sum(model.works[worker, week, day, shift] for day in days for shift in [\"night\"]) - 4) * (sum(model.works[worker, week, day, shift] for day in days for shift in [\"night\"]) - 3)\n", @@ -427,65 +797,106 @@ " model.constraints.add(\n", " model.nights[worker.id, week, \"weekend\"] * 3 == sum(model.works[worker.id, week, day, \"night\"] for day in days[4:])\n", " )\n", + "\n", + " # \n", " \n", + " for n in range(len(weeks_days)):\n", "\n", + " week, day = weeks_days[n]\n", "\n", - " for j in range(len(days)):\n", - " nweek = min(week + (j + 1) // 7, weeks[-1])\n", - " pweek = week\n", - " if j == 0:\n", - " pweek = max(week - 1, 1)\n", + " p1 = 1\n", + " if n > 0:\n", + " pweek, pday = weeks_days[n-1]\n", + " else:\n", + " p1 = 0\n", + " pweek, pday = weeks_days[n]\n", "\n", - " # Unable to work (hard constraint not preference)\n", - " model.constraints.add(\n", - " model.available[(worker.id, week, days[j])] >= \\\n", - " sum(model.works[worker.id, week, days[j], shift] for shift in days_shifts[days[j]]) \n", - " )\n", - " model.constraints.add(\n", - " model.available[(worker.id, week, days[j])] >= \\\n", - " model.works[worker.id, pweek, days[(j - 1) % 7], \"night\"]\n", - " )\n", + " n1 = 1\n", + " n2 = 1\n", + " try:\n", + " nweek, nday = weeks_days[n+1]\n", + " except IndexError:\n", + " #print(worker.id, \"shit\")\n", + " nweek, nday = weeks_days[n]\n", + " n1 = 0\n", + " \n", + " try:\n", + " \n", + " n2week, n2day = weeks_days[n+2]\n", + " except IndexError:\n", + " #print(worker.id, \"shit2\")\n", + " n2week, n2day = weeks_days[n]\n", + " n2 = 0\n", "\n", - "\n", - " # single shift per day\n", - " model.constraints.add(\n", - " 1 >= sum(model.works[worker.id, week, days[j], shift] for shift in days_shifts[days[j]])\n", + " #print(week, day, nweek, nday, n2week, n2day, n1, n2)\n", + " # Unable to work (hard constraint not preference)\n", + " model.constraints.add(\n", + " model.available[worker.id, week, day] >= \\\n", + " sum(model.works[worker.id, week, day, shift] for shift in Shifts.GetShiftNamesByDay(day)) \n", " )\n", - " ## if working in evening, until next evening (note that after sunday comes next monday)\n", - " #model.constraints.add(\n", - " # 1 >= sum(model.works[worker.id, week, days[j], shift] for shift in ['evening', 'night']) +\n", - " # model.works[worker.id, nweek, days[(j + 1) % 7], 'truro_twighlight']\n", - " #)\n", - "\n", - " # if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights\n", - " model.constraints.add(\n", - " 1 >= model.works[worker.id, week, days[j], \"night\"] +\n", - " sum(model.works[worker.id, nweek, days[(j + 1) % 7], shift] for shift in ['truro_twighlight', 'plymouth_twighlight']) +\n", - " sum(model.works[worker.id, nweek, days[(j + 2) % 7], shift] for shift in ['truro_twighlight', 'plymouth_twighlight']) +\n", - " sum(model.works[worker.id, pweek, days[(j - 1) % 7], shift] for shift in ['truro_twighlight', 'plymouth_twighlight'])\n", + " model.constraints.add(\n", + " model.available[worker.id, week, day] >= \\\n", + " model.works[worker.id, pweek, pday, \"night\"]\n", " )\n", "\n", - "# Constraint (def of model.needed)\n", - "#for worker in workers:\n", - "# model.constraints.add(\n", - "# 10000 * model.needed[worker.id] >= sum(model.works[worker.id, week, day, shift] for week in weeks for day in days for shift in days_shifts[day])\n", - "# ) # if any model.works[worker, ·, ·] non-zero, model.needed[worker] must be one; else is zero to reduce the obj function\n", - " # 10000 is to remark, but 5 was enough since max of 40 hours yields max of 5 shifts, the maximum possible sum\n", "\n", - "# Constraint (def of model.no_pref)\n", - "#for worker in workers:\n", - "# for week in weeks:\n", - "# model.constraints.add(\n", - "# model.no_pref[worker.id] >= sum(model.works[worker.id, week, 'Sat', shift] for shift in days_shifts['Sat'])\n", - "# - sum(model.works[worker.id, week, 'Sun', shift] for shift in days_shifts['Sun'])\n", - "# ) # if not working on sunday but working saturday model.needed must be 1; else will be zero to reduce the obj function\n" + " # single shift per day\n", + " model.constraints.add(\n", + " 1 >= sum(model.works[worker.id, week, day, shift] for shift in Shifts.GetShiftNamesByDay(day))\n", + " )\n", + " ## if working in evening, until next evening (note that after sunday comes next monday)\n", + " #model.constraints.add(\n", + " # 1 >= sum(model.works[worker.id, week, days[j], shift] for shift in ['evening', 'night']) +\n", + " # model.works[worker.id, nweek, days[(j + 1) % 7], 'truro_twilight']\n", + " #)\n", + "\n", + " # if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights\n", + "\n", + "\n", + " model.constraints.add(\n", + " 1 >= model.works[worker.id, week, day, \"night\"] +\n", + " sum(n1 * model.works[worker.id, nweek, nday, shift] for shift in Shifts.GetShiftNamesByDay(nday) if shift != \"night\") +\n", + " sum(n2 * model.works[worker.id, n2week, n2day, shift] for shift in Shifts.GetShiftNamesByDay(n2day) if shift != \"night\") +\n", + " sum(p1 * model.works[worker.id, pweek, pday, shift] for shift in Shifts.GetShiftNamesByDay(pday) if shift != \"night\")\n", + " )" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "0\n0\n" + }, + { + "output_type": "execute_result", + "data": { + "text/plain": "{'exeter_twilight',\n 'night',\n 'plymouth_twilight',\n 'torquay_twilight',\n 'truro_twilight'}" + }, + "metadata": {}, + "execution_count": 16 + } + ], + "source": [ + "print(model.works[18,6, \"Fri\", \"night\"].value)\n", + "print(model.works[18,7, \"Mon\", \"truro_twilight\"].value * 0)\n", + "Shifts.GetShiftNamesByDay(\"Mon\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "WARNING: Constant objective detected, replacing with a placeholder to prevent\n solver failure.\n" + } + ], "source": [ "opt = SolverFactory('cbc') # choose a solver\n", "results = opt.solve(model) # solve the model with the selected solver" @@ -493,14 +904,20 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 78, "metadata": { "tags": [ "outputPrepend", "outputPrepend" ] }, - "outputs": [], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "1 exeter_twilight\n1 truro_twilight\n1 torquay_twilight\n1 plymouth_twilight\n1 weekend_exeter\n1 weekend_truro\n1 weekend_torquay\n1 weekend_plymouth\n1 night\n2 exeter_twilight\n2 truro_twilight\n2 torquay_twilight\n2 plymouth_twilight\n2 weekend_exeter\n2 weekend_truro\n2 weekend_torquay\n2 weekend_plymouth\n2 night\n3 exeter_twilight\n3 truro_twilight\n3 torquay_twilight\n3 plymouth_twilight\n3 weekend_exeter\n3 weekend_truro\n3 weekend_torquay\n3 weekend_plymouth\n3 night\n4 exeter_twilight\n4 truro_twilight\n4 torquay_twilight\n4 plymouth_twilight\n4 weekend_exeter\n4 weekend_truro\n4 weekend_torquay\n4 weekend_plymouth\n4 night\n5 exeter_twilight\n5 truro_twilight\n5 torquay_twilight\n5 plymouth_twilight\n5 weekend_exeter\n5 weekend_truro\n5 weekend_torquay\n5 weekend_plymouth\n5 night\n6 exeter_twilight\n6 truro_twilight\n6 torquay_twilight\n6 plymouth_twilight\n6 weekend_exeter\n6 weekend_truro\n6 weekend_torquay\n6 weekend_plymouth\n6 night\n7 exeter_twilight\n7 truro_twilight\n7 torquay_twilight\n7 plymouth_twilight\n7 weekend_exeter\n7 weekend_truro\n7 weekend_torquay\n7 weekend_plymouth\n7 night\n8 exeter_twilight\n8 truro_twilight\n8 torquay_twilight\n8 plymouth_twilight\n8 weekend_exeter\n8 weekend_truro\n8 weekend_torquay\n8 weekend_plymouth\n8 night\n9 exeter_twilight\n9 truro_twilight\n9 torquay_twilight\n9 plymouth_twilight\n9 weekend_exeter\n9 weekend_truro\n9 weekend_torquay\n9 weekend_plymouth\n9 night\n10 exeter_twilight\n10 truro_twilight\n10 torquay_twilight\n10 plymouth_twilight\n10 weekend_exeter\n10 weekend_truro\n10 weekend_torquay\n10 weekend_plymouth\n10 night\n11 exeter_twilight\n11 truro_twilight\n11 torquay_twilight\n11 plymouth_twilight\n11 weekend_exeter\n11 weekend_truro\n11 weekend_torquay\n11 weekend_plymouth\n11 night\n12 exeter_twilight\n12 truro_twilight\n12 torquay_twilight\n12 plymouth_twilight\n12 weekend_exeter\n12 weekend_truro\n12 weekend_torquay\n12 weekend_plymouth\n12 night\n13 exeter_twilight\n13 truro_twilight\n13 torquay_twilight\n13 plymouth_twilight\n13 weekend_exeter\n13 weekend_truro\n13 weekend_torquay\n13 weekend_plymouth\n13 night\n14 exeter_twilight\n14 truro_twilight\n14 torquay_twilight\n14 plymouth_twilight\n14 weekend_exeter\n14 weekend_truro\n14 weekend_torquay\n14 weekend_plymouth\n14 night\n15 exeter_twilight\n15 truro_twilight\n15 torquay_twilight\n15 plymouth_twilight\n15 weekend_exeter\n15 weekend_truro\n15 weekend_torquay\n15 weekend_plymouth\n15 night\n16 exeter_twilight\n16 truro_twilight\n16 torquay_twilight\n16 plymouth_twilight\n16 weekend_exeter\n16 weekend_truro\n16 weekend_torquay\n16 weekend_plymouth\n16 night\n17 exeter_twilight\n17 truro_twilight\n17 torquay_twilight\n17 plymouth_twilight\n17 weekend_exeter\n17 weekend_truro\n17 weekend_torquay\n17 weekend_plymouth\n17 night\n18 exeter_twilight\n18 truro_twilight\n18 torquay_twilight\n18 plymouth_twilight\n18 weekend_exeter\n18 weekend_truro\n18 weekend_torquay\n18 weekend_plymouth\n18 night\n19 exeter_twilight\n19 truro_twilight\n19 torquay_twilight\n19 plymouth_twilight\n19 weekend_exeter\n19 weekend_truro\n19 weekend_torquay\n19 weekend_plymouth\n19 night\n20 exeter_twilight\n20 truro_twilight\n20 torquay_twilight\n20 plymouth_twilight\n20 weekend_exeter\n20 weekend_truro\n20 weekend_torquay\n20 weekend_plymouth\n20 night\n21 exeter_twilight\n21 truro_twilight\n21 torquay_twilight\n21 plymouth_twilight\n21 weekend_exeter\n21 weekend_truro\n21 weekend_torquay\n21 weekend_plymouth\n21 night\n22 exeter_twilight\n22 truro_twilight\n22 torquay_twilight\n22 plymouth_twilight\n22 weekend_exeter\n22 weekend_truro\n22 weekend_torquay\n22 weekend_plymouth\n22 night\n23 exeter_twilight\n23 truro_twilight\n23 torquay_twilight\n23 plymouth_twilight\n23 weekend_exeter\n23 weekend_truro\n23 weekend_torquay\n23 weekend_plymouth\n23 night\n24 exeter_twilight\n24 truro_twilight\n24 torquay_twilight\n24 plymouth_twilight\n24 weekend_exeter\n24 weekend_truro\n24 weekend_torquay\n24 weekend_plymouth\n24 night\n25 exeter_twilight\n25 truro_twilight\n25 torquay_twilight\n25 plymouth_twilight\n25 weekend_exeter\n25 weekend_truro\n25 weekend_torquay\n25 weekend_plymouth\n25 night\n26 exeter_twilight\n26 truro_twilight\n26 torquay_twilight\n26 plymouth_twilight\n26 weekend_exeter\n26 weekend_truro\n26 weekend_torquay\n26 weekend_plymouth\n26 night\n27 exeter_twilight\n27 truro_twilight\n27 torquay_twilight\n27 plymouth_twilight\n27 weekend_exeter\n27 weekend_truro\n27 weekend_torquay\n27 weekend_plymouth\n27 night\n28 exeter_twilight\n28 truro_twilight\n28 torquay_twilight\n28 plymouth_twilight\n28 weekend_exeter\n28 weekend_truro\n28 weekend_torquay\n28 weekend_plymouth\n28 night\n29 exeter_twilight\n29 truro_twilight\n29 torquay_twilight\n29 plymouth_twilight\n29 weekend_exeter\n29 weekend_truro\n29 weekend_torquay\n29 weekend_plymouth\n29 night\n30 exeter_twilight\n30 truro_twilight\n30 torquay_twilight\n30 plymouth_twilight\n30 weekend_exeter\n30 weekend_truro\n30 weekend_torquay\n30 weekend_plymouth\n30 night\n31 exeter_twilight\n31 truro_twilight\n31 torquay_twilight\n31 plymouth_twilight\n31 weekend_exeter\n31 weekend_truro\n31 weekend_torquay\n31 weekend_plymouth\n31 night\n32 exeter_twilight\n32 truro_twilight\n32 torquay_twilight\n32 plymouth_twilight\n32 weekend_exeter\n32 weekend_truro\n32 weekend_torquay\n32 weekend_plymouth\n32 night\n33 exeter_twilight\n33 truro_twilight\n33 torquay_twilight\n33 plymouth_twilight\n33 weekend_exeter\n33 weekend_truro\n33 weekend_torquay\n33 weekend_plymouth\n33 night\n34 exeter_twilight\n34 truro_twilight\n34 torquay_twilight\n34 plymouth_twilight\n34 weekend_exeter\n34 weekend_truro\n34 weekend_torquay\n34 weekend_plymouth\n34 night\n35 exeter_twilight\n35 truro_twilight\n35 torquay_twilight\n35 plymouth_twilight\n35 weekend_exeter\n35 weekend_truro\n35 weekend_torquay\n35 weekend_plymouth\n35 night\n36 exeter_twilight\n36 truro_twilight\n36 torquay_twilight\n36 plymouth_twilight\n36 weekend_exeter\n36 weekend_truro\n36 weekend_torquay\n36 weekend_plymouth\n36 night\n37 exeter_twilight\n37 truro_twilight\n37 torquay_twilight\n37 plymouth_twilight\n37 weekend_exeter\n37 weekend_truro\n37 weekend_torquay\n37 weekend_plymouth\n37 night\n38 exeter_twilight\n38 truro_twilight\n38 torquay_twilight\n38 plymouth_twilight\n38 weekend_exeter\n38 weekend_truro\n38 weekend_torquay\n38 weekend_plymouth\n38 night\n39 exeter_twilight\n39 truro_twilight\n39 torquay_twilight\n39 plymouth_twilight\n39 weekend_exeter\n39 weekend_truro\n39 weekend_torquay\n39 weekend_plymouth\n39 night\n40 exeter_twilight\n40 truro_twilight\n40 torquay_twilight\n40 plymouth_twilight\n40 weekend_exeter\n40 weekend_truro\n40 weekend_torquay\n40 weekend_plymouth\n40 night\n41 exeter_twilight\n41 truro_twilight\n41 torquay_twilight\n41 plymouth_twilight\n41 weekend_exeter\n41 weekend_truro\n41 weekend_torquay\n41 weekend_plymouth\n41 night\n42 exeter_twilight\n42 truro_twilight\n42 torquay_twilight\n42 plymouth_twilight\n42 weekend_exeter\n42 weekend_truro\n42 weekend_torquay\n42 weekend_plymouth\n42 night\n43 exeter_twilight\n43 truro_twilight\n43 torquay_twilight\n43 plymouth_twilight\n43 weekend_exeter\n43 weekend_truro\n43 weekend_torquay\n43 weekend_plymouth\n43 night\n44 exeter_twilight\n44 truro_twilight\n44 torquay_twilight\n44 plymouth_twilight\n44 weekend_exeter\n44 weekend_truro\n44 weekend_torquay\n44 weekend_plymouth\n44 night\n45 exeter_twilight\n45 truro_twilight\n45 torquay_twilight\n45 plymouth_twilight\n45 weekend_exeter\n45 weekend_truro\n45 weekend_torquay\n45 weekend_plymouth\n45 night\n46 exeter_twilight\n46 truro_twilight\n46 torquay_twilight\n46 plymouth_twilight\n46 weekend_exeter\n46 weekend_truro\n46 weekend_torquay\n46 weekend_plymouth\n46 night\n47 exeter_twilight\n47 truro_twilight\n47 torquay_twilight\n47 plymouth_twilight\n47weekend_exeter\n47 weekend_truro\n47 weekend_torquay\n47 weekend_plymouth\n47 night\n48 exeter_twilight\n48 truro_twilight\n48 torquay_twilight\n48 plymouth_twilight\n48 weekend_exeter\n48 weekend_truro\n48 weekend_torquay\n48 weekend_plymouth\n48 night\n49 exeter_twilight\n49 truro_twilight\n49 torquay_twilight\n49 plymouth_twilight\n49 weekend_exeter\n49 weekend_truro\n49 weekend_torquay\n49 weekend_plymouth\n49 night\n50 exeter_twilight\n50 truro_twilight\n50 torquay_twilight\n50 plymouth_twilight\n50 weekend_exeter\n50 weekend_truro\n50 weekend_torquay\n50 weekend_plymouth\n50 night\n51 exeter_twilight\n51 truro_twilight\n51 torquay_twilight\n51 plymouth_twilight\n51 weekend_exeter\n51 weekend_truro\n51 weekend_torquay\n51 weekend_plymouth\n51 night\n" + } + ], "source": [ "def get_workers_needed(needed):\n", " \"\"\"Extract to a list the needed workers for the optimal solution.\"\"\"\n", @@ -513,52 +930,74 @@ "\n", "def get_work_table(works):\n", " \"\"\"Build a timetable of the week as a dictionary from the model's optimal solution.\"\"\"\n", - " week_table = {week :{day: {shift: [] for shift in days_shifts[day]} for day in days} for week in weeks}\n", - " for week in weeks:\n", + " week_table = {week :{day: {shift: [] for shift in Shifts.GetShiftNames()} for day in days} for week in Shifts.weeks}\n", + " for week in Shifts.weeks:\n", " for worker in workers:\n", - " for day in days:\n", - " for shift in days_shifts[day]:\n", + " for day, shift in Shifts.GetAllDayShiftsAsNames():\n", " if works[worker.id, week, day, shift].value == 1:\n", " week_table[week][day][shift].append(worker.GetDetails())\n", " return week_table\n", "\n", "def get_worker_timetable(works):\n", - " timetable = { worker.name : { week : { day: \"\" for day in days } for week in weeks } for worker in workers }\n", + " timetable = { worker.name : { week : { day: \"\" for day in days } for week in Shifts.weeks } for worker in workers }\n", " for worker in workers:\n", - " for week in weeks:\n", - " for day in days:\n", - " for shift in days_shifts[day]:\n", + " for week in Shifts.weeks:\n", + " for day, shift in Shifts.GetAllDayShiftsAsNames():\n", " if works[worker.id, week, day, shift].value == 1:\n", " timetable[worker.name][week][day] = shift\n", " return timetable\n", "\n", - "def get_shift_summary(works):\n", - " timetable = { worker.GetDetails() : { shift : \"\" for shift in shifts } for worker in workers }\n", - " #timetable = { worker.GetDetails() : { shift : \"\" for shift in [\"truro_twighlight\"] } for worker in workers } \n", + "def get_worker_timetable_brief(works):\n", + " timetable = []\n", + " timetable.append(\"{:20}\".format(\"Worker\") + \"\".join([7*str(\"{}\".format(w))[-1:] for w in Shifts.weeks]))\n", " for worker in workers:\n", + " w = [\"{:20}\".format(worker.name)]\n", + " for week, day in Shifts.GetAllWeeksDays():\n", + " a = \"-\"\n", + " for shift in Shifts.GetShiftNamesByDay(day):\n", + " if works[worker.id, week, day, shift].value == 1:\n", + " a = shift[0]\n", + " w.append(a)\n", + " timetable.append(\"\".join(w))\n", + " \n", + " return \"\\n\".join(timetable)\n", "\n", - " for shift in shifts:\n", - " #for shift in [\"truro_twighlight\"]:\n", - " timetable[worker.GetDetails()][shift] = [works[worker.id, week, day, shift] for week in weeks for day in days].count(1)\n", - " return timetable \n", + "def get_shift_summary(works):\n", + " timetable = { worker.GetDetails() : { shift[0] : \"\" for shift in Shifts.GetShiftNames() } for worker in workers }\n", + " #timetable = { worker.GetDetails() : { shift : \"\" for shift in [\"truro_twilight\"] } for worker in workers } \n", + " t = []\n", + " for worker in workers:\n", + " l = [worker.GetDetails()+\": \"]\n", + "\n", + " for shift in Shifts.GetShiftNames():\n", + " #for shift in [\"truro_twilight\"]:\n", + " c = [works[worker.id, week, day, shift] for week in Shifts.weeks for day in days].count(1)\n", + " if c > 0:\n", + " l.append(\"{} ({})\".format(shift,c))\n", + " print(worker.id, shift)\n", + " timetable[worker.GetDetails()][shift[0]] = c\n", + " t.append(\",\".join(l))\n", + " return \"\\n\".join(t) \n", "\n", "def get_no_preference(no_pref):\n", " \"\"\"Extract to a list the workers not satisfied with their weekend preference.\"\"\"\n", " return [worker.id for worker in workers if no_pref[worker.id].value == 1]\n", "\n", "def get_night_blocks(nights):\n", - " timetable = { worker.name : { week : { block: \"\" for block in night_blocks } for week in weeks } for worker in workers }\n", + " timetable = { worker.name : { week : { block: \"\" for block in night_blocks } for week in Shifts.weeks } for worker in workers }\n", " for worker in workers:\n", - " for week in weeks:\n", + " for week in Shifts.weeks:\n", " for block in night_blocks:\n", " if nights[worker.id, week, block].value == 1:\n", " timetable[worker.name][week][block] = \"true\"\n", " return timetable\n", "\n", "\n", + "\n", "workers_needed = get_workers_needed(model.needed) # dict with the optimal timetable\n", "week_table = get_work_table(model.works) # list with the required workers\n", "worker_timetable = get_worker_timetable(model.works)\n", + "worker_timetable_brief = get_worker_timetable_brief(model.works)\n", "workers_no_pref = get_no_preference(model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun)\n", "worker_night_block = get_night_blocks(model.nights)\n", "worker_shift_summary = get_shift_summary(model.works)" @@ -566,53 +1005,93 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 116, "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": "{'1 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '2 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '3 t 4': {'night': 9, 'truro_twighlight': 6, 'plymouth_twighlight': 0},\n '4 t 4': {'night': 9, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '5 t 4': {'night': 11, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '6 t 4': {'night': 11, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '7 t 4': {'night': 9, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '8 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '9 t 4': {'night': 9, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '10 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '11 t 4': {'night': 11, 'truro_twighlight': 6, 'plymouth_twighlight': 0},\n '12 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '13 t 4': {'night': 11, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '14 t 4': {'night': 8, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '15 t 4': {'night': 8, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '16 t 4': {'night': 8, 'truro_twighlight': 6, 'plymouth_twighlight': 0},\n '17 t 4': {'night': 11, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '18 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '19 t 4': {'night': 10, 'truro_twighlight': 7, 'plymouth_twighlight': 0},\n '21 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '22 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '23 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '24 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '25 p 4': {'night': 9, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '26 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '27 p 4': {'night': 9, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '28 p 4': {'night': 9, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '29 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '30 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '31 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '32 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '33 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '34 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '35 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '36 p 4': {'night': 9, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '37 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '38 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '39 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '40 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '41 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '42 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '43 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '44 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '45 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '46 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '47 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '48 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '49 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '50 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '51 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '52 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '53 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '54 p 4': {'night': 10, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '55 p 4': {'night': 11, 'truro_twighlight': 0, 'plymouth_twighlight': 3},\n '56 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 4},\n '57 p 4': {'night': 8, 'truro_twighlight': 0, 'plymouth_twighlight': 4}}" - }, - "metadata": {}, - "execution_count": 12 - } - ], + "outputs": [], "source": [ - "#worker_timetable\n", - "worker_shift_summary" + "def ExportRotaToCSV(works):\n", + " with open(\"rota.csv\", 'w', newline='') as f:\n", + " wr = csv.writer(f, quoting=csv.QUOTE_ALL)\n", + " l = [\"Name\"]\n", + " l.extend([worker.name for worker in workers])\n", + " wr.writerow(l)\n", + "\n", + " l2 = [\"Site\"]\n", + " l2.extend([worker.site for worker in workers])\n", + " wr.writerow(l2)\n", + "\n", + " l3 = [\"Grade\"]\n", + " l3.extend([worker.grade for worker in workers])\n", + " wr.writerow(l3)\n", + "\n", + " l4 = [\"FTE\"]\n", + " l4.extend([worker.fte for worker in workers])\n", + " wr.writerow(l4)\n", + "\n", + "\n", + " for week, day in Shifts.GetAllWeeksDays():\n", + " d = [\"Week {} Day {}\".format(week, day)]\n", + "\n", + " for worker in workers:\n", + " i = \"\"\n", + " for shift in Shifts.GetShiftNamesByDay(day):\n", + " if works[worker.id, week, day, shift].value == 1:\n", + " i = shift\n", + "\n", + " d.append(i)\n", + " wr.writerow(d)\n", + "\n", + "ExportRotaToCSV(model.works)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 79, "metadata": {}, "outputs": [ { - "output_type": "execute_result", - "data": { - "text/plain": "[]" - }, - "metadata": {}, - "execution_count": 13 + "output_type": "stream", + "name": "stdout", + "text": "Worker 11111112222222333333344444445555555666666677777778888888999999900000001111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\nWijhdan Abusrewil e----w----e---e------e-----w---e----e---w-----e----e------------e---------e---e-----nnnn----------e-----w----e--nnnn------e-------e---------nnnn--w-----------e---e----------w---e---w\nSeren Peters ---p---p-----------w------w--------p--------p---w----p--nnnn--------w-nnnn-------------p----p------------------w----nnn---------p--------nnn--p---w------w---------------------------w\nHannah Lewis -----w---p---w--------p--nnn--p---w----------------p---w-----w-nnnn--------w------w----------p-nnn---p--------w----------p-------------------p-----p-----------------nnn--------------\nChristian Greer -----w------w-----------t------t---t-----------------nnn--------------t---t-----t-------t-----t--------w------------t----t----nnnn----------------w----nnn--t----nnnn--------w----t---\nNicolas Dziad nnnn---t--------------t---w------w-----t----t-----------------------w---t-nnn---t----t-------------t---w----t---------w-----------t-----------t-nnn---t-----t-nnn---t------t--------w-\nMichal Tomek ----e----e-nnn-------nnnn----------e--------e-----e-------e---w----e--------------w---e---w------w-----w-nnnn---e-------e--nnn--e-------e---e----w-------w-------e---e----e----e---e--\nAlex Wood -------nnnn--w----e---e------e---w-----nnn-----w---------e-----e---nnn--e-----------e---e-----e----------e-----w---e---e---e---e---w------w------------e---e----w---e----e---------nnn\nAlexander Sanchez- Cabello-p-----------------w------w-nnnn----p---w-nnnn-----------p---w-------w---p-----p---------------p-------w---p-nnn----------------------p------------nnnn-------p----------p----------w-\nMax Ireland -t--nnn------------w-------w-------nnnn-----t---w-----w-----nnn------------w---t-----------t---t------------t---t-----------------t----t-----t---------t------------t----t--nnn-------\nVilim Kalamar --t---w----t------nnn--t----t---nnn-----------------------t-------t----t------------t-----------w-----t----t----t------------w-----w-nnnn---t-------t-----nnnn---------w-----------t--\nSalma Aslam -------------------------------------------------------------------------------------------e------------------------------------------------------------------------------------------\nAdeola Omotade ----------------------------------------------------------------------------------------------------------------------------------------------------------------w-------p-------------\nAmina Odeh ------w---p--------------p------------p---p---------p-----p-nnn-----------nnn-------------w---p--------w-nnnn----------p----w-------w-----w----p----------nnnn---------------w--------\nCharles Finan ------w------w-----------------------t---w-----------nnn--t------t---w--------t-----t---nnn----------t-----t------t-----t--nnn---t------t---nnnn---t------------------w---------t-----\nAlan Eccles -------t------t-------------------w---------------------t---t--nnnn----t---------------------t----t---nnn----t---t-----------w------w-----------------t----t----w-------nnnn----------\nSarath Vennam ------------w---t-nnn-----------t------nnn------w------w----t---------t------------w---t----t-------t-nnn--------t--------t----t-------t---w----nnn--t-----t----w---------t-----------\nPaul Ward t----------------t---nnnn-----t-----t----w---t---t---t-------w-----------t----------nnnn--w---t---------w-----------t--nnnn---t-----w----t--------w-----------nnn--t-----t--------t---\nSayed Alqarooni -e------e--------e--------w-------w------w----nnn---e---e---e----e---w---e-------------e----e--nnn--e---------w-----e-------w----e----e--------e-----e----e----w-nnnn----------nnnn---\nJean Sukumar -----------nnn---p-------------------------------p------nnnn-------p-------------nnn------------------------------------p---w----p-----p---w------------w------w----------------------\nJenn Fong nnnn-------p---p-----p-------p----------------p--nnnn------p------p---------w-------p------p-----w------------------nnn-------p---nnn-------------w------w-----w-------w-----w--------\nChong Ng ----------t---------w--------t---------t-------w-nnnn--w-----w-----nnn--t------------t----------------t---------------w---t---t-------t--nnn---------t-nnn----t---------t-------------\nMatthew Thorley ----nnn----------t---t---t-------w------w---------------------w----------t-------nnn--t----nnnn-----t------------------t----w-----nnn------w--------t--------t-------t-----t---t------\nJenna Millington --------t-----nnnn---------w----------t-------------------------------nnnn--------------t----t---w---t--------w-----------------t-------t---------------------t------t--------w-------\nChi Leung ---t---nnnn---t---t------------t---t----w---------------t------------------w------w--------t---t--nnnn----t-------t-----t--------t-----------------t----w------w--------nnnn---t------\nTom Welsh --e--------e---e----w-------e---e--nnnn------e---nnnn--w--------------e----------e------nnn----e------e---e------e---w-------w-------e-----w----nnn---e------e-----e---w---e----e---w-\nRebecca Murphy -----------nnn--e-------e-------nnn----e---e-----e----w----e--------w------w---e-----e---w---------e-------e----nnnn-----e----e-----w----e----e----e----w---e-----------e-----w----nnn\nDelilah Trimmer -------------------------e------------e---e-----w----nnn--------------------w----------------e-------e------e---------w----------------e--------e---------------------w-----e--nnnn---\nSophie McGlade -----w--------------w------w------w-------------w--------------nnnn----p------p--nnn-----w--------p---p---p---------nnn----p-------------p-----------p--------nnn-----w---p----p----w-\nS Ghauri ----nnn-----w----------p----p--------p---w-----w-------w----p------nnn------w-----w-------------------nnn---p-----p---w--------------nnnn-------p---------p---------p------p----------\nKhalil Madbak ------w------w-------------w----------------------------p------p----w------w-------w----p--nnnn-----p--------nnn---p---nnnn----p---w--------nnnn-------p---p----------------p---------\nNatasha Hougham --------------p---------------------------------------w-------w--------------p---p--------------w------------nnn----------p----------nnnn--w----------------------p----w---------p----\nRichard Chaytor --------------------w----nnn---p----------nnnn----p---w-------w-----------------p-----p-nnn------w------w----p---p----w------w-----w----p---p---------p--------------nnn--------p-----\nSafi Rahman ----------------------------------------------------------------------p----------------------------------------w----------------------------------------------------------------------\nGeorge Edwards ----p---p-----nnnn-------------------------p---w--------nnnn---------w----p--------------w------w-------w-------p----w-----nnn----p-------w------w-----------p---p---p-------------nnn\nNeil McIntyre t--------t-----t------t---------nnn-----------------t-----------t------------t---t-----t----t----w-------------w-----w-------------w-----nnn---t----------nnnn---t----------nnn--t---w\nMo Babsail ----t---t-----nnnn----------t----------nnn----t---t---------------t---------w-------------w-----w---------t--------------------------t------t----w-nnnn---t--------t------t-nnn-----w-\nPaul Jenkins --t-----------------------w-nnnn----t-----t--------t-----t-----t---t---------------w-----w-----nnn-------t----w-nnnn------------t-nnn----t----t---------w---------t---------t---------\nRoss Kruger ----t----------t-----t-------t------------t---t---t------------t-----w------w------------w--------nnnn---t---t---------t----w-nnnn--------w----t---nnnn--w---t-------nnn----t----t----\nAmoolya Mannava ---------t---w-----------t-----------t---------w---t-------t-----t--------t---t-----nnnn----------nnnn-------------t-----t------------t------t---w--------t-------t---w------w-nnnn--w\nTom Davies --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\nTania King Mohammed ------w-----------nnn--------------------------------------------------e--------e---------------w-----------------e--------------------------e------e--nnn------------------------e---\nJin Virdi ---e---e----w------w---e------e------e----nnnn-------e-------w----e---nnnn---e-----w--------------------------------------------------------------------------------------------------\nHuub Witt ------------w---p---------------p------------p------------------p-------p----nnnn--w---------------p----------------p---------nnnn--w------------w------w---p------p----nnnn--w------w\nMirna Long p-----------------p--nnnn--------w-------w--------------------------------------------------------------------------------------------------------------------------------------------\nJon Stokes ----------------------------------------w-----------------------------------------------------------------------------------------------------------------------------------------p---\nKaris McFeely --p---------------------p--------w-----p------nnn-----------nnn--p-----------nnnn----p----w--------------p----w------w-nnnn--w-------p--------------p-----------w-----w-------w----p--\nHarriet Conley ---------------------------------------------t-------------------------------------------------------------------------------------------------------------------------w--------------\nJon Skinner nnnn-------t----t-------------t-------t-------nnn----t-----t-----------------nnnn------------------t----w----------t-------t---t----------w-----t--------w-----w-nnnn---------w----t--\nGana Kugathasan ---t--------------t----t--------t--nnnn----t-----t------------------w-----nnn-----w---------------------------------------------------------------------------------------------------\nJack Looker -t---w----t--------w----t---nnnn----------------------w-------w----t-----------t------t----nnnn---t------nnnn--w-----w-----t---------t----------t------t---------t------t-------t-----\nPete Abernethy -------nnnn---------w----nnn------w--------t--------t----t------t------------t---t----------------------------------------------------------------------------------------------------\n1 e 2: ,exeter_twilight (18),weekend_exeter (7),night (12)\n2 p 2: ,plymouth_twilight (9),weekend_plymouth (8),night (14)\n3 p 2: ,plymouth_twilight (9),weekend_plymouth (8),night (13)\n4 t 2: ,torquay_twilight (12),weekend_torquay (5),night (14)\n5 t 2: ,truro_twilight (15),weekend_truro (6),night (13)\n6 e 2: ,exeter_twilight (18),weekend_exeter (7),night (14)\n7 e 2: ,exeter_twilight (18),weekend_exeter (7),night (13)\n8 p 2: ,plymouth_twilight (10),weekend_plymouth (7),night (15)\n9 t 2: ,torquay_twilight (13),weekend_torquay (5),night (13)\n10 t 2: ,truro_twilight (14),weekend_truro (5),night (14)\n11 e 3: ,exeter_twilight (1)\n12 p 3: ,plymouth_twilight (1),weekend_plymouth (1)\n13 p 3: ,plymouth_twilight (9),weekend_plymouth (7),night (14)\n14 t 3: ,torquay_twilight (13),weekend_torquay (5),night (13)\n15 t 3: ,torquay_twilight (11),weekend_torquay (4),night (11)\n16 t 3: ,truro_twilight (14),weekend_truro (6),night (12)\n17 t 3: ,truro_twilight (15),weekend_truro (6),night (15)\n18 e 3: ,exeter_twilight (17),weekend_exeter (7),night (14)\n19 p 3: ,plymouth_twilight (6),weekend_plymouth (4),night (10)\n20 p 3: ,plymouth_twilight (10),weekend_plymouth (7),night (14)\n21 t 3: ,torquay_twilight (12),weekend_torquay (5),night (13)\n22 t 3: ,torquay_twilight (12),weekend_torquay (5),night (13)\n23 t 3: ,truro_twilight (9),weekend_truro (4),night (8)\n24 t 3: ,truro_twilight (14),weekend_truro (5),night (12)\n25 e 4: ,exeter_twilight (18),weekend_exeter (7),night (14)\n26 e 4: ,exeter_twilight (17),weekend_exeter (7),night (13)\n27 e 4: ,exeter_twilight (9),weekend_exeter (4),night (7)\n28 p 4: ,plymouth_twilight (10),weekend_plymouth (8),night (13)\n29 p 4: ,plymouth_twilight (10),weekend_plymouth (7),night (13)\n30 p 4: ,plymouth_twilight (9),weekend_plymouth (7),night (15)\n31 p 4: ,plymouth_twilight (6),weekend_plymouth (5),night (7)\n32 p 4: ,plymouth_twilight (10),weekend_plymouth (8),night (13)\n33 p 4: ,plymouth_twilight (1),weekend_plymouth (1)\n34 p 4: ,plymouth_twilight (9),weekend_plymouth (8),night (14)\n35 t 4: ,torquay_twilight (13),weekend_torquay (5),night (13)\n36 t 4: ,torquay_twilight (12),weekend_torquay (5),night (14)\n37 t 4: ,torquay_twilight (13),weekend_torquay (5),night (14)\n38 t 4: ,truro_twilight (15),weekend_truro (6),night (15)\n39 t 4: ,truro_twilight (14),weekend_truro (6),night (12)\n40 e 5: \n41 e 5: ,exeter_twilight (6),weekend_exeter (2),night (6)\n42 e 5: ,exeter_twilight (8),weekend_exeter (4),night (8)\n43 p 5: ,plymouth_twilight (9),weekend_plymouth (7),night (12)\n44 p 5: ,plymouth_twilight (2),weekend_plymouth (2),night (4)\n45 p 5: ,plymouth_twilight (1),weekend_plymouth (1)\n46 p 5: ,plymouth_twilight (9),weekend_plymouth (8),night (14)\n47 t 5: ,torquay_twilight (1),weekend_torquay (1)\n48 t 5: ,torquay_twilight (12),weekend_torquay (5),night (15)\n49 t 5: ,torquay_twilight (6),weekend_torquay (2),night (7)\n50 t 5: ,truro_twilight (14),weekend_truro (6),night (12)\n51 t 5: ,truro_twilight (6),weekend_truro (2),night (7)\n" } ], "source": [ - "workers_needed" + "\n", + "print(worker_timetable_brief)\n", + "print(worker_shift_summary)" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 80, "metadata": {}, "outputs": [ { "output_type": "execute_result", "data": { - "text/plain": " t 4'],\n 'plymouth_twighlight': ['43 p 4']},\n 'Wed': {'night': ['11 t 4', '16 t 4', '46 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['24 p 4']},\n 'Thu': {'night': ['11 t 4', '16 t 4', '46 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['40 p 4']},\n 'Fri': {'night': ['12 t 4', '21 p 4', '43 p 4'],\n 'truro_twighlight': ['4 t 4'],\n 'plymouth_twighlight': ['31 p 4']},\n 'Sat': {'night': ['12 t 4', '21 p 4', '43 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['12 t 4', '21 p 4', '43 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 4: {'Mon': {'night': ['8 t 4', '37 p 4', '41 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['24 p 4']},\n 'Tue': {'night': ['8 t 4', '37 p 4', '41 p 4'],\n 'truro_twighlight': ['7 t 4'],\n 'plymouth_twighlight': ['46 p 4']},\n 'Wed': {'night': ['8 t 4', '37 p 4', '41 p 4'],\n 'truro_twighlight': ['4 t 4'],\n 'plymouth_twighlight': ['35 p 4']},\n 'Thu': {'night': ['8 t 4', '37 p 4', '41 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['54 p 4']},\n 'Fri': {'night': ['32 p 4', '42 p 4', '48 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['29 p 4']},\n 'Sat': {'night': ['32 p 4', '42 p 4', '48 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['32 p 4', '42 p 4', '48 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 5: {'Mon': {'night': ['12 t 4', '35 p 4', '44 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['56 p 4']},\n 'Tue': {'night': ['12 t 4', '35 p 4', '44 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['34 p 4']},\n 'Wed': {'night': ['12 t 4', '35 p 4', '44 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['41 p 4']},\n 'Thu': {'night': ['12 t 4', '35 p 4', '44 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['51 p 4']},\n 'Fri': {'night': ['34 p 4', '45 p 4', '54 p 4'],\n 'truro_twighlight': ['3 t 4'],\n 'plymouth_twighlight': ['50 p 4']},\n 'Sat': {'night': ['34 p 4', '45 p 4', '54 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['34 p 4', '45 p 4', '54 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 6: {'Mon': {'night': ['32 p 4', '37 p 4', '45 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['26 p 4']},\n 'Tue': {'night': ['32 p 4', '37 p 4', '45 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['36 p 4']},\n 'Wed': {'night': ['32 p 4', '37 p 4', '45 p 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['39 p 4']},\n 'Thu': {'night': ['32 p 4', '37 p 4', '45 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['42 p 4']},\n 'Fri': {'night': ['9 t 4', '13 t 4', '27 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['49 p 4']},\n 'Sat': {'night': ['9 t 4', '13 t 4', '27 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['9 t 4', '13 t 4', '27 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 7: {'Mon': {'night': ['13 t 4', '23 p 4', '24 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['33 p 4']},\n 'Tue': {'night': ['13 t 4', '23 p 4', '24 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['37 p 4']},\n 'Wed': {'night': ['13 t 4', '23 p 4', '24 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['56 p 4']},\n 'Thu': {'night': ['13 t 4', '23 p 4', '24 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['53 p 4']},\n 'Fri': {'night': ['10 t 4', '28 p 4', '30 p 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['39 p 4']},\n 'Sat': {'night': ['10 t 4', '28 p 4', '30 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['10 t 4', '28 p 4', '30 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 8: {'Mon': {'night': ['45 p 4', '51 p 4', '56 p 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['26 p 4']},\n 'Tue': {'night': ['45 p 4', '51 p 4', '56 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['23 p 4']},\n 'Wed': {'night': ['45 p 4', '51 p 4', '56 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['28 p 4']},\n 'Thu': {'night': ['45 p 4', '51 p 4', '56 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['32 p 4']},\n 'Fri': {'night': ['18 t 4', '24 p 4', '37 p 4'],\n 'truro_twighlight': ['3 t 4'],\n 'plymouth_twighlight': ['42 p 4']},\n 'Sat': {'night': ['18 t 4', '24 p 4', '37 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['18 t 4', '24 p 4', '37 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 9: {'Mon': {'night': ['16 t 4', '22 p 4', '54 p 4'],\n 'truro_twighlight': ['3 t 4'],\n 'plymouth_twighlight': ['32 p 4']},\n 'Tue': {'night': ['16 t 4', '22 p 4', '54 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['25 p 4']},\n 'Wed': {'night': ['16 t 4', '22 p 4', '54 p 4'],\n 'truro_twighlight': ['5 t 4'],\n 'plymouth_twighlight': ['43 p 4']},\n 'Thu': {'night': ['16 t 4', '22 p 4', '54 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['52 p 4']},\n 'Fri': {'night': ['4 t 4', '23 p 4', '27 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['57 p 4']},\n 'Sat': {'night': ['4 t 4', '23 p 4', '27 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['4 t 4', '23 p 4', '27 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 10: {'Mon': {'night': ['31 p 4', '48 p 4', '55 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['42 p 4']},\n 'Tue': {'night': ['31 p 4', '48 p 4', '55 p 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['25 p 4']},\n 'Wed': {'night': ['31 p 4', '48 p 4', '55 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['27 p 4']},\n 'Thu': {'night': ['31 p 4', '48 p 4', '55 p 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['36 p 4']},\n 'Fri': {'night': ['8 t 4', '17 t 4', '47 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['34 p 4']},\n 'Sat': {'night': ['8 t 4', '17 t 4', '47 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['8 t 4', '17 t 4', '47 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 11: {'Mon': {'night': ['5 t 4', '14 t 4', '43 p 4'],\n 'truro_twighlight': ['7 t 4'],\n 'plymouth_twighlight': ['27 p 4']},\n 'Tue': {'night': ['5 t 4', '14 t 4', '43 p 4'],\n 'truro_twighlight': ['4 t 4'],\n 'plymouth_twighlight': ['31 p 4']},\n 'Wed': {'night': ['5 t 4', '14 t 4', '43 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['52 p 4']},\n 'Thu': {'night': ['5 t 4', '14 t 4', '43 p 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['57 p 4']},\n 'Fri': {'night': ['3 t 4', '26 p 4', '28 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['23 p 4']},\n 'Sat': {'night': ['3 t 4', '26 p 4', '28 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['3 t 4', '26 p 4', '28 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 12: {'Mon': {'night': ['5 t 4', '26 p 4', '49 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['36 p 4']},\n 'Tue': {'night': ['5 t 4', '26 p 4', '49 p 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['41 p 4']},\n 'Wed': {'night': ['5 t 4', '26 p 4', '49 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['30 p 4']},\n 'Thu': {'night': ['5 t 4', '26 p 4', '49 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['34 p 4']},\n 'Fri': {'night': ['11 t 4', '39 p 4', '40 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['48 p 4']},\n 'Sat': {'night': ['11 t 4', '39 p 4', '40 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['11 t 4', '39 p 4', '40 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 13: {'Mon': {'night': ['38 p 4', '49 p 4', '51 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['52 p 4']},\n 'Tue': {'night': ['38 p 4', '49 p 4', '51 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['21 p 4']},\n 'Wed': {'night': ['38 p 4', '49 p 4', '51 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['40 p 4']},\n 'Thu': {'night': ['38 p 4', '49 p 4', '51 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['44 p 4']},\n 'Fri': {'night': ['4 t 4', '7 t 4', '30 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['54 p 4']},\n 'Sat': {'night': ['4 t 4', '7 t 4', '30 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['4 t 4', '7 t 4', '30 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 14: {'Mon': {'night': ['1 t 4', '22 p 4', '40 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['55 p 4']},\n 'Tue': {'night': ['1 t 4', '22 p 4', '40 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['37 p 4']},\n 'Wed': {'night': ['1 t 4', '22 p 4', '40 p 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['30 p 4']},\n 'Thu': {'night': ['1 t 4', '22 p 4', '40 p 4'],\n 'truro_twighlight': ['4 t 4'],\n 'plymouth_twighlight': ['33 p 4']},\n 'Fri': {'night': ['3 t 4', '7 t 4', '29 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['43 p 4']},\n 'Sat': {'night': ['3 t 4', '7 t 4', '29 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['3 t 4', '7 t 4', '29 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 15: {'Mon': {'night': ['17 t 4', '42 p 4', '53 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['21 p 4']},\n 'Tue': {'night': ['17 t 4', '42 p 4', '53 p 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['45 p 4']},\n 'Wed': {'night': ['17 t 4', '42 p 4', '53 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['44 p 4']},\n 'Thu': {'night': ['17 t 4', '42 p 4', '53 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['46 p 4']},\n 'Fri': {'night': ['1 t 4', '3 t 4', '31 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['50 p 4']},\n 'Sat': {'night': ['1 t 4', '3 t 4', '31 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['1 t 4', '3 t 4', '31 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 16: {'Mon': {'night': ['2 t 4', '15 t 4', '57 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['25 p 4']},\n 'Tue': {'night': ['2 t 4', '15 t 4', '57 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['24 p 4']},\n 'Wed': {'night': ['2 t 4', '15 t 4', '57 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['28 p 4']},\n 'Thu': {'night': ['2 t 4', '15 t 4', '57 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['50 p 4']},\n 'Fri': {'night': ['7 t 4', '19 t 4', '36 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['27 p 4']},\n 'Sat': {'night': ['7 t 4', '19 t 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['7 t 4', '19 t 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 17: {'Mon': {'night': ['6 t 4', '15 t 4', '34 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['38 p 4']},\n 'Tue': {'night': ['6 t 4', '15 t 4', '34 p 4'],\n 'truro_twighlight': ['5 t 4'],\n 'plymouth_twighlight': ['23 p 4']},\n 'Wed': {'night': ['6 t 4', '15 t 4', '34 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['45 p 4']},\n 'Thu': {'night': ['6 t 4', '15 t 4', '34 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['55 p 4']},\n 'Fri': {'night': ['4 t 4', '25 p 4', '36 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['33 p 4']},\n 'Sat': {'night': ['4 t 4', '25 p 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['4 t 4', '25 p 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 18: {'Mon': {'night': ['6 t 4', '10 t 4', '42 p 4'],\n 'truro_twighlight': ['7 t 4'],\n 'plymouth_twighlight': ['40 p 4']},\n 'Tue': {'night': ['6 t 4', '10 t 4', '42 p 4'],\n 'truro_twighlight': ['5 t 4'],\n 'plymouth_twighlight': ['35 p 4']},\n 'Wed': {'night': ['6 t 4', '10 t 4', '42 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['38 p 4']},\n 'Thu': {'night': ['6 t 4', '10 t 4', '42 p 4'],\n 'truro_twighlight': ['11 t 4'],\n 'plymouth_twighlight': ['23 p 4']},\n 'Fri': {'night': ['18 t 4', '28 p 4', '35 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['26 p 4']},\n 'Sat': {'night': ['18 t 4', '28 p 4', '35 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['18 t 4', '28 p 4', '35 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 19: {'Mon': {'night': ['13 t 4', '18 t 4', '19 t 4'],\n 'truro_twighlight': ['10 t 4'],\n 'plymouth_twighlight': ['53 p 4']},\n 'Tue': {'night': ['13 t 4', '18 t 4', '19 t 4'],\n 'truro_twighlight': ['4 t 4'],\n 'plymouth_twighlight': ['47 p 4']},\n 'Wed': {'night': ['13 t 4', '18 t 4', '19 t 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['29 p 4']},\n 'Thu': {'night': ['13 t 4', '18 t 4', '19 t 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['54 p 4']},\n 'Fri': {'night': ['5 t 4', '12 t 4', '25 p 4'],\n 'truro_twighlight': ['3 t 4'],\n 'plymouth_twighlight': ['51 p 4']},\n 'Sat': {'night': ['5 t 4', '12 t 4', '25 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['5 t 4', '12 t 4', '25 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 20: {'Mon': {'night': ['38 p 4', '44 p 4', '53 p 4'],\n 'truro_twighlight': ['9 t 4'],\n 'plymouth_twighlight': ['22 p 4']},\n 'Tue': {'night': ['38 p 4', '44 p 4', '53 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['39 p 4']},\n 'Wed': {'night': ['38 p 4', '44 p 4', '53 p 4'],\n 'truro_twighlight': ['7 t 4'],\n 'plymouth_twighlight': ['34 p 4']},\n 'Thu': {'night': ['38 p 4', '44 p 4', '53 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['29 p 4']},\n 'Fri': {'night': ['6 t 4', '8 t 4', '31 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['32 p 4']},\n 'Sat': {'night': ['6 t 4', '8 t 4', '31 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['6 t 4', '8 t 4', '31 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 21: {'Mon': {'night': ['14 t 4', '21 p 4', '23 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['57 p 4']},\n 'Tue': {'night': ['14 t 4', '21 p 4', '23 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['38 p 4']},\n 'Wed': {'night': ['14 t 4', '21 p 4', '23 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['53 p 4']},\n 'Thu': {'night': ['14 t 4', '21 p 4', '23 p 4'],\n 'truro_twighlight': ['5 t 4'],\n 'plymouth_twighlight': ['45 p 4']},\n 'Fri': {'night': ['9 t 4', '32 p 4', '43 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['56 p 4']},\n 'Sat': {'night': ['9 t 4', '32 p 4', '43 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['9 t 4', '32 p 4', '43 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 22: {'Mon': {'night': ['17 t 4', '55 p 4', '57 p 4'],\n 'truro_twighlight': ['1 t 4'],\n 'plymouth_twighlight': ['41 p 4']},\n 'Tue': {'night': ['17 t 4', '55 p 4', '57 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['30 p 4']},\n 'Wed': {'night': ['17 t 4', '55 p 4', '57 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['45 p 4']},\n 'Thu': {'night': ['17 t 4', '55 p 4', '57 p 4'],\n 'truro_twighlight': ['18 t 4'],\n 'plymouth_twighlight': ['29 p 4']},\n 'Fri': {'night': ['19 t 4', '27 p 4', '40 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['35 p 4']},\n 'Sat': {'night': ['19 t 4', '27 p 4', '40 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['19 t 4', '27 p 4', '40 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 23: {'Mon': {'night': ['39 p 4', '48 p 4', '50 p 4'],\n 'truro_twighlight': ['6 t 4'],\n 'plymouth_twighlight': ['35 p 4']},\n 'Tue': {'night': ['39 p 4', '48 p 4', '50 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['46 p 4']},\n 'Wed': {'night': ['39 p 4', '48 p 4', '50 p 4'],\n 'truro_twighlight': ['7 t 4'],\n 'plymouth_twighlight': ['31 p 4']},\n 'Thu': {'night': ['39 p 4', '48 p 4', '50 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['24 p 4']},\n 'Fri': {'night': ['33 p 4', '52 p 4', '55 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['26 p 4']},\n 'Sat': {'night': ['33 p 4', '52 p 4', '55 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['33 p 4', '52 p 4', '55 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 24: {'Mon': {'night': ['35 p 4', '46 p 4', '52 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['48 p 4']},\n 'Tue': {'night': ['35 p 4', '46 p 4', '52 p 4'],\n 'truro_twighlight': ['5 t 4'],\n 'plymouth_twighlight': ['53 p 4']},\n 'Wed': {'night': ['35 p 4', '46 p 4', '52 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['55 p 4']},\n 'Thu': {'night': ['35 p 4', '46 p 4', '52 p 4'],\n 'truro_twighlight': ['15 t 4'],\n 'plymouth_twighlight': ['47 p 4']},\n 'Fri': {'night': ['9 t 4', '25 p 4', '41 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['57 p 4']},\n 'Sat': {'night': ['9 t 4', '25 p 4', '41 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['9 t 4', '25 p 4', '41 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 25: {'Mon': {'night': ['29 p 4', '33 p 4', '39 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['30 p 4']},\n 'Tue': {'night': ['29 p 4', '33 p 4', '39 p 4'],\n 'truro_twighlight': ['14 t 4'],\n 'plymouth_twighlight': ['48 p 4']},\n 'Wed': {'night': ['29 p 4', '33 p 4', '39 p 4'],\n 'truro_twighlight': ['16 t 4'],\n 'plymouth_twighlight': ['51 p 4']},\n 'Thu': {'night': ['29 p 4', '33 p 4', '39 p 4'],\n 'truro_twighlight': ['8 t 4'],\n 'plymouth_twighlight': ['22 p 4']},\n 'Fri': {'night': ['2 t 4', '10 t 4', '36 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['47 p 4']},\n 'Sat': {'night': ['2 t 4', '10 t 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['2 t 4', '10 t 4', '36 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}},\n 26: {'Mon': {'night': ['47 p 4', '52 p 4', '56 p 4'],\n 'truro_twighlight': ['12 t 4'],\n 'plymouth_twighlight': ['39 p 4']},\n 'Tue': {'night': ['47 p 4', '52 p 4', '56 p 4'],\n 'truro_twighlight': ['19 t 4'],\n 'plymouth_twighlight': ['25 p 4']},\n 'Wed': {'night': ['47 p 4', '52 p 4', '56 p 4'],\n 'truro_twighlight': ['2 t 4'],\n 'plymouth_twighlight': ['27 p 4']},\n 'Thu': {'night': ['47 p 4', '52 p 4', '56 p 4'],\n 'truro_twighlight': ['17 t 4'],\n 'plymouth_twighlight': ['44 p 4']},\n 'Fri': {'night': ['2 t 4', '24 p 4', '41 p 4'],\n 'truro_twighlight': ['13 t 4'],\n 'plymouth_twighlight': ['49 p 4']},\n 'Sat': {'night': ['2 t 4', '24 p 4', '41 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []},\n 'Sun': {'night': ['2 t 4', '24 p 4', '41 p 4'],\n 'truro_twighlight': [],\n 'plymouth_twighlight': []}}}" + "text/plain": "[('Mon', 'exeter_twilight'),\n ('Tue', 'exeter_twilight'),\n ('Wed', 'exeter_twilight'),\n ('Thu', 'exeter_twilight'),\n ('Fri', 'exeter_twilight'),\n ('Mon', 'truro_twilight'),\n ('Tue', 'truro_twilight'),\n ('Wed', 'truro_twilight'),\n ('Thu', 'truro_twilight'),\n ('Fri', 'truro_twilight'),\n ('Mon', 'torquay_twilight'),\n ('Tue', 'torquay_twilight'),\n ('Wed', 'torquay_twilight'),\n ('Thu', 'torquay_twilight'),\n ('Fri', 'torquay_twilight'),\n ('Mon', 'plymouth_twilight'),\n ('Tue', 'plymouth_twilight'),\n ('Wed', 'plymouth_twilight'),\n ('Thu', 'plymouth_twilight'),\n ('Fri', 'plymouth_twilight'),\n ('Sat', 'weekend_exeter'),\n ('Sun', 'weekend_exeter'),\n ('Sat', 'weekend_truro'),\n ('Sun', 'weekend_truro'),\n ('Sat', 'weekend_torquay'),\n ('Sun', 'weekend_torquay'),\n ('Sat', 'weekend_plymouth'),\n ('Sun', 'weekend_plymouth'),\n ('Mon', 'night'),\n ('Tue', 'night'),\n ('Wed', 'night'),\n ('Thu', 'night'),\n ('Fri', 'night'),\n ('Sat', 'night'),\n ('Sun', 'night')]" }, "metadata": {}, - "execution_count": 14 + "execution_count": 80 + } + ], + "source": [ + "#workers_needed\n", + "Shifts.GetAllDayShiftsAsNames()" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "4']},\n 'Wed': {'exeter_twilight': ['41 e 5'],\n 'truro_twilight': ['24 t 3'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '26 e 4', '37 t 4']},\n 'Thu': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['48 t 5'],\n 'plymouth_twilight': ['30 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '26 e 4', '37 t 4']},\n 'Fri': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['4 t 2'],\n 'plymouth_twilight': ['43 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['2 p 2', '20 p 3', '28 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['25 e 4'],\n 'weekend_truro': ['50 t 5'],\n 'weekend_torquay': ['35 t 4'],\n 'weekend_plymouth': ['34 p 4', '46 p 5'],\n 'night': ['2 p 2', '20 p 3', '28 p 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['27 e 4'],\n 'weekend_truro': ['5 t 2'],\n 'weekend_torquay': ['21 t 3'],\n 'weekend_plymouth': ['29 p 4', '32 p 4'],\n 'night': ['2 p 2', '20 p 3', '28 p 4']}},\n 18: {'Mon': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['38 t 4'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['13 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['17 t 3', '30 p 4', '46 p 5']},\n 'Tue': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['24 t 3'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['19 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['17 t 3', '30 p 4', '46 p 5']},\n 'Wed': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['4 t 2'],\n 'plymouth_twilight': ['3 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['17 t 3', '30 p 4', '46 p 5']},\n 'Thu': {'exeter_twilight': ['1 e 2'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['31 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['17 t 3', '30 p 4', '46 p 5']},\n 'Fri': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['48 t 5'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['6 e 2', '14 t 3', '34 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['18 e 3'],\n 'weekend_truro': ['38 t 4'],\n 'weekend_torquay': ['22 t 3'],\n 'weekend_plymouth': ['13 p 3', '19 p 3'],\n 'night': ['6 e 2', '14 t 3', '34 p 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['25 e 4'],\n 'weekend_truro': ['10 t 2'],\n 'weekend_torquay': ['15 t 3'],\n 'weekend_plymouth': ['32 p 4', '46 p 5'],\n 'night': ['6 e 2', '14 t 3', '34 p 4']}},\n 19: {'Mon': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['20 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '38 t 4', '43 p 5']},\n 'Tue': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['48 t 5'],\n 'plymouth_twilight': ['30 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '38 t 4', '43 p 5']},\n 'Wed': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['23 t 3'],\n 'torquay_twilight': ['37 t 4'],\n 'plymouth_twilight': ['2 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '38 t 4', '43 p 5']},\n 'Thu': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['24 t 3'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['19 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '38 t 4', '43 p 5']},\n 'Fri': {'exeter_twilight': ['1 e 2'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['34 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['20 p 3', '22 t 3', '37 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['7 e 2'],\n 'weekend_truro': ['10 t 2'],\n 'weekend_torquay': ['35 t 4'],\n 'weekend_plymouth': ['30 p 4', '32 p 4'],\n 'night': ['20 p 3', '22 t 3', '37 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['26 e 4'],\n 'weekend_truro': ['17 t 3'],\n 'weekend_torquay': ['15 t 3'],\n 'weekend_plymouth': ['13 p 3', '43 p 5'],\n 'night': ['20 p 3', '22 t 3', '37 t 4']}},\n 20: {'Mon': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['36 t 4'],\n 'plymouth_twilight': ['46 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '29 p 4', '31 p 4']},\n 'Tue': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['8 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '29 p 4', '31 p 4']},\n 'Wed': {'exeter_twilight': ['27 e 4'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['19 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '29 p 4', '31 p 4']},\n 'Thu': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['23 t 3'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['32 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '29 p 4', '31 p 4']},\n 'Fri': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['37 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['2 p 2', '21 t 3', '35 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['7 e 2'],\n 'weekend_truro': ['38 t 4'],\n 'weekend_torquay': ['48 t 5'],\n 'weekend_plymouth': ['13 p 3', '34 p 4'],\n 'night': ['2 p 2', '21 t 3', '35 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['25 e 4'],\n 'weekend_truro': ['16 t 3'],\n 'weekend_torquay': ['22 t 3'],\n 'weekend_plymouth': ['19 p 3', '31 p 4'],\n 'night': ['2 p 2', '21 t 3', '35 t 4']}},\n 21: {'Mon': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['10 t 2'],\n 'torquay_twilight': ['36 t 4'],\n 'plymouth_twilight': ['32 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '14 t 3', '30 p 4']},\n 'Tue': {'exeter_twilight': ['41 e 5'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['3 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '14 t 3', '30 p 4']},\n 'Wed': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['37 t 4'],\n 'plymouth_twilight': ['2 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '14 t 3', '30 p 4']},\n 'Thu': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['38 t 4'],\n 'torquay_twilight': ['35 t 4'],\n 'plymouth_twilight': ['13 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['1 e 2', '14 t 3', '30 p 4']},\n 'Fri': {'exeter_twilight': ['27 e 4'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['48 t 5'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['5 t 2', '16 t 3', '25 e 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['6 e 2'],\n 'weekend_truro': ['39 t 4'],\n 'weekend_torquay': ['36 t 4'],\n 'weekend_plymouth': ['34 p 4', '43 p 5'],\n 'night': ['5 t 2', '16 t 3', '25 e 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['1 e 2'],\n 'weekend_truro': ['17 t 3'],\n 'weekend_torquay': ['4 t 2'],\n 'weekend_plymouth': ['2 p 2', '20 p 3'],\n 'night': ['5 t 2', '16 t 3', '25 e 4']}},\n 22: {'Mon': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['24 t 3'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['3 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['8 p 2', '36 t 4', '38 t 4']},\n 'Tue': {'exeter_twilight': ['41 e 5'],\n 'truro_twilight': ['10 t 2'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['46 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['8 p 2', '36 t 4', '38 t 4']},\n 'Wed': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['8 p 2', '36 t 4', '38 t 4']},\n 'Thu': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['15 t 3'],\n 'plymouth_twilight': ['32 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['8 p 2', '36 t 4', '38 t 4']},\n 'Fri': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['30 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '21 t 3', '41 e 5']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['26 e 4'],\n 'weekend_truro': ['24 t 3'],\n 'weekend_torquay': ['37 t 4'],\n 'weekend_plymouth': ['19 p 3', '43 p 5'],\n 'night': ['4 t 2', '21 t 3', '41 e 5']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['6 e 2'],\n 'weekend_truro': ['38 t 4'],\n 'weekend_torquay': ['48 t 5'],\n 'weekend_plymouth': ['2 p 2', '20 p 3'],\n 'night': ['4 t 2', '21 t 3', '41 e 5']}},\n 23: {'Mon': {'exeter_twilight': ['18 e 3'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['36 t 4'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '13 p 3', '35 t 4']},\n 'Tue': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['15 t 3'],\n 'plymouth_twilight': ['30 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '13 p 3', '35 t 4']},\n 'Wed': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['4 t 2'],\n 'plymouth_twilight': ['43 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '13 p 3', '35 t 4']},\n 'Thu': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['38 t 4'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['34 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['10 t 2', '13 p 3', '35 t 4']},\n 'Fri': {'exeter_twilight': ['1 e 2'],\n 'truro_twilight': ['23 t 3'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['8 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['5 t 2', '17 t 3', '28 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['18 e 3'],\n 'weekend_truro': ['24 t 3'],\n 'weekend_torquay': ['48 t 5'],\n 'weekend_plymouth': ['19 p 3', '20 p 3'],\n 'night': ['5 t 2', '17 t 3', '28 p 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['7 e 2'],\n 'weekend_truro': ['16 t 3'],\n 'weekend_torquay': ['15 t 3'],\n 'weekend_plymouth': ['12 p 3', '46 p 5'],\n 'night': ['5 t 2', '17 t 3', '28 p 4']}},\n 24: {'Mon': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['35 t 4'],\n 'plymouth_twilight': ['34 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '18 e 3', '48 t 5']},\n 'Tue': {'exeter_twilight': ['1 e 2'],\n 'truro_twilight': ['39 t 4'],\n 'torquay_twilight': ['37 t 4'],\n 'plymouth_twilight': ['31 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '18 e 3', '48 t 5']},\n 'Wed': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['36 t 4'],\n 'plymouth_twilight': ['43 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '18 e 3', '48 t 5']},\n 'Thu': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['4 t 2', '18 e 3', '48 t 5']},\n 'Fri': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['23 t 3'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['34 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['3 p 2', '32 p 4', '38 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['27 e 4'],\n 'weekend_truro': ['39 t 4'],\n 'weekend_torquay': ['14 t 3'],\n 'weekend_plymouth': ['28 p 4', '46 p 5'],\n 'night': ['3 p 2', '32 p 4', '38 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['25 e 4'],\n 'weekend_truro': ['10 t 2'],\n 'weekend_torquay': ['47 t 5'],\n 'weekend_plymouth': ['20 p 3', '31 p 4'],\n 'night': ['3 p 2', '32 p 4', '38 t 4']}},\n 25: {'Mon': {'exeter_twilight': ['26 e 4'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['21 t 3'],\n 'plymouth_twilight': ['12 p 3'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 t 3', '24 t 3', '43 p 5']},\n 'Tue': {'exeter_twilight': ['7 e 2'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['9 t 2'],\n 'plymouth_twilight': ['8 p 2'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 t 3', '24 t 3', '43 p 5']},\n 'Wed': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['16 t 3'],\n 'torquay_twilight': ['36 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 t 3', '24 t 3', '43 p 5']},\n 'Thu': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['5 t 2'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 t 3', '24 t 3', '43 p 5']},\n 'Fri': {'exeter_twilight': ['27 e 4'],\n 'truro_twilight': ['38 t 4'],\n 'torquay_twilight': ['37 t 4'],\n 'plymouth_twilight': ['30 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['9 t 2', '35 t 4', '36 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['1 e 2'],\n 'weekend_truro': ['39 t 4'],\n 'weekend_torquay': ['4 t 2'],\n 'weekend_plymouth': ['13 p 3', '20 p 3'],\n 'night': ['9 t 2', '35 t 4', '36 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['26 e 4'],\n 'weekend_truro': ['23 t 3'],\n 'weekend_torquay': ['48 t 5'],\n 'weekend_plymouth': ['43 p 5', '46 p 5'],\n 'night': ['9 t 2', '35 t 4', '36 t 4']}},\n 26: {'Mon': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['24 t 3'],\n 'torquay_twilight': ['22 t 3'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['18 e 3', '27 e 4', '39 t 4']},\n 'Tue': {'exeter_twilight': ['25 e 4'],\n 'truro_twilight': ['50 t 5'],\n 'torquay_twilight': ['14 t 3'],\n 'plymouth_twilight': ['32 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['18 e 3', '27 e 4', '39 t 4']},\n 'Wed': {'exeter_twilight': ['1 e 2'],\n 'truro_twilight': ['38 t 4'],\n 'torquay_twilight': ['35 t 4'],\n 'plymouth_twilight': ['31 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['18 e 3', '27 e 4', '39 t 4']},\n 'Thu': {'exeter_twilight': ['41 e 5'],\n 'truro_twilight': ['17 t 3'],\n 'torquay_twilight': ['4 t 2'],\n 'plymouth_twilight': ['45 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['18 e 3', '27 e 4', '39 t 4']},\n 'Fri': {'exeter_twilight': ['6 e 2'],\n 'truro_twilight': ['10 t 2'],\n 'torquay_twilight': ['48 t 5'],\n 'plymouth_twilight': ['46 p 5'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['7 e 2', '26 e 4', '34 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['25 e 4'],\n 'weekend_truro': ['5 t 2'],\n 'weekend_torquay': ['36 t 4'],\n 'weekend_plymouth': ['8 p 2', '28 p 4'],\n 'night': ['7 e 2', '26 e 4', '34 p 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['1 e 2'],\n 'weekend_truro': ['39 t 4'],\n 'weekend_torquay': ['35 t 4'],\n 'weekend_plymouth': ['2 p 2', '43 p 5'],\n 'night': ['7 e 2', '26 e 4', '34 p 4']}}}" + }, + "metadata": {}, + "execution_count": 81 } ], "source": [ @@ -621,7 +1100,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 82, "metadata": {}, "outputs": [], "source": [ @@ -630,7 +1109,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 83, "metadata": {}, "outputs": [], "source": [ @@ -639,7 +1118,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 84, "metadata": {}, "outputs": [ { @@ -648,7 +1127,7 @@ "text/plain": "1" }, "metadata": {}, - "execution_count": 17 + "execution_count": 84 } ], "source": [ @@ -657,7 +1136,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 85, "metadata": {}, "outputs": [ { @@ -666,7 +1145,7 @@ "text/plain": "0" }, "metadata": {}, - "execution_count": 18 + "execution_count": 85 } ], "source": [ @@ -675,7 +1154,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 86, "metadata": {}, "outputs": [], "source": [ @@ -686,7 +1165,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 87, "metadata": {}, "outputs": [ { @@ -696,7 +1175,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'a' is not defined" ] } @@ -707,7 +1186,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 88, "metadata": {}, "outputs": [ { @@ -716,7 +1195,7 @@ "text/plain": "0" }, "metadata": {}, - "execution_count": 21 + "execution_count": 88 } ], "source": [ @@ -725,7 +1204,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 89, "metadata": {}, "outputs": [ { @@ -734,7 +1213,7 @@ "text/plain": "6" }, "metadata": {}, - "execution_count": 22 + "execution_count": 89 } ], "source": [