From b92bbb6334d22bbef629c660a7c6868205e4e1a4 Mon Sep 17 00:00:00 2001 From: lrdossan Date: Mon, 16 Sep 2024 14:48:46 +0200 Subject: [PATCH] changes after rebase from master --- .../src/caimira/calculator/models/models.py | 56 ++++++++----------- .../calculator/report/virus_report_data.py | 31 +++++----- .../validators/co2/co2_validator.py | 6 +- .../calculator/validators/form_validator.py | 35 ++++++------ .../validators/virus/virus_validator.py | 6 +- .../apps/calculator/test_model_generator.py | 32 ++++++++++- .../test_specific_model_generator.py | 2 - .../tests/models/test_dynamic_population.py | 28 ++++++---- .../templates/base/calculator.report.html.j2 | 30 ++++++---- .../templates/cern/calculator.report.html.j2 | 20 ++++--- cern_caimira/tests/test_report_generator.py | 2 +- 11 files changed, 147 insertions(+), 101 deletions(-) diff --git a/caimira/src/caimira/calculator/models/models.py b/caimira/src/caimira/calculator/models/models.py index 64c566fd..9af2a8cf 100644 --- a/caimira/src/caimira/calculator/models/models.py +++ b/caimira/src/caimira/calculator/models/models.py @@ -1873,50 +1873,42 @@ class ExposureModel: 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. - In the case dynamic occupancy is defined, the maximum number of exposed occupants during the course of the simulation will be considered. + Currently disabled when dynamic occupancy is defined for the exposed population. """ - exposed_occ: int = max(self.exposed.number.values) if isinstance(self.exposed.number, IntPiecewiseConstant) else self.exposed.number + + if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant) or + isinstance(self.exposed.number, IntPiecewiseConstant)): + raise NotImplementedError("Cannot compute expected new cases " + "with dynamic occupancy") if self.short_range != (): - new_cases_long_range = nested_replace(self, {'short_range': [],}).infection_probability() * (exposed_occ - self.exposed_to_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() * exposed_occ / 100 + return self.infection_probability() * self.exposed.number / 100 def reproduction_number(self) -> _VectorisedFloat: """ The reproduction number can be thought of as the expected number of cases directly generated by one infected case in a population. - It handles the cases when dynamic occupancy for the infected population is defined. + Currently disabled when dynamic occupancy is defined for both the infected and exposed population. """ - infected_number = self.concentration_model.infected.number - if isinstance(infected_number, IntPiecewiseConstant): - # Handle case when infected number is dynamic - max_occ = max(infected_number.values) - if max_occ == 1: - return self.expected_new_cases() - else: - # Adjust to treat dynamic occupancy, limiting infected to 1 when present - inf_occ_values = [1 if occ > 0 else occ for occ in infected_number.values] - single_exposure_model = nested_replace( - self, { - 'concentration_model.infected.number.values': inf_occ_values - } - ) - return single_exposure_model.expected_new_cases() - - elif isinstance(infected_number, int): - # Handle case when infected number is a single integer - if infected_number == 1: - return self.expected_new_cases() + if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant) or + isinstance(self.exposed.number, IntPiecewiseConstant)): + raise NotImplementedError("Cannot compute reproduction number " + "with dynamic occupancy") - # Create an equivalent exposure model but with precisely - # one infected case. - single_exposure_model = nested_replace( - self, { - 'concentration_model.infected.number': 1} - ) + if self.concentration_model.infected.number == 1: + return self.expected_new_cases() - return single_exposure_model.expected_new_cases() + # Create an equivalent exposure model but with precisely + # one infected case. + single_exposure_model = nested_replace( + self, { + 'concentration_model.infected.number': 1} + ) + + return single_exposure_model.expected_new_cases() + \ No newline at end of file diff --git a/caimira/src/caimira/calculator/report/virus_report_data.py b/caimira/src/caimira/calculator/report/virus_report_data.py index 84f313e2..6c4d62e6 100644 --- a/caimira/src/caimira/calculator/report/virus_report_data.py +++ b/caimira/src/caimira/calculator/report/virus_report_data.py @@ -173,10 +173,13 @@ def calculate_report_data(form: VirusFormData, executor_factory: typing.Callable prob = np.array(model.infection_probability()) prob_dist_count, prob_dist_bins = np.histogram(prob/100, bins=100, density=True) - # 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 = None + # Probabilistic exposure and expected new cases (only for static occupancy) + prob_probabilistic_exposure = None + expected_new_cases = None + if form.occupancy_format == "static": + if form.exposure_option == "p_probabilistic_exposure": + prob_probabilistic_exposure = np.array(model.total_probability_rule()).mean() + expected_new_cases = np.array(model.expected_new_cases()).mean() exposed_presence_intervals = [list(interval) for interval in model.exposed.presence_interval().boundaries()] @@ -207,11 +210,10 @@ def calculate_report_data(form: VirusFormData, executor_factory: typing.Callable "prob_hist_count": list(prob_dist_count), "prob_hist_bins": list(prob_dist_bins), "prob_probabilistic_exposure": prob_probabilistic_exposure, - "expected_new_cases": np.array(model.expected_new_cases()).mean(), + "expected_new_cases": expected_new_cases, "uncertainties_plot_src": uncertainties_plot_src, "CO2_concentrations": CO2_concentrations, "conditional_probability_data": conditional_probability_data, - "uncertainties_plot_src": uncertainties_plot_src, } @@ -417,24 +419,20 @@ def manufacture_alternative_scenarios(form: VirusFormData) -> typing.Dict[str, m def scenario_statistics( mc_model: mc.ExposureModel, sample_times: typing.List[float], + static_occupancy: bool, compute_prob_exposure: bool, ): model = mc_model.build_model( size=mc_model.data_registry.monte_carlo['sample_size']) - if (compute_prob_exposure): - # It means we have data to calculate the total_probability_rule - prob_probabilistic_exposure = model.total_probability_rule() - else: - prob_probabilistic_exposure = -1 return { 'probability_of_infection': np.mean(model.infection_probability()), - 'expected_new_cases': np.mean(model.expected_new_cases()), + 'expected_new_cases': np.mean(model.expected_new_cases()) if static_occupancy else None, 'concentrations': [ np.mean(model.concentration(time)) for time in sample_times ], - 'prob_probabilistic_exposure': prob_probabilistic_exposure, + 'prob_probabilistic_exposure': model.total_probability_rule() if compute_prob_exposure else None, } @@ -455,16 +453,15 @@ def comparison_report( else: statistics = {} - if (form.short_range_option == "short_range_yes" and form.exposure_option == "p_probabilistic_exposure" and form.occupancy_format == "static"): - compute_prob_exposure = True - else: - compute_prob_exposure = False + static_occupancy = form.occupancy_format == "static" + compute_prob_exposure = form.short_range_option == "short_range_yes" and form.exposure_option == "p_probabilistic_exposure" and static_occupancy with executor_factory() as executor: results = executor.map( scenario_statistics, scenarios.values(), [report_data['times']] * len(scenarios), + [static_occupancy] * len(scenarios), [compute_prob_exposure] * len(scenarios), timeout=60, ) diff --git a/caimira/src/caimira/calculator/validators/co2/co2_validator.py b/caimira/src/caimira/calculator/validators/co2/co2_validator.py index 659bd27c..941f8b75 100644 --- a/caimira/src/caimira/calculator/validators/co2/co2_validator.py +++ b/caimira/src/caimira/calculator/validators/co2/co2_validator.py @@ -201,11 +201,13 @@ class CO2FormData(FormData): # intervals and number of people are dynamic. Activity type is not needed. if self.occupancy_format == 'dynamic': if isinstance(self.dynamic_infected_occupancy, typing.List) and len(self.dynamic_infected_occupancy) > 0: - infected_people, infected_presence = self.generate_dynamic_occupancy(self.dynamic_infected_occupancy) + infected_people = self.generate_dynamic_occupancy(self.dynamic_infected_occupancy) + infected_presence = None else: raise TypeError(f'If dynamic occupancy is selected, a populated list of occupancy intervals is expected. Got "{self.dynamic_infected_occupancy}".') if isinstance(self.dynamic_exposed_occupancy, typing.List) and len(self.dynamic_exposed_occupancy) > 0: - exposed_people, exposed_presence = self.generate_dynamic_occupancy(self.dynamic_exposed_occupancy) + exposed_people = self.generate_dynamic_occupancy(self.dynamic_exposed_occupancy) + exposed_presence = None else: raise TypeError(f'If dynamic occupancy is selected, a populated list of occupancy intervals is expected. Got "{self.dynamic_exposed_occupancy}".') else: diff --git a/caimira/src/caimira/calculator/validators/form_validator.py b/caimira/src/caimira/calculator/validators/form_validator.py index 281647b5..70e89e58 100644 --- a/caimira/src/caimira/calculator/validators/form_validator.py +++ b/caimira/src/caimira/calculator/validators/form_validator.py @@ -19,7 +19,7 @@ minutes_since_midnight = typing.NewType('minutes_since_midnight', int) @dataclasses.dataclass class FormData: - specific_breaks: dict + # Static occupancy inputs exposed_coffee_break_option: str exposed_coffee_duration: int exposed_finish: minutes_since_midnight @@ -27,19 +27,23 @@ class FormData: exposed_lunch_option: bool exposed_lunch_start: minutes_since_midnight exposed_start: minutes_since_midnight - infected_coffee_break_option: str - infected_coffee_duration: int + infected_coffee_break_option: str #Used if infected_dont_have_breaks_with_exposed + infected_coffee_duration: int #Used if infected_dont_have_breaks_with_exposed infected_dont_have_breaks_with_exposed: bool infected_finish: minutes_since_midnight - infected_lunch_finish: minutes_since_midnight # Used if infected_dont_have_breaks_with_exposed - infected_lunch_option: bool # Used if infected_dont_have_breaks_with_exposed - infected_lunch_start: minutes_since_midnight # Used if infected_dont_have_breaks_with_exposed - infected_people: int - dynamic_infected_occupancy: list + infected_lunch_finish: minutes_since_midnight #Used if infected_dont_have_breaks_with_exposed + infected_lunch_option: bool #Used if infected_dont_have_breaks_with_exposed + infected_lunch_start: minutes_since_midnight #Used if infected_dont_have_breaks_with_exposed infected_start: minutes_since_midnight + infected_people: int + occupancy_format: str room_volume: float + specific_breaks: dict total_people: int + + # Dynamic occupancy inputs dynamic_exposed_occupancy: list + dynamic_infected_occupancy: list data_registry: DataRegistry @@ -385,24 +389,24 @@ class FormData: for occupancy in dynamic_occupancy: # Check if each occupancy entry is a dictionary if not isinstance(occupancy, typing.Dict): - raise TypeError(f'Each occupancy entry should be in a dictionary format. Got "{type(occupancy)}."') + raise TypeError(f'Each occupancy entry should be in a dictionary format. Got "{type(occupancy)}".') # Check for required keys in each occupancy entry dict_keys = list(occupancy.keys()) if "total_people" not in dict_keys: - raise TypeError(f'Unable to fetch "total_people" key. Got "{dict_keys[0]}".') + raise TypeError(f'Unable to fetch "total_people" key. Got "{dict_keys}".') else: value = occupancy["total_people"] # Check if the value is a non-negative integer if not isinstance(value, int): - raise ValueError(f"Total number of people should be integer. Got {value}.") + raise ValueError(f'Total number of people should be integer. Got "{type(value)}".') elif not value >= 0: - raise ValueError(f"Total number of people should be non-negative. Got {value}.") + raise ValueError(f'Total number of people should be non-negative. Got "{value}".') if "start_time" not in dict_keys: - raise TypeError(f'Unable to fetch "start_time" key. Got "{dict_keys[1]}".') + raise TypeError(f'Unable to fetch "start_time" key. Got "{dict_keys}".') if "finish_time" not in dict_keys: - raise TypeError(f'Unable to fetch "finish_time" key. Got "{dict_keys[2]}".') + raise TypeError(f'Unable to fetch "finish_time" key. Got "{dict_keys}".') # Validate time format for start_time and finish_time for time_key in ["start_time", "finish_time"]: @@ -427,8 +431,7 @@ class FormData: transition_times=tuple(unique_transition_times_sorted), values=tuple(values) ) - population_presence: typing.Union[None, models.Interval] = None - return population_occupancy, population_presence + return population_occupancy def _hours2timestring(hours: float): diff --git a/caimira/src/caimira/calculator/validators/virus/virus_validator.py b/caimira/src/caimira/calculator/validators/virus/virus_validator.py index bdea535f..ea3642c8 100644 --- a/caimira/src/caimira/calculator/validators/virus/virus_validator.py +++ b/caimira/src/caimira/calculator/validators/virus/virus_validator.py @@ -470,7 +470,8 @@ class VirusFormData(FormData): if isinstance(self.dynamic_infected_occupancy, typing.List) and len(self.dynamic_infected_occupancy) > 0: # If dynamic occupancy is defined, the generator will parse and validate the # respective input to a format readable by the model - `IntPiecewiseConstant`. - infected_occupancy, infected_presence = self.generate_dynamic_occupancy(self.dynamic_infected_occupancy) + infected_occupancy = self.generate_dynamic_occupancy(self.dynamic_infected_occupancy) + infected_presence = None else: raise TypeError(f'If dynamic occupancy is selected, a populated list of occupancy intervals is expected. Got "{self.dynamic_infected_occupancy}".') else: @@ -515,7 +516,8 @@ class VirusFormData(FormData): if isinstance(self.dynamic_exposed_occupancy, typing.List) and len(self.dynamic_exposed_occupancy) > 0: # If dynamic occupancy is defined, the generator will parse and validate the # respective input to a format readable by the model - IntPiecewiseConstant. - exposed_occupancy, exposed_presence = self.generate_dynamic_occupancy(self.dynamic_exposed_occupancy) + exposed_occupancy = self.generate_dynamic_occupancy(self.dynamic_exposed_occupancy) + exposed_presence = None else: raise TypeError(f'If dynamic occupancy is selected, a populated list of occupancy intervals is expected. Got "{self.dynamic_exposed_occupancy}".') else: diff --git a/caimira/tests/apps/calculator/test_model_generator.py b/caimira/tests/apps/calculator/test_model_generator.py index 435ecd39..2aca9c3e 100644 --- a/caimira/tests/apps/calculator/test_model_generator.py +++ b/caimira/tests/apps/calculator/test_model_generator.py @@ -1,5 +1,5 @@ import dataclasses -import typing +import re import numpy as np import numpy.testing as npt @@ -588,3 +588,33 @@ def test_form_timezone(baseline_form_data, data_registry, longitude, latitude, m name, offset = form.tz_name_and_utc_offset() assert name == expected_tz_name assert offset == expected_offset + + +@pytest.mark.parametrize( + ["dynamic_occupancy_input", "error"], + [ + [[["total_people", 10, "start_time", "10:00", "finish_time", "11:00"]], "Each occupancy entry should be in a dictionary format. Got \"\"."], + [[{"tal_people": 10, "start_time": "10:00", "finish_time": "11:00"}], "Unable to fetch \"total_people\" key. Got \"['tal_people', 'start_time', 'finish_time']\"."], + [[{"total_people": 10, "art_time": "10:00", "finish_time": "11:00"}], "Unable to fetch \"start_time\" key. Got \"['total_people', 'art_time', 'finish_time']\"."], + [[{"total_people": 10, "start_time": "10:00", "ish_time": "11:00"}], "Unable to fetch \"finish_time\" key. Got \"['total_people', 'start_time', 'ish_time']\"."], + [[{"total_people": 10, "start_time": "10", "finish_time": "11:00"}], "Wrong time format - \"HH:MM\". Got \"10\"."], + [[{"total_people": 10, "start_time": "10:00", "finish_time": "11"}], "Wrong time format - \"HH:MM\". Got \"11\"."], + ] +) +def test_dynamic_occupancy_structure(dynamic_occupancy_input, error, baseline_form: virus_validator.VirusFormData): + with pytest.raises(TypeError, match=re.escape(error)): + baseline_form.generate_dynamic_occupancy(dynamic_occupancy_input) + + +@pytest.mark.parametrize( + ["dynamic_occupancy_input", "error"], + [ + [[{"total_people": "10", "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be integer. Got \"\"."], + [[{"total_people": 9.8, "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be integer. Got \"\"."], + [[{"total_people": [10], "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be integer. Got \"\"."], + [[{"total_people": -1, "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be non-negative. Got \"-1\"."], + ] +) +def test_dynamic_occupancy_total_people(dynamic_occupancy_input, error, baseline_form: virus_validator.VirusFormData): + with pytest.raises(ValueError, match=re.escape(error)): + baseline_form.generate_dynamic_occupancy(dynamic_occupancy_input) diff --git a/caimira/tests/apps/calculator/test_specific_model_generator.py b/caimira/tests/apps/calculator/test_specific_model_generator.py index 6945a2c1..513d908c 100644 --- a/caimira/tests/apps/calculator/test_specific_model_generator.py +++ b/caimira/tests/apps/calculator/test_specific_model_generator.py @@ -1,5 +1,3 @@ -from typing import Type -import numpy as np import pytest from caimira.calculator.validators.virus import virus_validator diff --git a/caimira/tests/models/test_dynamic_population.py b/caimira/tests/models/test_dynamic_population.py index 8a28f93b..a6377094 100644 --- a/caimira/tests/models/test_dynamic_population.py +++ b/caimira/tests/models/test_dynamic_population.py @@ -232,24 +232,32 @@ def test_dynamic_total_probability_rule( def test_dynamic_expected_new_cases( - full_exposure_model: models.ExposureModel, dynamic_infected_single_exposure_model: models.ExposureModel, dynamic_exposed_single_exposure_model: models.ExposureModel, dynamic_population_exposure_model: models.ExposureModel): - base_expected_new_cases = full_exposure_model.expected_new_cases() - npt.assert_almost_equal(base_expected_new_cases, dynamic_infected_single_exposure_model.expected_new_cases()) - npt.assert_almost_equal(base_expected_new_cases, dynamic_exposed_single_exposure_model.expected_new_cases()) - npt.assert_almost_equal(base_expected_new_cases, dynamic_population_exposure_model.expected_new_cases()) + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases " + "with dynamic occupancy")): + dynamic_infected_single_exposure_model.expected_new_cases() + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases " + "with dynamic occupancy")): + dynamic_exposed_single_exposure_model.expected_new_cases() + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases " + "with dynamic occupancy")): + dynamic_population_exposure_model.expected_new_cases() def test_dynamic_reproduction_number( - full_exposure_model: models.ExposureModel, dynamic_infected_single_exposure_model: models.ExposureModel, dynamic_exposed_single_exposure_model: models.ExposureModel, dynamic_population_exposure_model: models.ExposureModel): - base_reproduction_number = full_exposure_model.reproduction_number() - npt.assert_almost_equal(base_reproduction_number, dynamic_infected_single_exposure_model.reproduction_number()) - npt.assert_almost_equal(base_reproduction_number, dynamic_exposed_single_exposure_model.reproduction_number()) - npt.assert_almost_equal(base_reproduction_number, dynamic_population_exposure_model.reproduction_number()) + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number " + "with dynamic occupancy")): + dynamic_infected_single_exposure_model.reproduction_number() + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number " + "with dynamic occupancy")): + dynamic_exposed_single_exposure_model.reproduction_number() + with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number " + "with dynamic occupancy")): + dynamic_population_exposure_model.reproduction_number() diff --git a/cern_caimira/src/cern_caimira/apps/templates/base/calculator.report.html.j2 b/cern_caimira/src/cern_caimira/apps/templates/base/calculator.report.html.j2 index 88bdf0b2..fadd5e79 100644 --- a/cern_caimira/src/cern_caimira/apps/templates/base/calculator.report.html.j2 +++ b/cern_caimira/src/cern_caimira/apps/templates/base/calculator.report.html.j2 @@ -104,7 +104,7 @@ {% endblock long_range_warning_animation %} -
Expected new cases: {{ long_range_expected_cases | float_format }}
+ {% if form.occupancy_format == "static" %}
Expected new cases: {{ long_range_expected_cases | float_format }}
{% endif %}
{% if form.short_range_option == "short_range_yes" %} @@ -126,19 +126,27 @@ {% endblock warning_animation %} -
Expected new cases: {{ expected_new_cases | float_format }}
+ {% if form.occupancy_format == "static" %} +
Expected new cases: {{ expected_new_cases | float_format }}
+ {% endif %} {% endif %}
{% block report_summary %}
{% if form.short_range_option == "short_range_yes" %}
{% endif %} {% block probabilistic_exposure_probability %} @@ -302,7 +310,7 @@ Scenario P(I) - Expected new cases + {% if form.occupancy_format == "static" %}Expected new cases{% endif %} @@ -310,7 +318,7 @@ {{ scenario_name }} {{ scenario_stats.probability_of_infection | non_zero_percentage }} - {{ scenario_stats.expected_new_cases | float_format }} + {% if form.occupancy_format == "static" %}{{ scenario_stats.expected_new_cases | float_format }}{% endif %} {% endfor %} @@ -630,11 +638,11 @@
    {% for interaction in form.short_range_interactions %}
  • 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" }}
    • -
    +
      +
    • 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 %}
diff --git a/cern_caimira/src/cern_caimira/apps/templates/cern/calculator.report.html.j2 b/cern_caimira/src/cern_caimira/apps/templates/cern/calculator.report.html.j2 index 701c2ab0..4248b65b 100644 --- a/cern_caimira/src/cern_caimira/apps/templates/cern/calculator.report.html.j2 +++ b/cern_caimira/src/cern_caimira/apps/templates/cern/calculator.report.html.j2 @@ -11,7 +11,7 @@ {% set long_range_prob_inf = prob_inf %} {% endif %} -{% if ((long_range_prob_inf > red_prob_lim) or (expected_new_cases >= 1)) %} +{% if ((long_range_prob_inf > red_prob_lim) or (form.occupancy_format == "static" and expected_new_cases >= 1)) %} {% set long_range_scale_warning = 'red' %} {% set long_range_warning_color= 'bg-danger' %} {% elif (orange_prob_lim <= long_range_prob_inf <= red_prob_lim) %} @@ -22,7 +22,7 @@ {% set long_range_warning_color = 'bg-success' %} {% endif %} -{% if ((prob_inf > red_prob_lim) or (expected_new_cases >= 1)) %} {% set scale_warning = 'red' %} +{% if ((prob_inf > red_prob_lim) or (form.occupancy_format == "static" and expected_new_cases >= 1)) %} {% set scale_warning = 'red' %} {% elif (orange_prob_lim <= prob_inf <= red_prob_lim) %} {% set scale_warning = 'orange' %} {% else %} {% set scale_warning = 'green' %} {% endif %} @@ -70,7 +70,10 @@ {% if form.short_range_option == "short_range_yes" %}
@@ -84,7 +87,10 @@ {% endif %} @@ -137,12 +143,12 @@ Scenario P(i) - Expected new cases + {% if form.occupancy_format == "static" %}Expected new cases{% endif %} {% for scenario_name, scenario_stats in alternative_scenarios.stats.items() %} - {%if (( scenario_stats.probability_of_infection > red_prob_lim) or (scenario_stats.expected_new_cases >= 1)) %} + {%if (( scenario_stats.probability_of_infection > red_prob_lim) or (form.occupancy_format == "static" and scenario_stats.expected_new_cases >= 1)) %} {% elif (orange_prob_lim <= scenario_stats.probability_of_infection <= red_prob_lim) %} @@ -151,7 +157,7 @@ {% endif%} {{ scenario_name }} {{ scenario_stats.probability_of_infection | non_zero_percentage }} - {{ scenario_stats.expected_new_cases | float_format }} + {% if form.occupancy_format == "static" %}{{ scenario_stats.expected_new_cases | float_format }}{% endif %} {% endfor %} diff --git a/cern_caimira/tests/test_report_generator.py b/cern_caimira/tests/test_report_generator.py index a6a5a031..f15833a2 100644 --- a/cern_caimira/tests/test_report_generator.py +++ b/cern_caimira/tests/test_report_generator.py @@ -180,6 +180,6 @@ def test_static_vs_dynamic_occupancy_from_form(baseline_form_data, data_registry 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) - np.testing.assert_almost_equal(static_occupancy_report_data['expected_new_cases'], dynamic_occupancy_report_data['expected_new_cases'], 1) + assert dynamic_occupancy_report_data['expected_new_cases'] == None assert dynamic_occupancy_report_data['prob_probabilistic_exposure'] == None \ No newline at end of file