From e97a4cc8057ee17e59b0693926f8ff2e9b55db00 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 7 May 2024 15:16:29 +0200 Subject: [PATCH 01/16] added new model input --- caimira/models.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/caimira/models.py b/caimira/models.py index 192a6afc..04098c44 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1331,6 +1331,9 @@ class ShortRangeModel: #: Interpersonal distances distance: _VectorisedFloat + #: Total people with short-range interactions + total_people: int = 1 + def dilution_factor(self) -> _VectorisedFloat: ''' The dilution factor for the respective expiratory activity type. @@ -1816,10 +1819,16 @@ class ExposureModel: """ The expect_new_cases should always take the long-range infection_probability and multiply by the occupants exposed to long-range. """ + prob_inf: _VectorisedFloat = self.infection_probability() if self.short_range != (): - return nested_replace(self, {'short_range': ()}).infection_probability() * self.exposed.number / 100 + # If short-range interaction are defined, the total expected number of new cases + # has to take into account both the long- and short-range probability of infection. + long_range_model = nested_replace(self, {'short_range': (),}) + long_range_prob = long_range_model.infection_probability() + short_range_total_people = self.short_range[0].total_people + return (prob_inf * short_range_total_people + long_range_prob * self.exposed.number) / 100 - return self.infection_probability() * self.exposed.number / 100 + return prob_inf * self.exposed.number / 100 def reproduction_number(self) -> _VectorisedFloat: """ From 0352675878844df97211f11faa14b8f84045aaf2 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 7 May 2024 15:16:55 +0200 Subject: [PATCH 02/16] added input to model generator from form --- caimira/apps/calculator/defaults.py | 1 + caimira/apps/calculator/model_generator.py | 10 +++++++++- caimira/apps/templates/base/calculator.form.html.j2 | 10 ++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index 4976d266..70f61a68 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -73,6 +73,7 @@ DEFAULTS = { 'sensor_in_use': '', 'short_range_option': 'short_range_no', 'short_range_interactions': '[]', + 'short_range_total_people': 1, } # ------------------ Activities ---------------------- diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 4a305418..1baf0538 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -72,6 +72,7 @@ class VirusFormData(FormData): sensor_in_use: str short_range_option: str short_range_interactions: list + short_range_total_people: int _DEFAULTS: typing.ClassVar[typing.Dict[str, typing.Any]] = DEFAULTS @@ -182,6 +183,13 @@ class VirusFormData(FormData): if total_percentage != 100: raise ValueError(f'The sum of all respiratory activities should be 100. Got {total_percentage}.') + + # Validate number of people with short-range interactions + max_people_for_sr = self.total_people - self.infected_people + if self.short_range_total_people > max_people_for_sr: + raise ValueError( + f'The total number of people having short-range interactions ({self.short_range_total_people}) should be lower than the exposed population ({max_people_for_sr}).' + ) def initialize_room(self) -> models.Room: # Initializes room with volume either given directly or as product of area and height @@ -206,7 +214,6 @@ class VirusFormData(FormData): room = self.initialize_room() ventilation: models._VentilationBase = self.ventilation() infected_population = self.infected_population() - short_range = [] if self.short_range_option == "short_range_yes": for interaction in self.short_range_interactions: @@ -216,6 +223,7 @@ class VirusFormData(FormData): activity=infected_population.activity, presence=self.short_range_interval(interaction), distance=short_range_distances(self.data_registry), + total_people=self.short_range_total_people, )) return mc.ExposureModel( diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2 index 6842ea63..3aacea77 100644 --- a/caimira/apps/templates/base/calculator.form.html.j2 +++ b/caimira/apps/templates/base/calculator.form.html.j2 @@ -584,6 +584,16 @@
+
+
+
+ +
+
+ +
+
+
+
Expected new cases: {{ long_range_expected_cases | float_format }}

