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 | +
|---|---|---|
| {{ scenario_name }} | +{{ scenario_stats.probability_of_infection | int_format }}% | +{{ scenario_stats.expected_new_cases | float_format }} | +