diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index cb6738f7..9f9dbce3 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -185,6 +185,10 @@ class FormData: return form_dict def validate(self): + # Validate number of infected <= number of total people + if self.infected_people > self.total_people: + raise ValueError('Number of infected people cannot be more than number of total people.') + # Validate time intervals selected by user time_intervals = [ ['exposed_start', 'exposed_finish'], @@ -202,6 +206,43 @@ class FormData: raise ValueError( f"{start_name} must be less than {end_name}. Got {start} and {end}.") + def validate_lunch(start, finish): + lunch_start = getattr(self, f'{population}_lunch_start') + lunch_finish = getattr(self, f'{population}_lunch_finish') + return (start <= lunch_start <= finish and + start <= lunch_finish <= finish) + + def get_lunch_mins(population): + lunch_mins = 0 + if getattr(self, f'{population}_lunch_option'): + lunch_mins = getattr(self, f'{population}_lunch_finish') - getattr(self, f'{population}_lunch_start') + return lunch_mins + + def get_coffee_mins(population): + coffee_mins = 0 + if getattr(self, f'{population}_coffee_break_option') != 'coffee_break_0': + coffee_mins = COFFEE_OPTIONS_INT[getattr(self, f'{population}_coffee_break_option')] * getattr(self, f'{population}_coffee_duration') + return coffee_mins + + def get_activity_mins(population): + return getattr(self, f'{population}_finish') - getattr(self, f'{population}_start') + + populations = ['exposed', 'infected'] if self.infected_dont_have_breaks_with_exposed else ['exposed'] + for population in populations: + # Validate lunch time within the activity times. + if (getattr(self, f'{population}_lunch_option') and + not validate_lunch(getattr(self, f'{population}_start'), getattr(self, f'{population}_finish')) + ): + raise ValueError( + f"{population} lunch break must be within presence times." + ) + + # Length of breaks < length of activity + if (get_lunch_mins(population) + get_coffee_mins(population)) >= get_activity_mins(population): + raise ValueError( + f"Length of breaks >= Length of {population} presence." + ) + validation_tuples = [('activity_type', ACTIVITY_TYPES), ('exposed_coffee_break_option', COFFEE_OPTIONS_INT), ('infected_coffee_break_option', COFFEE_OPTIONS_INT), @@ -229,6 +270,11 @@ class FormData: "window_opening_regime cannot be 'not-applicable' if " "ventilation_type is 'natural_ventilation'" ) + if (self.window_opening_regime == 'windows_open_periodically' and + self.windows_duration > self.windows_frequency): + raise ValueError( + 'Duration cannot be bigger than frequency.' + ) if (self.ventilation_type == 'mechanical_ventilation' and self.mechanical_ventilation_type == 'not-applicable'): diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index cd7de105..3f531d17 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -167,6 +167,13 @@ def test_ventilation_window_hepa(baseline_form: model_generator.FormData): assert ventilation == baseline_vent +def test_infected_less_than_total_people(baseline_form: model_generator.FormData): + baseline_form.total_people = 10 + baseline_form.infected_people = 11 + with pytest.raises(ValueError, match='Number of infected people cannot be more than number of total people.'): + baseline_form.validate() + + def present_times(interval: models.Interval) -> models.BoundarySequence_t: assert isinstance(interval, models.SpecificInterval) return interval.present_times @@ -255,6 +262,59 @@ def test_exposed_present_lunch_end_before_beginning(baseline_form: model_generat baseline_form.validate() +@pytest.mark.parametrize( + "exposed_lunch_start, exposed_lunch_finish", + [ + [8, 14], # lunch_start before the presence begining + [19, 20], # lunch_start after the presence finishing + [7, 8], # lunch_finish before the presence begining + [9, 20], # lunch_finish after the presence finishing + ], +) +def test_exposed_presence_lunch_break(baseline_form: model_generator.FormData, exposed_lunch_start, exposed_lunch_finish): + baseline_form.exposed_lunch_start = minutes_since_midnight(exposed_lunch_start * 60) + baseline_form.exposed_lunch_finish = minutes_since_midnight(exposed_lunch_finish * 60) + with pytest.raises(ValueError, match='exposed lunch break must be within presence times.'): + baseline_form.validate() + + +@pytest.mark.parametrize( + "infected_lunch_start, infected_lunch_finish", + [ + [8, 14], # lunch_start before the presence begining + [19, 20], # lunch_start after the presence finishing + [7, 8], # lunch_finish before the presence begining + [9, 20], # lunch_finish after the presence finishing + ], +) +def test_infected_presence_lunch_break(baseline_form: model_generator.FormData, infected_lunch_start, infected_lunch_finish): + baseline_form.infected_lunch_start = minutes_since_midnight(infected_lunch_start * 60) + baseline_form.infected_lunch_finish = minutes_since_midnight(infected_lunch_finish * 60) + with pytest.raises(ValueError, match='infected lunch break must be within presence times.'): + baseline_form.validate() + + +def test_exposed_breaks_length(baseline_form: model_generator.FormData): + baseline_form.exposed_coffee_break_option = 'coffee_break_4' + baseline_form.exposed_coffee_duration = 30 + baseline_form.exposed_start = minutes_since_midnight(10 * 60) + baseline_form.exposed_finish = minutes_since_midnight(11 * 60) + baseline_form.exposed_lunch_option = False + with pytest.raises(ValueError, match='Length of breaks >= Length of exposed presence.'): + baseline_form.validate() + + +def test_infected_breaks_length(baseline_form: model_generator.FormData): + baseline_form.infected_start = minutes_since_midnight(9 * 60) + baseline_form.infected_finish = minutes_since_midnight(12 * 60) + baseline_form.infected_lunch_start = minutes_since_midnight(10 * 60) + baseline_form.infected_lunch_finish = minutes_since_midnight(11 * 60) + baseline_form.infected_coffee_break_option = 'coffee_break_4' + baseline_form.infected_coffee_duration = 30 + with pytest.raises(ValueError, match='Length of breaks >= Length of infected presence.'): + baseline_form.validate() + + @pytest.fixture def coffee_break_between_1045_and_1115(baseline_form: model_generator.FormData): baseline_form.exposed_coffee_break_option = 'coffee_break_1' @@ -435,6 +495,14 @@ def test_key_validation_natural_ventilation_window_opening_regime_na(baseline_fo model_generator.FormData.from_dict(baseline_form_data) +def test_natural_ventilation_window_opening_periodically(baseline_form: model_generator.FormData): + baseline_form.window_opening_regime = 'windows_open_periodically' + baseline_form.windows_duration = 20 + baseline_form.windows_frequency = 10 + with pytest.raises(ValueError, match='Duration cannot be bigger than frequency.'): + baseline_form.validate() + + def test_key_validation_mech_ventilation_type_na(baseline_form_data): baseline_form_data['ventilation_type'] = 'mechanical_ventilation' baseline_form_data['mechanical_ventilation_type'] = 'not-applicable'