From dd60c0ef2371acc8f0d92b0100338d0962d4af09 Mon Sep 17 00:00:00 2001
From: Phil Elson
Date: Fri, 20 Nov 2020 14:03:18 +0100
Subject: [PATCH] Implement alternative scenarios infrastructure for the
calculator.
---
cara/apps/calculator/model_generator.py | 2 +-
cara/apps/calculator/report_generator.py | 56 +++++++++++++++++++
cara/apps/calculator/templates/report.html.j2 | 28 +++++++++-
3 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py
index bdb1cf80..1ba26cef 100644
--- a/cara/apps/calculator/model_generator.py
+++ b/cara/apps/calculator/model_generator.py
@@ -384,7 +384,7 @@ def baseline_raw_form_data():
'mask_type': 'Type I',
'mask_wearing': 'removed',
'mechanical_ventilation_type': '',
- 'model_version': 'BetaV1.1.0',
+ 'model_version': 'v1.1.0',
'opening_distance': '0.2',
'recurrent_event_month': 'January',
'room_number': '123',
diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py
index c98355e9..aa2585a5 100644
--- a/cara/apps/calculator/report_generator.py
+++ b/cara/apps/calculator/report_generator.py
@@ -3,6 +3,7 @@ import dataclasses
from datetime import datetime
import io
from pathlib import Path
+import typing
import jinja2
import matplotlib
@@ -99,6 +100,59 @@ def minutes_to_time(minutes: int) -> str:
return f"{hour_string}:{minute_string}"
+def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, models.ExposureModel]:
+ scenarios = {}
+
+ with_mask = dataclasses.replace(form, mask_wearing='continuous')
+ without_mask = dataclasses.replace(form, mask_wearing='removed')
+
+ scenarios['With mask'] = with_mask.build_model()
+ scenarios['Without mask'] = without_mask.build_model()
+
+ return scenarios
+
+
+def comparison_plot(scenarios: typing.Dict[str, models.ExposureModel]):
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1)
+
+ resolution = 350
+ times = None
+ for name, model in scenarios.items():
+ if times is None:
+ t_start = min(model.exposed.presence.boundaries()[0][0],
+ model.concentration_model.infected.presence.boundaries()[0][0])
+ t_end = max(model.exposed.presence.boundaries()[-1][1],
+ model.concentration_model.infected.presence.boundaries()[-1][1])
+ times = np.linspace(t_start, t_end, resolution)
+ concentrations = [model.concentration_model.concentration(time) for time in times]
+
+ ax.plot(times, concentrations, label=name)
+
+ ax.legend()
+ ax.spines['right'].set_visible(False)
+ ax.spines['top'].set_visible(False)
+
+ ax.set_xlabel('Time (hour of day)')
+ ax.set_ylabel('Concentration ($q/m^3$)')
+ ax.set_title('Concentration of infectious quanta')
+
+ return fig
+
+
+def comparison_report(scenarios: typing.Dict[str, models.ExposureModel]):
+ statistics = {}
+ for name, model in scenarios.items():
+ statistics[name] = {
+ 'probability_of_infection': model.infection_probability(),
+ 'expected_new_cases': model.expected_new_cases(),
+ }
+ return {
+ 'plot': embed_figure(comparison_plot(scenarios)),
+ 'stats': statistics,
+ }
+
+
def build_report(model: models.ExposureModel, form: FormData):
now = datetime.now()
time = now.strftime("%d/%m/%Y %H:%M:%S")
@@ -112,6 +166,8 @@ def build_report(model: models.ExposureModel, form: FormData):
}
context.update(calculate_report_data(model))
+ alternative_scenarios = manufacture_alternative_scenarios(form)
+ context['alternative_scenarios'] = comparison_report(alternative_scenarios)
cara_templates = Path(__file__).parent.parent / "templates"
calculator_templates = Path(__file__).parent / "templates"
diff --git a/cara/apps/calculator/templates/report.html.j2 b/cara/apps/calculator/templates/report.html.j2
index 451e60f2..036ccda4 100644
--- a/cara/apps/calculator/templates/report.html.j2
+++ b/cara/apps/calculator/templates/report.html.j2
@@ -166,7 +166,33 @@
{% endfor %}
-
+
+
+ Alternative scenarios:
+
+
+
+
+
+
+ | Scenario |
+ P(i) |
+ Expected new cases |
+
+
+
+ {% for scenario_name, scenario_stats in alternative_scenarios.stats.items() %}
+
+ | {{ scenario_name }} |
+ {{ scenario_stats.probability_of_infection | int_format }}% |
+ {{ scenario_stats.expected_new_cases | float_format }} |
+
+ {% endfor %}
+
+
+
+
+