From f4c9c5fa96340265ddf252caed8a097b3a13ec6d Mon Sep 17 00:00:00 2001 From: lrdossan Date: Fri, 19 Jul 2024 16:12:03 +0200 Subject: [PATCH] added test for dynamic occupancy from input form --- .../calculator/report/virus_report_data.py | 4 +- .../validators/co2/co2_validator.py | 6 +- .../caimira/calculator/validators/defaults.py | 2 +- .../validators/virus/virus_validator.py | 5 +- cern_caimira/tests/test_report_generator.py | 59 +++++++++++++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/caimira/src/caimira/calculator/report/virus_report_data.py b/caimira/src/caimira/calculator/report/virus_report_data.py index 4dbdbc54..29fddac6 100644 --- a/caimira/src/caimira/calculator/report/virus_report_data.py +++ b/caimira/src/caimira/calculator/report/virus_report_data.py @@ -176,7 +176,7 @@ def calculate_report_data(form: VirusFormData, executor_factory: typing.Callable # Probabilistic exposure if form.exposure_option == "p_probabilistic_exposure" and form.occupancy_format == "static": prob_probabilistic_exposure = np.array(model.total_probability_rule()).mean() - else: prob_probabilistic_exposure = 0 + else: prob_probabilistic_exposure = -1 # Expected new cases if (form.occupancy_format == "static"): expected_new_cases = np.array(model.expected_new_cases()).mean() @@ -425,7 +425,7 @@ def scenario_statistics( # It means we have data to calculate the total_probability_rule prob_probabilistic_exposure = model.total_probability_rule() else: - prob_probabilistic_exposure = 0. + prob_probabilistic_exposure = -1 if (compute_expected_new_cases): expected_new_cases = np.mean(model.expected_new_cases()) diff --git a/caimira/src/caimira/calculator/validators/co2/co2_validator.py b/caimira/src/caimira/calculator/validators/co2/co2_validator.py index ba865a7c..7a41f6c6 100644 --- a/caimira/src/caimira/calculator/validators/co2/co2_validator.py +++ b/caimira/src/caimira/calculator/validators/co2/co2_validator.py @@ -60,8 +60,10 @@ class CO2FormData(FormData): self.data_registry = DataRegistry() def validate(self): - # Validate population parameters - self.validate_population_parameters() + # Validate population parameters when static occupancy is defined. + # Dynamic population is validated in the generate_dynamic_occupancy method. + if self.occupancy_format == 'static': + self.validate_population_parameters() # Validate room capacity if self.room_capacity: diff --git a/caimira/src/caimira/calculator/validators/defaults.py b/caimira/src/caimira/calculator/validators/defaults.py index 8ef2d138..bd14db38 100644 --- a/caimira/src/caimira/calculator/validators/defaults.py +++ b/caimira/src/caimira/calculator/validators/defaults.py @@ -37,7 +37,7 @@ DEFAULTS = { 'infected_lunch_option': True, 'infected_lunch_start': '12:30', 'infected_people': 1, - 'dynamic_infected_occupancy': '[]', + 'dynamic_infected_occupancy': NO_DEFAULT, 'infected_start': '08:30', 'inside_temp': NO_DEFAULT, 'location_latitude': NO_DEFAULT, diff --git a/caimira/src/caimira/calculator/validators/virus/virus_validator.py b/caimira/src/caimira/calculator/validators/virus/virus_validator.py index 99a00dce..c8230974 100644 --- a/caimira/src/caimira/calculator/validators/virus/virus_validator.py +++ b/caimira/src/caimira/calculator/validators/virus/virus_validator.py @@ -73,7 +73,10 @@ class VirusFormData(FormData): _DEFAULTS: typing.ClassVar[typing.Dict[str, typing.Any]] = DEFAULTS def validate(self): - self.validate_population_parameters() + # Validate population parameters when static occupancy is defined. + # Dynamic population is validated in the generate_dynamic_occupancy method. + if self.occupancy_format == 'static': + self.validate_population_parameters() validation_tuples = [('activity_type', self.data_registry.population_scenario_activity.keys()), ('mechanical_ventilation_type', diff --git a/cern_caimira/tests/test_report_generator.py b/cern_caimira/tests/test_report_generator.py index 6e257f61..3e9026b7 100644 --- a/cern_caimira/tests/test_report_generator.py +++ b/cern_caimira/tests/test_report_generator.py @@ -2,6 +2,7 @@ import concurrent.futures from functools import partial import os import time +import json import numpy as np import pytest @@ -123,4 +124,62 @@ def test_expected_new_cases(baseline_form_with_sr: VirusFormData): lr_expected_new_cases = alternative_statistics['stats']['Base scenario without short-range interactions']['expected_new_cases'] np.testing.assert_almost_equal(sr_lr_expected_new_cases, lr_expected_new_cases + sr_lr_prob_inf * baseline_form_with_sr.short_range_occupants, 2) + + +def test_static_vs_dynamic_occupancy_from_form(baseline_form_data, data_registry): + """ + Assert that the results between a static and dynamic occupancy model (from form inputs) are similar. + """ + executor_factory = partial( + concurrent.futures.ThreadPoolExecutor, 1, + ) + + # By default the baseline form accepts static occupancy + static_occupancy_baseline_form: VirusFormData = VirusFormData.from_dict(baseline_form_data, data_registry) + static_occupancy_model = static_occupancy_baseline_form.build_model() + static_occupancy_report_data = rep_gen.calculate_report_data(static_occupancy_baseline_form, executor_factory) + + # Update the initial form data to include dynamic occupancy (please note the 4 coffee and 1 lunch breaks) + baseline_form_data['occupancy_format'] = 'dynamic' + baseline_form_data['dynamic_infected_occupancy'] = json.dumps([ + {'total_people': 1, 'start_time': '09:00', 'finish_time': '10:03'}, + {'total_people': 0, 'start_time': '10:03', 'finish_time': '10:13'}, + {'total_people': 1, 'start_time': '10:13', 'finish_time': '11:16'}, + {'total_people': 0, 'start_time': '11:16', 'finish_time': '11:26'}, + {'total_people': 1, 'start_time': '11:26', 'finish_time': '12:30'}, + {'total_people': 0, 'start_time': '12:30', 'finish_time': '13:30'}, + {'total_people': 1, 'start_time': '13:30', 'finish_time': '14:53'}, + {'total_people': 0, 'start_time': '14:53', 'finish_time': '15:03'}, + {'total_people': 1, 'start_time': '15:03', 'finish_time': '16:26'}, + {'total_people': 0, 'start_time': '16:26', 'finish_time': '16:36'}, + {'total_people': 1, 'start_time': '16:36', 'finish_time': '18:00'}, + ]) + baseline_form_data['dynamic_exposed_occupancy'] = json.dumps([ + {'total_people': 9, 'start_time': '09:00', 'finish_time': '10:03'}, + {'total_people': 0, 'start_time': '10:03', 'finish_time': '10:13'}, + {'total_people': 9, 'start_time': '10:13', 'finish_time': '11:16'}, + {'total_people': 0, 'start_time': '11:16', 'finish_time': '11:26'}, + {'total_people': 9, 'start_time': '11:26', 'finish_time': '12:30'}, + {'total_people': 0, 'start_time': '12:30', 'finish_time': '13:30'}, + {'total_people': 9, 'start_time': '13:30', 'finish_time': '14:53'}, + {'total_people': 0, 'start_time': '14:53', 'finish_time': '15:03'}, + {'total_people': 9, 'start_time': '15:03', 'finish_time': '16:26'}, + {'total_people': 0, 'start_time': '16:26', 'finish_time': '16:36'}, + {'total_people': 9, 'start_time': '16:36', 'finish_time': '18:00'}, + ]) + baseline_form_data['total_people'] = 0 + baseline_form_data['infected_people'] = 0 + + dynamic_occupancy_baseline_form: VirusFormData = VirusFormData.from_dict(baseline_form_data, data_registry) + dynamic_occupancy_model = dynamic_occupancy_baseline_form.build_model() + dynamic_occupancy_report_data = rep_gen.calculate_report_data(dynamic_occupancy_baseline_form, executor_factory) + + assert (list(sorted(static_occupancy_model.concentration_model.infected.presence.transition_times())) == + list(dynamic_occupancy_model.concentration_model.infected.number.transition_times)) + assert (list(sorted(static_occupancy_model.exposed.presence.transition_times())) == + list(dynamic_occupancy_model.exposed.number.transition_times)) + + np.testing.assert_almost_equal(static_occupancy_report_data['prob_inf'], dynamic_occupancy_report_data['prob_inf'], 1) + assert dynamic_occupancy_report_data['expected_new_cases'] == -1 + assert dynamic_occupancy_report_data['prob_probabilistic_exposure'] == -1 \ No newline at end of file