changes after rebase from master

This commit is contained in:
lrdossan 2024-09-16 14:48:46 +02:00
parent e545d99190
commit b92bbb6334
11 changed files with 147 additions and 101 deletions

View file

@ -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()

View file

@ -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,
)

View file

@ -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:

View file

@ -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):

View file

@ -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:

View file

@ -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 \"<class 'list'>\"."],
[[{"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 \"<class 'str'>\"."],
[[{"total_people": 9.8, "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be integer. Got \"<class 'float'>\"."],
[[{"total_people": [10], "start_time": "10:00", "finish_time": "11:00"}], "Total number of people should be integer. Got \"<class 'list'>\"."],
[[{"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)

View file

@ -1,5 +1,3 @@
from typing import Type
import numpy as np
import pytest
from caimira.calculator.validators.virus import virus_validator

View file

@ -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()

View file

@ -104,7 +104,7 @@
</div>
{% endblock long_range_warning_animation %}
</div>
<h6><b>Expected new cases:</b> {{ long_range_expected_cases | float_format }}</h6>
{% if form.occupancy_format == "static" %}<h6><b>Expected new cases:</b> {{ long_range_expected_cases | float_format }}</h6>{% endif %}
</div>
<br>
{% if form.short_range_option == "short_range_yes" %}
@ -126,19 +126,27 @@
</div>
{% endblock warning_animation %}
</div>
<h6><b>Expected new cases:</b> {{ expected_new_cases | float_format }}</h6>
{% if form.occupancy_format == "static" %}
<h6><b>Expected new cases:</b> {{ expected_new_cases | float_format }}</h6>
{% endif %}
</div>
{% endif %}
<div class="d-flex">
{% block report_summary %}
<div class="flex-row align-self-center">
<div class="align-self-center alert alert-dark mb-0" role="alert">
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>*.
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>
{% endif %}*.
</div>
{% if form.short_range_option == "short_range_yes" %}
<br>
<div class="align-self-center alert alert-dark mb-0" role="alert">
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>.
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>
{% endif %}.
</div>
{% endif %}
{% block probabilistic_exposure_probability %}
@ -302,7 +310,7 @@
<tr>
<th>Scenario</th>
<th>P(I)</th>
<th>Expected new cases</th>
{% if form.occupancy_format == "static" %}<th>Expected new cases</th>{% endif %}
</tr>
</thead>
<tbody>
@ -310,7 +318,7 @@
<tr>
<td> {{ scenario_name }}</td>
<td> {{ scenario_stats.probability_of_infection | non_zero_percentage }}</td>
<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>
{% if form.occupancy_format == "static" %}<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>
@ -630,11 +638,11 @@
<ul>
{% for interaction in form.short_range_interactions %}
<li>Interaction no. {{ loop.index }}:
<ul>
<li>Expiratory activity: {{ "Shouting/Singing" if interaction.expiration == "Shouting" else interaction.expiration }} </li>
<li>Start time: {{ interaction.start_time }} </li>
<li>Duration: {{ interaction.duration }} {{ "minutes" if interaction.duration|float > 1 else "minute" }}</li>
</ul>
<ul>
<li>Expiratory activity: {{ "Shouting/Singing" if interaction.expiration == "Shouting" else interaction.expiration }} </li>
<li>Start time: {{ interaction.start_time }} </li>
<li>Duration: {{ interaction.duration }} {{ "minutes" if interaction.duration|float > 1 else "minute" }}</li>
</ul>
</li>
{% endfor %}
</ul>

View file

@ -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 @@
<div class="alert alert-success mb-0" role="alert">
<strong>Acceptable:</strong>
{% endif %}
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{long_range_prob_inf | non_zero_percentage}}</b> and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>*.
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{long_range_prob_inf | non_zero_percentage}}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>
{% endif %}*.
</div>
{% if form.short_range_option == "short_range_yes" %}
<br>
@ -84,7 +87,10 @@
<div class="alert alert-success mb-0" role="alert">
<strong>Acceptable:</strong>
{% endif %}
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>.
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>
{% endif %}.
</div>
{% endif %}
@ -137,12 +143,12 @@
<tr>
<th>Scenario</th>
<th>P(i)</th>
<th>Expected new cases</th>
{% if form.occupancy_format == "static" %}<th>Expected new cases</th>{% endif %}
</tr>
</thead>
<tbody>
{% 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)) %}
<tr class="alert-danger">
{% elif (orange_prob_lim <= scenario_stats.probability_of_infection <= red_prob_lim) %}
<tr class="alert-warning">
@ -151,7 +157,7 @@
{% endif%}
<td> {{ scenario_name }}</td>
<td> {{ scenario_stats.probability_of_infection | non_zero_percentage }}</td>
<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>
{% if form.occupancy_format == "static" %}<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>

View file

@ -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