diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index e3113dcb..789d8865 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -256,6 +256,10 @@ def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, mc.Exp without_mask_or_vent = dataclass_utils.replace(without_mask, ventilation_type='no_ventilation') scenarios['No ventilation with Type I masks'] = with_mask_no_vent.build_mc_model() scenarios['Neither ventilation nor masks'] = without_mask_or_vent.build_mc_model() + + else: + no_short_range_alternative = dataclass_utils.replace(form, short_range_interactions=[]) + scenarios['Base scenario without short-range interactions'] = no_short_range_alternative.build_mc_model() return scenarios diff --git a/cara/apps/calculator/static/js/report.js b/cara/apps/calculator/static/js/report.js index 56ba86bc..bd41d380 100644 --- a/cara/apps/calculator/static/js/report.js +++ b/cara/apps/calculator/static/js/report.js @@ -103,7 +103,7 @@ function draw_plot(svg_id) { sr_unique_activities = [...new Set(short_range_expirations)] if (show_sr_legend) { - // Long range cumulative dose line legend - line and area + // Long-range cumulative dose line legend - line and area var legendLongCumulativeIcon = vis.append('line') .style("stroke-dasharray", "5 5") //dashed array for line .attr('stroke-width', '2') @@ -113,18 +113,18 @@ function draw_plot(svg_id) { .text('Long-range cumulative dose') .style('font-size', '15px') .attr('opacity', 0); - // Short range area icon + // Short-range area icon var legendShortRangeAreaIcon = {}; sr_unique_activities.forEach((b, index) => { legendShortRangeAreaIcon[index] = vis.append('rect') .attr('width', 20) .attr('height', 15); - // Short range area icon colors + // Short-range area icon colors if (sr_unique_activities[index] == 'Breathing') legendShortRangeAreaIcon[index].attr('fill', 'red').attr('fill-opacity', '0.2'); else if (sr_unique_activities[index] == 'Speaking') legendShortRangeAreaIcon[index].attr('fill', 'green').attr('fill-opacity', '0.1'); else legendShortRangeAreaIcon[index].attr('fill', 'blue').attr('fill-opacity', '0.1'); }); - // Short range area text + // Short-range area text var legendShortRangeText = {}; sr_unique_activities.forEach((b, index) => { legendShortRangeText[index] = vis.append('text') @@ -432,9 +432,9 @@ function draw_plot(svg_id) { if (show_sr_legend) { sr_unique_activities.forEach((b, index) => { legendShortRangeAreaIcon[index].attr('x', legend_x_start) - .attr('y', graph_height + 4 * size - 15/2); + .attr('y', graph_height + (4 + index) * size - 15/2); legendShortRangeText[index].attr('x', legend_x_start + space_between_text_icon) - .attr('y', graph_height + 4 * size + text_height); + .attr('y', graph_height + (4 + index) * size + text_height); }); legendLongCumulativeIcon.attr("x1", legend_x_start) .attr("x2", legend_x_start + 20) @@ -541,7 +541,7 @@ function draw_plot(svg_id) { // Generate the alternative scenarios plot using d3 library. // 'alternative_scenarios' is a dictionary with all the alternative scenarios // 'times' is a list of times for all the scenarios -// The method is prepared to consider short range interactions if needed. +// The method is prepared to consider short-range interactions if needed. function draw_alternative_scenarios_plot(concentration_plot_svg_id, alternative_plot_svg_id) { // H:M format var time_format = d3.timeFormat('%H:%M'); diff --git a/cara/apps/static/images/long_range_anim.png b/cara/apps/static/images/long_range_anim.png new file mode 100644 index 00000000..15e9f937 Binary files /dev/null and b/cara/apps/static/images/long_range_anim.png differ diff --git a/cara/apps/static/images/short_range_anim.png b/cara/apps/static/images/short_range_anim.png new file mode 100644 index 00000000..b1ab82a9 Binary files /dev/null and b/cara/apps/static/images/short_range_anim.png differ diff --git a/cara/apps/templates/base/calculator.report.html.j2 b/cara/apps/templates/base/calculator.report.html.j2 index 45a8ff0d..38413851 100644 --- a/cara/apps/templates/base/calculator.report.html.j2 +++ b/cara/apps/templates/base/calculator.report.html.j2 @@ -46,6 +46,13 @@
+ {% if form.short_range_option == "short_range_yes" %} + {% set scenario = alternative_scenarios.stats.values() | first %} + {% set long_range_prob_inf = scenario.probability_of_infection %} + {% else %} + {% set long_range_prob_inf = prob_inf %} + {% endif %} + {% block report_results %}
Results @@ -59,36 +66,73 @@

-
-
-
Probability of infection (%)
-
- {% block warning_animation %} -
- {{prob_inf | non_zero_percentage}} - - - - -
- {% endblock warning_animation %} +
+
+ Probability of infection (%)
+ {% if form.short_range_option == "short_range_yes" %} + Without short-range interactions + {% endif %} +
+
+ +
+ {% block long_range_warning_animation %} +
+ {{long_range_prob_inf | non_zero_percentage}} + + + + +
+ {% endblock long_range_warning_animation %} +
-
-
+
+ {% if form.short_range_option == "short_range_yes" %} +
+
+ Probability of infection (%)
+ With short-range interactions +
+
+ +
+ {% block warning_animation %} +
+ {{prob_inf | non_zero_percentage}} + + + + +
+ {% endblock warning_animation %} +
+
+ {% endif %} +
{% block report_summary %} -
-

{% block report_summary_footnote %} {% endblock report_summary_footnote %}
-

* The results are based on the parameters and assumptions published in the CARA publication: doi.org/10.1098/rsfs.2021.0076.


+

* The results are based on the parameters and assumptions published in the CARA publication: doi.org/10.1098/rsfs.2021.0076.


{% if form.short_range_option == "short_range_yes" %} {% if 'Speaking' in form.short_range_interactions|string or 'Shouting' in form.short_range_interactions|string %} diff --git a/cara/apps/templates/cern/calculator.report.html.j2 b/cara/apps/templates/cern/calculator.report.html.j2 index cb518441..c55cd6dd 100644 --- a/cara/apps/templates/cern/calculator.report.html.j2 +++ b/cara/apps/templates/cern/calculator.report.html.j2 @@ -1,6 +1,25 @@ {% extends "base/calculator.report.html.j2" %} {% set cern_level = 'green-1' %} + +{% if form.short_range_option == "short_range_yes" %} + {% set scenario = alternative_scenarios.stats.values() | first %} + {% set long_range_prob_inf = scenario.probability_of_infection %} +{% else %} + {% set long_range_prob_inf = prob_inf %} +{% endif %} + +{% if ((long_range_prob_inf > 10) or (expected_new_cases >= 1)) %} + {% set long_range_scale_warning = 'red' %} + {% set long_range_warning_color= 'bg-danger' %} +{% elif (2 <= long_range_prob_inf <= 10) %} + {% set long_range_scale_warning = 'orange' %} + {% set long_range_warning_color = 'bg-warning' %} +{% else %} + {% set long_range_scale_warning = 'green' %} + {% set long_range_warning_color = 'bg-success' %} +{% endif %} + {% if ((prob_inf > 10) or (expected_new_cases >= 1)) %} {% set scale_warning = 'red' %} {% elif (2 <= prob_inf <= 10) %} {% set scale_warning = 'orange' %} {% else %} {% set scale_warning = 'green' %} @@ -12,12 +31,21 @@ {% endblock report_preamble_navtab %} +{% block long_range_warning_animation %} +
+ {{long_range_prob_inf | non_zero_percentage}} + + + + +
+{% endblock long_range_warning_animation %} + {% block warning_animation %} {% if scale_warning == 'red' %} {% set warning_color= 'bg-danger' %} {% elif scale_warning == 'orange' %} {% set warning_color = 'bg-warning' %} {% elif scale_warning == 'green' %} {% set warning_color = 'bg-success' %} {% endif %} -
{{prob_inf | non_zero_percentage}} @@ -28,51 +56,63 @@ {% endblock warning_animation %} {% block report_summary %} - {% set report_message = "Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally, the probability of one exposed occupant getting infected is " + prob_inf | non_zero_percentage + " and the expected number of new cases is " + expected_new_cases | float_format + "*." %}
- {% if scale_warning == 'red' %} +
+ {% if long_range_scale_warning == 'red' %} - {% elif scale_warning == 'orange' %} + {% elif long_range_scale_warning == 'orange' %} - {% elif scale_warning == 'green' %} + {% elif long_range_scale_warning == 'green' %} + {% if form.short_range_option == "short_range_yes" %} +
+ {% if scale_warning == 'red' %} + {% endblock report_summary %} -{% block report_summary_footnote %} +{% block report_summary_footnote %} +
{% if scale_warning == 'red' %} This exceeds the authorised risk threshold or number of expected new cases. The risk level must be reduced before this activity can be undertaken. diff --git a/cara/tests/test_full_algorithm.py b/cara/tests/test_full_algorithm.py index 53a4e99e..744c3982 100644 --- a/cara/tests/test_full_algorithm.py +++ b/cara/tests/test_full_algorithm.py @@ -438,7 +438,7 @@ class SimpleExposureModel(SimpleConcentrationModel): def dose(self) -> _VectorisedFloat: """ total deposited dose (integrated over time and over particle - diameters), including short and long range. + diameters), including short and long-range. """ result = 0. for t1,t2 in self.infected_presence.boundaries(): diff --git a/requirements.txt b/requirements.txt index 724bf5cc..a7a970cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -64,6 +64,7 @@ python-dateutil==2.8.2 pyzmq==22.1.0 requests==2.26.0 requests-unixsocket==0.2.0 +retry==0.9.2 scikit-learn==0.24.2 scipy==1.7.0 Send2Trash==1.7.1 @@ -76,6 +77,7 @@ threadpoolctl==2.2.0 timezonefinder==5.2.0 tornado==6.1 traitlets==5.0.5 +types-retry==0.9.7 urllib3==1.26.6 voila==0.2.10 wcwidth==0.2.5 diff --git a/setup.py b/setup.py index 00accdec..bc035ea7 100644 --- a/setup.py +++ b/setup.py @@ -30,10 +30,12 @@ REQUIREMENTS: dict = { 'numpy', 'psutil', 'python-dateutil', + 'retry', 'scipy', 'sklearn', 'timezonefinder', 'tornado', + 'types-retry', 'voila >=0.2.4', ], 'app': [], @@ -42,10 +44,8 @@ REQUIREMENTS: dict = { 'pytest-mypy', 'pytest-tornasync', 'numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git', - 'retry', 'types-dataclasses', 'types-python-dateutil', - 'types-retry', ], 'dev': [ 'jupyterlab',