def test_circular_get_window_demand(self): s = Splitter(self.week_demand, self.min_length, self.max_length) # For circular - I'll do one manual for sanity and a couple programmatic assert s._get_window_demand(1, 4) == self.expected_flat_week_demand[1:4] assert s._get_window_demand(11, 13) == [0, 1] assert s._get_window_demand(11, 11) == []
def test_windowing_split_valid_subproblem(self): # Window so large that it gets recursively split self.min_length = 2 # Update versus prior test self.week_demand = [[1, 0, 0, 4], [1, 2, 1, 0], [1, 1, 1, 1]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(3, 7), (8, 10), (10, 13)] assert s._windows == expected_windows
def test_circular_get_demand(self): s = Splitter(self.week_demand, self.min_length, self.max_length) # For circular - I'll do one manual for sanity and a couple programmatic assert s._get_flat_demand(13) == self.expected_flat_week_demand[1] for t in range(len(self.expected_flat_week_demand)): assert s._get_flat_demand(t) == self.expected_flat_week_demand[t] assert s._get_flat_demand(t + len(self.expected_flat_week_demand) ) == self.expected_flat_week_demand[t]
def test_windowing_short_subproblem(self): self.week_demand = [[1, 2, 0, 0], [1, 3, 1, 0], [0, 1, 1, 0]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [ (4, 7), ] assert s._windows == expected_windows
def test_windowing_split_invalid_subproblem(self): # Window that is so large that it gets recursively split . . . # BUT one of the split problems is less the min length, so # it shoudl abandon the search self.week_demand = [[1, 1, 0, 4], [1, 2, 1, 0], [0, 1, 1, 1]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(3, 7), (9, 14)] assert s._windows == expected_windows
def test_london_on_demand_two(self): """Test on demand trial data that supposedly had low efficiency""" # yapf: disable demand = [ [0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 8, 12, 10, 9, 8, 9, 15, 15, 20, 17, 14, 7, 4], [0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 6, 10, 14, 14, 9, 9, 9, 12, 20, 25, 20, 12, 9, 5], [0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 7, 12, 15, 12, 10, 9, 12, 12, 16, 21, 20, 12, 9, 4], [0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 7, 10, 13, 13, 9, 9, 11, 15, 17, 24, 23, 14, 7, 5], [0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 7, 10, 13, 13, 11, 11, 11, 12, 25, 29, 29, 20, 12, 9], [6, 0, 0, 0, 0, 0, 0, 0, 3, 6, 6, 12, 19, 18, 17, 16, 16, 18, 24, 25, 25, 18, 11, 7], [4, 0, 0, 0, 0, 0, 0, 0, 3, 6, 7, 12, 19, 19, 17, 16, 16, 18, 23, 29, 26, 14, 9, 5], ] # yapf: enable min_length = 4 max_length = 14 s = Splitter(demand, min_length, max_length) s.calculate() s.validate() # For these, we really need perfect efficiency assert s.efficiency() < 0.01
def test_splitter_init(self): s = Splitter(self.week_demand, self.min_length, self.max_length) assert len( s.flat_demand) == len(self.week_demand) * len(self.week_demand[0]) assert s.flat_demand == self.expected_flat_week_demand assert s.min_length == self.min_length assert s.max_length == self.max_length assert s.day_length == len(self.week_demand[0])
def test_flat_index_to_time(self): s = Splitter(self.week_demand, self.min_length, self.max_length) flat_to_time = [ (0, 0), (1, 1), (2, 2), (3, 3), (4, 0), (5, 1), (6, 2), (7, 3), (8, 0), (9, 1), (10, 2), (11, 3), ] for (flat, time) in flat_to_time: assert s._flat_index_to_time(flat) == time
def test_flat_index_to_day(self): """Take the flat demand index and return the day integer""" s = Splitter(self.week_demand, self.min_length, self.max_length) flat_to_day = [ (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (5, 1), (6, 1), (7, 1), (8, 2), (9, 2), (10, 2), (11, 2), ] for (flat, day) in flat_to_day: assert s._flat_index_to_day(flat) == day
def _process_task(self, task): # 1. Fetch schedule self.org = self.client.get_organization( task.data.get("organization_id")) self.loc = self.org.get_location(task.data.get("location_id")) self.role = self.loc.get_role(task.data.get("role_id")) self.sched = self.role.get_schedule(task.data.get("schedule_id")) self._compute_demand() self._subtract_existing_shifts_from_demand() # Run the calculation s = Splitter(self.demand, self.sched.data.get("min_shift_length_hour"), self.sched.data.get("max_shift_length_hour")) s.calculate() s.efficiency() # Naive becuase not yet datetimes naive_shifts = s.get_shifts() logger.info("Starting upload of %s shifts", len(naive_shifts)) local_start_time = self._get_local_start_time() for shift in naive_shifts: # We have to think of daylight savings time here, so we need to # guarantee that we don't have any errors. We do this by overshooting # the timedelta by an extra two hours, then rounding back to midnight. logger.debug("Processing shift %s", shift) start_day = normalize_to_midnight( deepcopy(local_start_time) + timedelta(days=shift["day"])) # Beware of time changes - duplicate times are possible try: start = start_day.replace(hour=shift["start"]) except pytz.AmbiguousTimeError: # Randomly pick one. Minor tech debt. start = start_day.replace(hour=shift["start"], is_dst=False) stop = start + timedelta(hours=shift["length"]) # Convert to the strings we are passing up to the cLoUd utc_start_str = start.astimezone(self.default_tz).isoformat() utc_stop_str = stop.astimezone(self.default_tz).isoformat() logger.info("Creating shift with start %s stop %s", start, stop) self.role.create_shift(start=utc_start_str, stop=utc_stop_str)
def test_la(self): """24/7 LA client data""" # yapf: disable demand = [ # They sometimes have leading zeros [0, 0, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 1], [1, 1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3], [4, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4], [3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4], [4, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4], ] # yapf: enable min_length = 6 max_length = 8 s = Splitter(demand, min_length, max_length) s.calculate() s.validate() assert s.efficiency() < 0.08
def test_call_center(self): """See if it chokes for call center client data""" # yapf: disable demand = [ [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0], ] # yapf: enable min_length = 9 max_length = 9 s = Splitter(demand, min_length, max_length) s.calculate() s.validate() # For these, we really need perfect efficiency assert s.efficiency() < 0.001
def test_succeeds_with_zero_demand(self): # yapf: disable demand = [ [0]*24, [0]*24, [0]*24, [0]*24, [0]*24, [0]*24, [0]*24, ] # yapf: enable min_length = 4 max_length = 8 s = Splitter(demand, min_length, max_length) s.calculate() s.validate() efficiency = s.efficiency() assert efficiency == PERFECT_OPTIMALITY
def test_online_store_prod_failure(self): """This data caused a production error""" # yapf: disable demand = [ [8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8], [8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8], [8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8], [8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8], [8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8], ] # yapf: enable min_length = 8 max_length = 8 s = Splitter(demand, min_length, max_length) s.calculate() s.validate()
def test_on_demand_old(self): """Old on demand data - good test of wrap-around""" # yapf: disable demand = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 4, 4, 6, 6, 7, 7, 5, 2], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 4, 5, 3, 3, 4, 4, 7, 6, 5, 4, 2], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 4, 5, 4, 5, 5, 7, 6, 6, 5, 3], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 5, 5, 2, 5, 6, 7, 6, 6, 5, 3], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 5, 4, 6, 5, 6, 8, 5, 4, 4], [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6, 6, 5, 2], [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 5, 5, 4, 6, 6, 11, 8, 6, 4, 2], ] # yapf: enable min_length = 4 max_length = 8 s = Splitter(demand, min_length, max_length) s.calculate() s.validate() efficiency = s.efficiency() assert efficiency < EFFICIENCY_LIMIT
def test_windowing_standard(self): s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(0, 3), (4, 7), (8, 11)] assert s._windows == expected_windows
def test_splitter_init_different_day_lengths(self): self.week_demand[1].pop() with pytest.raises(UnequalDayLengthException): Splitter(self.week_demand, self.min_length, self.max_length)
def test_windowing_standard_again(self): self.week_demand = [[0, 2, 3, 4, 0, 3, 1, 8], [1, 2, 3, 0, 2, 3, 1, 8]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(1, 4), (5, 11), (12, 16)] assert s._windows == expected_windows
def test_is_always_open_case_false(self): s = Splitter(self.week_demand, self.min_length, self.max_length) # Just going through this whole week, cause f**k it assert s._is_always_open() is False
def test_is_circular_necessary_case_false(self): s = Splitter(self.week_demand, self.min_length, self.max_length) assert s._is_circular_necessary() is False
def test_is_circular_necessary_case_true(self): self.week_demand = [[1, 1, 0, 4], [1, 2, 1, 0], [1, 1, 1, 1]] s = Splitter(self.week_demand, self.min_length, self.max_length) assert s._is_circular_necessary() is True
def test_windowing_circular(self): self.week_demand = [[1, 1, 0, 4], [1, 2, 1, 0], [0, 0, 1, 1]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(3, 7), (10, 14)] assert s._windows == expected_windows
def test_is_always_open_case_carryover_true(self): self.week_demand = [[0, 0, 0, 4], [1, 3, 1, 8], [1, 1, 1, 2]] s = Splitter(self.week_demand, self.min_length, self.max_length) assert s._is_always_open() is True
def test_windowing_always_open(self): self.week_demand = [[1, 2, 3, 4], [1, 3, 1, 8], [1, 1, 1, 2]] s = Splitter(self.week_demand, self.min_length, self.max_length) s._generate_windows() expected_windows = [(0, 4), (4, 8), (8, 12)] assert s._windows == expected_windows