{% if form.short_range_option == "short_range_yes" %} @@ -118,13 +126,14 @@ {% endblock warning_animation %} +
Expected new cases: {{ expected_new_cases | float_format }}
{% endif %}
{% block report_summary %}
{% if form.short_range_option == "short_range_yes" %}
diff --git a/caimira/models.py b/caimira/models.py index 04098c44..9cbfce75 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1819,16 +1819,11 @@ class ExposureModel: """ The expect_new_cases should always take the long-range infection_probability and multiply by the occupants exposed to long-range. """ - prob_inf: _VectorisedFloat = self.infection_probability() - if self.short_range != (): - # If short-range interaction are defined, the total expected number of new cases - # has to take into account both the long- and short-range probability of infection. - long_range_model = nested_replace(self, {'short_range': (),}) - long_range_prob = long_range_model.infection_probability() - short_range_total_people = self.short_range[0].total_people - return (prob_inf * short_range_total_people + long_range_prob * self.exposed.number) / 100 + # If short-range interaction are defined, the total exposed people + # is only those of the short-range interactions. + exposed = self.short_range[0].total_people if self.short_range != () else self.exposed.number - return prob_inf * self.exposed.number / 100 + return self.infection_probability() * exposed / 100 def reproduction_number(self) -> _VectorisedFloat: """ From 70dc4a93d6d770690b068a17f1913ab6f938145e Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 7 May 2024 16:01:41 +0200 Subject: [PATCH 06/16] simplified report logic --- .../templates/base/calculator.report.html.j2 | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/caimira/apps/templates/base/calculator.report.html.j2 b/caimira/apps/templates/base/calculator.report.html.j2 index c0310cb2..bcf904b7 100644 --- a/caimira/apps/templates/base/calculator.report.html.j2 +++ b/caimira/apps/templates/base/calculator.report.html.j2 @@ -54,20 +54,21 @@
+ {% set long_range_prob_inf = prob_inf %} + {% set long_range_expected_cases = expected_new_cases %} + {% set total_expected_new_cases = expected_new_cases %} + {# Update values if short range option is "short_range_yes" #} {% if form.short_range_option == "short_range_yes" %} - {# Probability of infection values #} {% set scenario = alternative_scenarios.stats.values() | first %} - {% set long_range_prob_inf = scenario.probability_of_infection %} - {% set long_range_prob_probabilistic_exposure = scenario.prob_probabilistic_exposure if form.exposure_option == 'p_probabilistic_exposure' %} - {# Expected new case values #} - {% set long_range_expected_cases = scenario.expected_new_cases %} - {% set total_expected_new_cases = scenario.expected_new_cases + expected_new_cases %} - {% else %} {# Probability of infection values #} - {% set long_range_prob_inf = prob_inf %} + {% set long_range_prob_inf = scenario.probability_of_infection %} + {% set long_range_expected_cases = scenario.expected_new_cases %} {# Expected new case values #} - {% set total_expected_new_cases = expected_new_cases %} + {% set total_expected_new_cases = scenario.expected_new_cases + expected_new_cases %} + {% if form.exposure_option == 'p_probabilistic_exposure' %} + {% set long_range_prob_probabilistic_exposure = scenario.prob_probabilistic_exposure %} + {% endif %} {% endif %} {% block report_results %} From a54da08789b5da26b97bbddc433360451b034e5f Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Wed, 15 May 2024 11:17:08 +0200 Subject: [PATCH 07/16] renamed and moved total_people for short-range interactions to the exposure model --- caimira/apps/calculator/model_generator.py | 2 +- caimira/models.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 1baf0538..5ab6099c 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -223,7 +223,6 @@ class VirusFormData(FormData): activity=infected_population.activity, presence=self.short_range_interval(interaction), distance=short_range_distances(self.data_registry), - total_people=self.short_range_total_people, )) return mc.ExposureModel( @@ -242,6 +241,7 @@ class VirusFormData(FormData): geographic_cases=self.geographic_cases, ascertainment_bias=CONFIDENCE_LEVEL_OPTIONS[self.ascertainment_bias], ), + exposed_to_short_range=self.short_range_total_people, ) def build_model(self, sample_size=None) -> models.ExposureModel: diff --git a/caimira/models.py b/caimira/models.py index 9cbfce75..389c082c 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1331,9 +1331,6 @@ class ShortRangeModel: #: Interpersonal distances distance: _VectorisedFloat - #: Total people with short-range interactions - total_people: int = 1 - def dilution_factor(self) -> _VectorisedFloat: ''' The dilution factor for the respective expiratory activity type. @@ -1582,6 +1579,9 @@ class ExposureModel: #: Geographical data geographical_data: Cases + #: Total people with short-range interactions + exposed_to_short_range: int = 1 + #: The number of times the exposure event is repeated (default 1). @property def repeats(self) -> int: @@ -1821,7 +1821,7 @@ class ExposureModel: """ # If short-range interaction are defined, the total exposed people # is only those of the short-range interactions. - exposed = self.short_range[0].total_people if self.short_range != () else self.exposed.number + exposed = self.exposed_to_short_range if self.short_range != () else self.exposed.number return self.infection_probability() * exposed / 100 From ed5b0d0e275e4481c05dceed352b45aa53e66325 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Wed, 15 May 2024 11:19:14 +0200 Subject: [PATCH 08/16] minor version update --- caimira/apps/calculator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py index be8de7ab..74435939 100644 --- a/caimira/apps/calculator/__init__.py +++ b/caimira/apps/calculator/__init__.py @@ -42,7 +42,7 @@ from .user import AuthenticatedUser, AnonymousUser # calculator version. If the calculator needs to make breaking changes (e.g. change # form attributes) then it can also increase its MAJOR version without needing to # increase the overall CAiMIRA version (found at ``caimira.__version__``). -__version__ = "4.15.1" +__version__ = "4.15.2" LOG = logging.getLogger("Calculator") From 04b8e91639ce7d364a7c5b8d65125087e0436a1c Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Fri, 17 May 2024 17:05:10 +0200 Subject: [PATCH 09/16] updated new cases method --- caimira/apps/calculator/report_generator.py | 2 +- .../apps/templates/base/calculator.report.html.j2 | 9 ++++----- .../apps/templates/cern/calculator.report.html.j2 | 4 ++-- caimira/models.py | 14 +++++++++----- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index fae89ef5..cf6ab070 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -431,7 +431,7 @@ def manufacture_alternative_scenarios(form: VirusFormData) -> typing.Dict[str, m scenarios['Neither ventilation nor masks'] = without_mask_or_vent.build_mc_model() else: - no_short_range_alternative = dataclass_utils.replace(form, short_range_interactions=[]) + no_short_range_alternative = dataclass_utils.replace(form, short_range_interactions=[], total_people=form.total_people - form.short_range_total_people) scenarios['Base scenario without short-range interactions'] = no_short_range_alternative.build_mc_model() return scenarios diff --git a/caimira/apps/templates/base/calculator.report.html.j2 b/caimira/apps/templates/base/calculator.report.html.j2 index bcf904b7..1c11b192 100644 --- a/caimira/apps/templates/base/calculator.report.html.j2 +++ b/caimira/apps/templates/base/calculator.report.html.j2 @@ -56,16 +56,15 @@
{% set long_range_prob_inf = prob_inf %} {% set long_range_expected_cases = expected_new_cases %} - {% set total_expected_new_cases = expected_new_cases %} {# Update values if short range option is "short_range_yes" #} {% if form.short_range_option == "short_range_yes" %} {% set scenario = alternative_scenarios.stats.values() | first %} {# Probability of infection values #} {% set long_range_prob_inf = scenario.probability_of_infection %} - {% set long_range_expected_cases = scenario.expected_new_cases %} {# Expected new case values #} - {% set total_expected_new_cases = scenario.expected_new_cases + expected_new_cases %} + {% set long_range_expected_cases = scenario.expected_new_cases %} + {% if form.exposure_option == 'p_probabilistic_exposure' %} {% set long_range_prob_probabilistic_exposure = scenario.prob_probabilistic_exposure %} {% endif %} @@ -134,12 +133,12 @@ {% block report_summary %}
{% if form.short_range_option == "short_range_yes" %}
{% endif %} {% block probabilistic_exposure_probability %} diff --git a/caimira/apps/templates/cern/calculator.report.html.j2 b/caimira/apps/templates/cern/calculator.report.html.j2 index d482b1dc..0e0a0175 100644 --- a/caimira/apps/templates/cern/calculator.report.html.j2 +++ b/caimira/apps/templates/cern/calculator.report.html.j2 @@ -70,7 +70,7 @@ {% if form.short_range_option == "short_range_yes" %}
@@ -84,7 +84,7 @@ {% endif %} diff --git a/caimira/models.py b/caimira/models.py index 389c082c..82387f58 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1817,13 +1817,17 @@ class ExposureModel: "with dynamic occupancy") """ - The expect_new_cases should always take the long-range infection_probability and multiply by the occupants exposed to long-range. + The expected_new_cases has two different use case scenarios: + 1) Long-range only: take the infection_probability and multiply by the occupants exposed to long-range. + 2) Short- and long-range: take the infection_probability of long-range multiplied by the occupants exposed to long-range only, added + to the infection_probability of short- and long-range multiplied by the occupants exposed to short-range only. """ - # If short-range interaction are defined, the total exposed people - # is only those of the short-range interactions. - exposed = self.exposed_to_short_range if self.short_range != () else self.exposed.number - return self.infection_probability() * exposed / 100 + if self.short_range != (): + new_cases_long_range = nested_replace(self, {'short_range': (),}).infection_probability() * (self.exposed.number - self.exposed_to_short_range) + return (new_cases_long_range + (self.infection_probability() * self.exposed_to_short_range)) / 100 + + return self.infection_probability() * self.exposed.number / 100 def reproduction_number(self) -> _VectorisedFloat: """ From 10146f9a184d57933233c17570c17dfebbfd554b Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 21 May 2024 11:03:59 +0200 Subject: [PATCH 10/16] added baseline model with sr --- caimira/tests/apps/calculator/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/caimira/tests/apps/calculator/conftest.py b/caimira/tests/apps/calculator/conftest.py index 38c19e97..59ce26db 100644 --- a/caimira/tests/apps/calculator/conftest.py +++ b/caimira/tests/apps/calculator/conftest.py @@ -11,3 +11,12 @@ def baseline_form_data(): @pytest.fixture def baseline_form(baseline_form_data, data_registry): return model_generator.VirusFormData.from_dict(baseline_form_data, data_registry) + + +@pytest.fixture +def baseline_form_with_sr(baseline_form_data, data_registry): + form_data_sr = baseline_form_data + form_data_sr['short_range_option'] = 'short_range_yes' + form_data_sr['short_range_interactions'] = '[{"expiration": "Shouting", "start_time": "10:30", "duration": "30"}]' + form_data_sr['short_range_total_people'] = 5 + return model_generator.VirusFormData.from_dict(form_data_sr, data_registry) \ No newline at end of file From e61700aaadbd0851745c665b7f559b8fe8e59417 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 21 May 2024 11:04:25 +0200 Subject: [PATCH 11/16] added test for expected new cases --- .../apps/calculator/test_report_generator.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/caimira/tests/apps/calculator/test_report_generator.py b/caimira/tests/apps/calculator/test_report_generator.py index 22355c5d..1f842fc0 100644 --- a/caimira/tests/apps/calculator/test_report_generator.py +++ b/caimira/tests/apps/calculator/test_report_generator.py @@ -7,7 +7,9 @@ import numpy as np import pytest from caimira.apps.calculator import make_app -from caimira.apps.calculator.report_generator import ReportGenerator, readable_minutes +from caimira.apps.calculator.model_generator import VirusFormData +from caimira.apps.calculator.report_generator import (ReportGenerator, readable_minutes, calculate_report_data, + manufacture_alternative_scenarios, interesting_times, comparison_report) import caimira.apps.calculator.report_generator as rep_gen @@ -90,3 +92,26 @@ def test_interesting_times_w_temp(exposure_model_w_outside_temp_changes): 5., 5.4, 5.8, 6.2, 6.6, 7., 7.4, 7.8, 8. ] np.testing.assert_allclose(result, expected) + + +def test_expected_new_cases(baseline_form_with_sr: VirusFormData): + model = baseline_form_with_sr.build_model() + + executor_factory = partial( + concurrent.futures.ThreadPoolExecutor, 1, + ) + + # Short- and Long-range contributions + report_data = calculate_report_data(baseline_form_with_sr, model, executor_factory) + sr_lr_expected_new_cases = report_data['expected_new_cases'] + sr_lr_prob_inf = report_data['prob_inf']/100 + + # Long-range contributions alone + scenario_sample_times = interesting_times(model) + alternative_scenarios = manufacture_alternative_scenarios(baseline_form_with_sr) + alternative_statistics = comparison_report( + baseline_form_with_sr, report_data, alternative_scenarios, scenario_sample_times, executor_factory=executor_factory, + ) + + 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_total_people, 2) From a0d8f45424cb4619f7167e04c51c8622975a3455 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 23 May 2024 09:19:57 +0200 Subject: [PATCH 12/16] renamed new input "short_range_total_people" to "short_range_occupants" --- caimira/apps/calculator/defaults.py | 2 +- caimira/apps/calculator/model_generator.py | 8 ++++---- caimira/apps/calculator/report_generator.py | 2 +- caimira/apps/calculator/static/js/form.js | 2 +- caimira/apps/templates/base/calculator.form.html.j2 | 2 +- caimira/apps/templates/base/calculator.report.html.j2 | 2 +- caimira/tests/apps/calculator/conftest.py | 2 +- caimira/tests/apps/calculator/test_report_generator.py | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index 70f61a68..40e6ad33 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -73,7 +73,7 @@ DEFAULTS = { 'sensor_in_use': '', 'short_range_option': 'short_range_no', 'short_range_interactions': '[]', - 'short_range_total_people': 1, + 'short_range_occupants': 1, } # ------------------ Activities ---------------------- diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 5ab6099c..234f2752 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -72,7 +72,7 @@ class VirusFormData(FormData): sensor_in_use: str short_range_option: str short_range_interactions: list - short_range_total_people: int + short_range_occupants: int _DEFAULTS: typing.ClassVar[typing.Dict[str, typing.Any]] = DEFAULTS @@ -186,9 +186,9 @@ class VirusFormData(FormData): # Validate number of people with short-range interactions max_people_for_sr = self.total_people - self.infected_people - if self.short_range_total_people > max_people_for_sr: + if self.short_range_occupants > max_people_for_sr: raise ValueError( - f'The total number of people having short-range interactions ({self.short_range_total_people}) should be lower than the exposed population ({max_people_for_sr}).' + f'The total number of people having short-range interactions ({self.short_range_occupants}) should be lower than the exposed population ({max_people_for_sr}).' ) def initialize_room(self) -> models.Room: @@ -241,7 +241,7 @@ class VirusFormData(FormData): geographic_cases=self.geographic_cases, ascertainment_bias=CONFIDENCE_LEVEL_OPTIONS[self.ascertainment_bias], ), - exposed_to_short_range=self.short_range_total_people, + exposed_to_short_range=self.short_range_occupants, ) def build_model(self, sample_size=None) -> models.ExposureModel: diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index cf6ab070..8ae92987 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -431,7 +431,7 @@ def manufacture_alternative_scenarios(form: VirusFormData) -> typing.Dict[str, m scenarios['Neither ventilation nor masks'] = without_mask_or_vent.build_mc_model() else: - no_short_range_alternative = dataclass_utils.replace(form, short_range_interactions=[], total_people=form.total_people - form.short_range_total_people) + no_short_range_alternative = dataclass_utils.replace(form, short_range_interactions=[], total_people=form.total_people - form.short_range_occupants) scenarios['Base scenario without short-range interactions'] = no_short_range_alternative.build_mc_model() return scenarios diff --git a/caimira/apps/calculator/static/js/form.js b/caimira/apps/calculator/static/js/form.js index be376497..71023c2a 100644 --- a/caimira/apps/calculator/static/js/form.js +++ b/caimira/apps/calculator/static/js/form.js @@ -1268,7 +1268,7 @@ $(document).ready(function () { let activity = validate_sr_parameter('#sr_expiration_no_' + String(index)[0], "Required input."); let start = validate_sr_parameter('#sr_start_no_' + String(index)[0], "Required input."); let duration = validate_sr_parameter('#sr_duration_no_' + String(index)[0], "Required input."); - let total_people = validate_sr_people('#short_range_total_people'); + let total_people = validate_sr_people('#short_range_occupants'); if (activity && start && duration && total_people) { if (validate_sr_time('#sr_start_no_' + String(index)) && validate_sr_time('#sr_duration_no_' + String(index))) { document.getElementById('sr_expiration_no_' + String(index)).disabled = true; diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2 index 3aacea77..745612e6 100644 --- a/caimira/apps/templates/base/calculator.form.html.j2 +++ b/caimira/apps/templates/base/calculator.form.html.j2 @@ -590,7 +590,7 @@
- +
diff --git a/caimira/apps/templates/base/calculator.report.html.j2 b/caimira/apps/templates/base/calculator.report.html.j2 index 1c11b192..cc6190c5 100644 --- a/caimira/apps/templates/base/calculator.report.html.j2 +++ b/caimira/apps/templates/base/calculator.report.html.j2 @@ -614,7 +614,7 @@ Short-range interactions: {{ form.short_range_interactions|length }}

  • - Total number of people having short-range interactions: {{ form.short_range_total_people }} + Total number of people having short-range interactions: {{ form.short_range_occupants }}

    • {% for interaction in form.short_range_interactions %} diff --git a/caimira/tests/apps/calculator/conftest.py b/caimira/tests/apps/calculator/conftest.py index 59ce26db..d774e333 100644 --- a/caimira/tests/apps/calculator/conftest.py +++ b/caimira/tests/apps/calculator/conftest.py @@ -18,5 +18,5 @@ def baseline_form_with_sr(baseline_form_data, data_registry): form_data_sr = baseline_form_data form_data_sr['short_range_option'] = 'short_range_yes' form_data_sr['short_range_interactions'] = '[{"expiration": "Shouting", "start_time": "10:30", "duration": "30"}]' - form_data_sr['short_range_total_people'] = 5 + form_data_sr['short_range_occupants'] = 5 return model_generator.VirusFormData.from_dict(form_data_sr, data_registry) \ No newline at end of file diff --git a/caimira/tests/apps/calculator/test_report_generator.py b/caimira/tests/apps/calculator/test_report_generator.py index 1f842fc0..ba0295eb 100644 --- a/caimira/tests/apps/calculator/test_report_generator.py +++ b/caimira/tests/apps/calculator/test_report_generator.py @@ -114,4 +114,4 @@ 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_total_people, 2) + 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) From 0c41b8e98af41d67e38f64d624a65f6a857fed28 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 23 May 2024 09:31:45 +0200 Subject: [PATCH 13/16] changed the default value of short_range_occupants to be 0 --- caimira/apps/calculator/defaults.py | 2 +- caimira/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index 40e6ad33..e12664eb 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -73,7 +73,7 @@ DEFAULTS = { 'sensor_in_use': '', 'short_range_option': 'short_range_no', 'short_range_interactions': '[]', - 'short_range_occupants': 1, + 'short_range_occupants': 0, } # ------------------ Activities ---------------------- diff --git a/caimira/models.py b/caimira/models.py index 82387f58..9275fb77 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1580,7 +1580,7 @@ class ExposureModel: geographical_data: Cases #: Total people with short-range interactions - exposed_to_short_range: int = 1 + exposed_to_short_range: int = 0 #: The number of times the exposure event is repeated (default 1). @property From aa2e3be2cc0c355567338acc250d740a44f838c3 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 23 May 2024 09:33:48 +0200 Subject: [PATCH 14/16] replaced max_people_for_sr to max_occupants_for_sr --- caimira/apps/calculator/model_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 234f2752..5d6e1a34 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -185,10 +185,10 @@ class VirusFormData(FormData): raise ValueError(f'The sum of all respiratory activities should be 100. Got {total_percentage}.') # Validate number of people with short-range interactions - max_people_for_sr = self.total_people - self.infected_people - if self.short_range_occupants > max_people_for_sr: + max_occupants_for_sr = self.total_people - self.infected_people + if self.short_range_occupants > max_occupants_for_sr: raise ValueError( - f'The total number of people having short-range interactions ({self.short_range_occupants}) should be lower than the exposed population ({max_people_for_sr}).' + f'The total number of occupants having short-range interactions ({self.short_range_occupants}) should be lower than the exposed population ({max_occupants_for_sr}).' ) def initialize_room(self) -> models.Room: From ac04710d1b086dec7be561db9099f51ecae6a09e Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 23 May 2024 09:55:17 +0200 Subject: [PATCH 15/16] ui modifications --- .../templates/base/calculator.report.html.j2 | 19 +++++++++---------- .../templates/cern/calculator.report.html.j2 | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/caimira/apps/templates/base/calculator.report.html.j2 b/caimira/apps/templates/base/calculator.report.html.j2 index cc6190c5..76c3d3f9 100644 --- a/caimira/apps/templates/base/calculator.report.html.j2 +++ b/caimira/apps/templates/base/calculator.report.html.j2 @@ -138,7 +138,7 @@ {% if form.short_range_option == "short_range_yes" %}
      {% endif %} {% block probabilistic_exposure_probability %} @@ -610,17 +610,16 @@ {% endif %}

      {% if form.short_range_option == "short_range_yes" %} -
    • - Short-range interactions: {{ form.short_range_interactions|length }} -

    • -
    • - Total number of people having short-range interactions: {{ form.short_range_occupants }} -

    • +
    • Total number of occupants having short-range interactions: {{ form.short_range_occupants }}

      • {% for interaction in form.short_range_interactions %} -
      • Expiratory activity {{ loop.index if form.short_range_interactions|length > 1 }}: {{ "Shouting/Singing" if interaction.expiration == "Shouting" else interaction.expiration }}
      • -
      • Start time {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.start_time }}
      • -
      • Duration {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.duration }} {{ "minutes" if interaction.duration|float > 1 else "minute" }}
      • +
      • Interaction no. {{ loop.index }}: +
          +
        • Expiratory activity: {{ "Shouting/Singing" if interaction.expiration == "Shouting" else interaction.expiration }}
        • +
        • Start time: {{ interaction.start_time }}
        • +
        • Duration: {{ interaction.duration }} {{ "minutes" if interaction.duration|float > 1 else "minute" }}
        • +
        +
      • {% endfor %}
      {% endif %} diff --git a/caimira/apps/templates/cern/calculator.report.html.j2 b/caimira/apps/templates/cern/calculator.report.html.j2 index 0e0a0175..701c2ab0 100644 --- a/caimira/apps/templates/cern/calculator.report.html.j2 +++ b/caimira/apps/templates/cern/calculator.report.html.j2 @@ -84,7 +84,7 @@ {% endif %} From 8ea055a8e107f8d4534d6831445f2361da0f3520 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 23 May 2024 09:57:03 +0200 Subject: [PATCH 16/16] changed docstring for expected_new_cases method --- caimira/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/caimira/models.py b/caimira/models.py index 9275fb77..441b70ff 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -1817,10 +1817,10 @@ class ExposureModel: "with dynamic occupancy") """ - The expected_new_cases has two different use case scenarios: - 1) Long-range only: take the infection_probability and multiply by the occupants exposed to long-range. - 2) Short- and long-range: take the infection_probability of long-range multiplied by the occupants exposed to long-range only, added - to the infection_probability of short- and long-range multiplied by the occupants exposed to short-range only. + The expected_new_cases may provide one or two different outputs: + 1) Long-range exposure: take the infection_probability and multiply by the occupants exposed to long-range. + 2) Short- and long-range exposure: take the infection_probability of long-range multiplied by the occupants exposed to long-range only, + plus the infection_probability of short- and long-range multiplied by the occupants exposed to short-range only. """ if self.short_range != ():