From c38f69ca9a49437d2830d881a63e97dd47ab4509 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 --- .../apps/calculator/co2_model_generator.py | 6 +- caimira/apps/calculator/defaults.py | 2 +- caimira/apps/calculator/model_generator.py | 6 +- caimira/apps/calculator/report_generator.py | 4 +- .../apps/calculator/test_report_generator.py | 60 +++++++++++++++++++ 5 files changed, 71 insertions(+), 7 deletions(-) diff --git a/caimira/apps/calculator/co2_model_generator.py b/caimira/apps/calculator/co2_model_generator.py index a4a6a9c2..19e05746 100644 --- a/caimira/apps/calculator/co2_model_generator.py +++ b/caimira/apps/calculator/co2_model_generator.py @@ -59,8 +59,10 @@ def __init__(self, **kwargs): 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 specific inputs - breaks (exposed and infected) if self.specific_breaks != {}: diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index 333256ea..2d2f7139 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -38,7 +38,7 @@ '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/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 4b3595b0..522f1e44 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -77,8 +77,10 @@ class VirusFormData(FormData): _DEFAULTS: typing.ClassVar[typing.Dict[str, typing.Any]] = DEFAULTS 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() validation_tuples = [('activity_type', self.data_registry.population_scenario_activity.keys()), ('mechanical_ventilation_type', MECHANICAL_VENTILATION_TYPES), diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index 04882a4c..30bc072c 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -171,7 +171,7 @@ def calculate_report_data(form: VirusFormData, model: models.ExposureModel, exec # 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() @@ -479,7 +479,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/tests/apps/calculator/test_report_generator.py b/caimira/tests/apps/calculator/test_report_generator.py index ba0295eb..dc0335a2 100644 --- a/caimira/tests/apps/calculator/test_report_generator.py +++ b/caimira/tests/apps/calculator/test_report_generator.py @@ -2,6 +2,7 @@ from functools import partial import os import time +import json import numpy as np import pytest @@ -115,3 +116,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 = calculate_report_data(static_occupancy_baseline_form, static_occupancy_model, 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 = calculate_report_data(dynamic_occupancy_baseline_form, dynamic_occupancy_model, 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