From f19d76589c30abfa57f79ef2047762d06bb5c51e Mon Sep 17 00:00:00 2001 From: gaazzopa Date: Thu, 5 Nov 2020 21:22:03 +0100 Subject: [PATCH 1/3] Updated report display --- cara/apps/calculator/report_generator.py | 90 ++++--------------- cara/apps/calculator/static/css/report.css | 19 ++-- cara/apps/calculator/templates/report.html.j2 | 64 +++++++------ 3 files changed, 67 insertions(+), 106 deletions(-) diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index 3edfaa37..2ce64f03 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -2,84 +2,28 @@ from datetime import datetime from pathlib import Path import jinja2 -import numpy as np from cara import models from .model_generator import FormData -def calculate_report_data(model: models.Model): - resolution = 600 - times = list(np.linspace(0, 10, resolution)) - concentrations = [model.concentration(time) for time in times] - highest_const = max(concentrations) - prob = model.infection_probability() - er = model.infected.emission_rate(0.1) - exposed_occupants = model.exposed_occupants - r0 = prob * exposed_occupants / 100 - return { - "times": times, - "concentrations": concentrations, - "highest_const": highest_const, - "prob_inf": prob, - "emission_rate": er, - "exposed_occupants": exposed_occupants, - "R0": r0, - } - - def build_report(model: models.Model, form: FormData): now = datetime.now() - time = now.strftime("%d/%m/%Y %H:%M:%S") - request = {"the": "form", "request": "data"} + time = now.strftime('%d/%m/%Y %H:%M:%S') + request = {'the': 'form', 'request': 'data'} + context = {'model': model, 'request': request, 'creation_date': time, 'model_version': 'Beta v1.0.0', + 'simulation_name': 'SAMPLE', 'room_number': '40/1-02A', 'room_volume': 30, 'ventilation_type': 'natural_ventilation', + 'air_supply': 1, 'air_changes': 2, 'windows_number': 5, 'window_height': 2, 'window_width': 1, + 'opening_distance': 0.05, 'windows_open': '20 minutes every 2 hours', 'hepa_option': 'No', 'total_people': 8, + 'infected_people': 7, 'activity_type': 'Office work – typical scenario with all persons seated, talking', + 'activity_start': '00:00', 'activity_finish': '01:15', 'exposure_start': '00:00', 'exposure_finish': '01:15', + 'event_type' : 'single_event', 'single_event_date': '5th November', 'recurrent_event_month': 'November', + 'lunch_option': 'No', 'lunch_start': '00:00', 'lunch_finish': '01:15', 'coffee_breaks': 4,'coffee_duration': 15, + 'coffee_times': [['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00']], 'mask_wearing': 'No', + 'infection_probability': round(model.infection_probability(), 2), 'reproduction_rate': 2} - context = { - "model": model, - "form": form, - "creation_date": time, - "model_version": "Beta v1.0.0", - "simulation_name": "SAMPLE", - "room_number": "40/1-02A", - "room_volume": 30, - "mechanical_ventilation": "Yes", - "air_supply": 1, - "air_changes": 2, - "windows_number": 5, - "window_height": 2, - "window_width": 1, - "opening_distance": 0.05, - "windows_open": "20 minutes every 2 hours", - "hepa_filtration": "No", - "total_people": 8, - "infected_people": 7, - "activity_type": "Office work – typical scenario with all persons seated, talking", - "activity_start": "00:00", - "activity_finish": "01:15", - "exposure_start": "00:00", - "exposure_finish": "01:15", - "single_event_date": "5th November", - "lunch_option": "Yes", - "lunch_start": "00:00", - "lunch_finish": "01:15", - "coffee_option": "Yes", - "coffee_number": 4, - "coffee_duration": 15, - "coffee_start1": "00:00", - "coffee_finish1": "00:00", - "coffee_start2": "00:00", - "coffee_finish2": "00:00", - "coffee_start3": "00:00", - "coffee_finish3": "00:00", - "coffee_start4": "00:00", - "coffee_finish4": "00:00", - "mask_wearing": "Yes", - "infection_probability": round(model.infection_probability(), 2), - "reproduction_rate": 2, - } - - context.update(calculate_report_data(model)) - - p = Path(__file__).parent / "templates" - env = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(p))) - template = env.get_template("report.html.j2") - return template.render(**context) + p = Path(__file__).parent / 'templates' + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(Path(p))) + template = env.get_template('report.html.j2') + return template.render(**context) \ No newline at end of file diff --git a/cara/apps/calculator/static/css/report.css b/cara/apps/calculator/static/css/report.css index eb4363a5..1ecf4eaf 100644 --- a/cara/apps/calculator/static/css/report.css +++ b/cara/apps/calculator/static/css/report.css @@ -6,13 +6,6 @@ padding: 20px; } -.image { - display: flex; - align-items: center; - justify-content: center; - font-size: 13pt; -} - h1{ text-align: center; } @@ -41,3 +34,15 @@ p.result_title { font-weight: bold; font-size: 15pt; } + +.image { + display: flex; + justify-content: center; + font-size: 13pt; +} + +.discalimer { + display: flex; + justify-content: center; + font-size: 10pt; +} \ No newline at end of file diff --git a/cara/apps/calculator/templates/report.html.j2 b/cara/apps/calculator/templates/report.html.j2 index c714ec60..3fdc8dc1 100644 --- a/cara/apps/calculator/templates/report.html.j2 +++ b/cara/apps/calculator/templates/report.html.j2 @@ -9,9 +9,6 @@ -

- VERY IMPORTANT DISCLAIMER

-

Output from CARA - COVID Airborne Risk Assessment tool

Created {{ creation_date }} using model version {{ model_version }}


@@ -26,15 +23,19 @@

Ventilation data:

Event data:

@@ -54,29 +55,41 @@
  • Number of attendees and infected people: {{ total_people }} in attendance, of whom {{ infected_people }} are infected.

  • Activity type: {{ activity_type }}

  • Exposure time (presence of infected person):

  • + {% if event_type == "single_event"%}
  • Single event on {{ single_event_date }}

  • + {% endif %} + {% if event_type == "recurrent_event"%} +
  • Recurrent event for the month of {{ recurrent_event_month }}

  • + {% endif %} +

    Break data:

    - -

    Mask wearing:

    Results:

    -

    In this scenario, the estimated probability of one exposed occupant getting infected (Pi) is {{ infection_probability }} % and the estimated basic reproduction rate (R0) rate is {{ reproduction_rate }}. If you have selected a recurrent event, this is the probability per day, and is cumulative over the number of days.

    -

    Exposure graph: - +

    In this scenario, the estimated probability of one exposed occupant getting infected P(i) is {{ infection_probability }} % and the estimated basic reproduction rate (R0) rate is {{ reproduction_rate }}. This probability estimate is per simulated time period, i.e. if you have simulated a working day, which will be repeated n times per week, the cumulative probability of infection per person is n x P(i).

    +

    Exposure graph:

    \ No newline at end of file From 0f415abad95788d90b9c7a3c04cc2c53a739e884 Mon Sep 17 00:00:00 2001 From: gaazzopa Date: Thu, 5 Nov 2020 21:22:57 +0100 Subject: [PATCH 2/3] Added disclaimer to report --- cara/apps/calculator/templates/report.html.j2 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cara/apps/calculator/templates/report.html.j2 b/cara/apps/calculator/templates/report.html.j2 index 3fdc8dc1..efcdd3a3 100644 --- a/cara/apps/calculator/templates/report.html.j2 +++ b/cara/apps/calculator/templates/report.html.j2 @@ -99,5 +99,16 @@

    Results:

    In this scenario, the estimated probability of one exposed occupant getting infected P(i) is {{ infection_probability }} % and the estimated basic reproduction rate (R0) rate is {{ reproduction_rate }}. This probability estimate is per simulated time period, i.e. if you have simulated a working day, which will be repeated n times per week, the cumulative probability of infection per person is n x P(i).

    Exposure graph:

    + +






    +
    +

    +

    Disclaimer:

    +

    The risk assessment tool simulates the long range airborne spread SARS-CoV-2 virus in a finite volume, assuming a homogenous mixture, and estimates the risk of COVID-19 infection thereto. The results DO NOT include short-range airborne exposure (where the physical distance plays a factor) nor the other know modes of transmission of SARS-CoV-2. Hence, this model implies that proper physical distancing, good hand hygiene and other barrier measures are ensured.

    + It is based on current scientific data and can be used to measures the effectiveness of different mitigation measures.

    + Note that this model is based on a deterministic approach, i.e., at least one person is infected and shedding viruses into the volume. Nonetheless, it is also important to understand that the absolute risk of infection is uncertain as it will depend on the probability that someone infected attends the event. The model is mostly useful to compare the impact and effectiveness of mitigation measures such as ventilation, filtration, exposure time, activity and the size of the room on long-range airborne transmission of COVID-19 in indoor settings.

    + This application is meant for informative and educational purposes. The user can be able to adapt different settings and measure the relative impact on the estimated infection probabilities to allow for a targeted decision making and investment. The user should acknowledge that until the virus is in circulation among the population, the notion of 'zero risk' or a 'completely safe scenario' does not exist. Each event is unique and the results are as accurate as the inputs. The app is based on our scientific understanding of infectious diseases transmission, exposure and aerosol science as of November 2020.

    + We do not assume responsibility for any injury or damage to persons or property arising out of or related to any use of this app.

    +
    \ No newline at end of file From 881669ab0953e69baed0e0bef3b2478a02518f13 Mon Sep 17 00:00:00 2001 From: gaazzopa Date: Thu, 5 Nov 2020 23:35:33 +0100 Subject: [PATCH 3/3] Updated report to use form values --- cara/apps/calculator/report_generator.py | 81 +++++++++++++++---- cara/apps/calculator/static/css/report.css | 9 +-- cara/apps/calculator/templates/report.html.j2 | 3 +- 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index 2ce64f03..db6db86b 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -2,28 +2,77 @@ from datetime import datetime from pathlib import Path import jinja2 +import numpy as np from cara import models from .model_generator import FormData +def calculate_report_data(model: models.Model): + resolution = 600 + times = list(np.linspace(0, 10, resolution)) + concentrations = [model.concentration(time) for time in times] + highest_const = max(concentrations) + prob = model.infection_probability() + er = model.infected.emission_rate(0.1) + exposed_occupants = model.exposed_occupants + r0 = prob * exposed_occupants / 100 + return { + "times": times, + "concentrations": concentrations, + "highest_const": highest_const, + "prob_inf": prob, + "emission_rate": er, + "exposed_occupants": exposed_occupants, + "R0": r0, + } + + def build_report(model: models.Model, form: FormData): now = datetime.now() - time = now.strftime('%d/%m/%Y %H:%M:%S') - request = {'the': 'form', 'request': 'data'} - context = {'model': model, 'request': request, 'creation_date': time, 'model_version': 'Beta v1.0.0', - 'simulation_name': 'SAMPLE', 'room_number': '40/1-02A', 'room_volume': 30, 'ventilation_type': 'natural_ventilation', - 'air_supply': 1, 'air_changes': 2, 'windows_number': 5, 'window_height': 2, 'window_width': 1, - 'opening_distance': 0.05, 'windows_open': '20 minutes every 2 hours', 'hepa_option': 'No', 'total_people': 8, - 'infected_people': 7, 'activity_type': 'Office work – typical scenario with all persons seated, talking', - 'activity_start': '00:00', 'activity_finish': '01:15', 'exposure_start': '00:00', 'exposure_finish': '01:15', - 'event_type' : 'single_event', 'single_event_date': '5th November', 'recurrent_event_month': 'November', - 'lunch_option': 'No', 'lunch_start': '00:00', 'lunch_finish': '01:15', 'coffee_breaks': 4,'coffee_duration': 15, - 'coffee_times': [['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00']], 'mask_wearing': 'No', - 'infection_probability': round(model.infection_probability(), 2), 'reproduction_rate': 2} + time = now.strftime("%d/%m/%Y %H:%M:%S") + request = {"the": "form", "request": "data"} + context = { + 'model': model, + 'request': request, + 'creation_date': time, + 'model_version': 'Beta v1.0.0', + 'simulation_name': form.simulation_name, + 'room_number': form.room_number, + 'room_volume': form.room_volume, + 'ventilation_type': form.ventilation_type, + 'air_supply': form.air_supply, + 'air_changes': form.air_changes, + 'windows_number': form.windows_number, + 'window_height': form.window_height, + 'window_width': form.window_width, + 'opening_distance': form.opening_distance, + 'windows_open': form.windows_open, + 'hepa_option': 'No', + 'total_people': form.total_people, + 'infected_people': form.infected_people, + 'activity_type': form.activity_type, + 'activity_start': form.activity_start, + 'activity_finish': form.activity_finish, + 'exposure_start': '00:00', + 'exposure_finish': '01:15', + 'event_type': form.event_type, + 'single_event_date': form.single_event_date, + 'recurrent_event_month': form.recurrent_event_month, + 'lunch_option': form.lunch_option, + 'lunch_start': form.lunch_start, + 'lunch_finish': form.lunch_finish, + 'coffee_breaks': form.coffee_breaks, + 'coffee_duration': form.coffee_duration, + 'coffee_times': [['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00'], ['00:00','00:00']], + 'mask_wearing': form.mask_wearing, + 'infection_probability': round(model.infection_probability(), 2), + 'reproduction_rate': 2 + } - p = Path(__file__).parent / 'templates' - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(Path(p))) - template = env.get_template('report.html.j2') + context.update(calculate_report_data(model)) + + p = Path(__file__).parent / "templates" + env = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(p))) + template = env.get_template("report.html.j2") return template.render(**context) \ No newline at end of file diff --git a/cara/apps/calculator/static/css/report.css b/cara/apps/calculator/static/css/report.css index 1ecf4eaf..0ad87e49 100644 --- a/cara/apps/calculator/static/css/report.css +++ b/cara/apps/calculator/static/css/report.css @@ -6,7 +6,7 @@ padding: 20px; } -h1{ +h1 { text-align: center; } @@ -36,13 +36,10 @@ p.result_title { } .image { - display: flex; - justify-content: center; + text-align: center; font-size: 13pt; } .discalimer { - display: flex; - justify-content: center; - font-size: 10pt; + font-size: 12pt; } \ No newline at end of file diff --git a/cara/apps/calculator/templates/report.html.j2 b/cara/apps/calculator/templates/report.html.j2 index efcdd3a3..9d8586dc 100644 --- a/cara/apps/calculator/templates/report.html.j2 +++ b/cara/apps/calculator/templates/report.html.j2 @@ -102,8 +102,7 @@






    -

    -

    Disclaimer:

    +

    Disclaimer:

    The risk assessment tool simulates the long range airborne spread SARS-CoV-2 virus in a finite volume, assuming a homogenous mixture, and estimates the risk of COVID-19 infection thereto. The results DO NOT include short-range airborne exposure (where the physical distance plays a factor) nor the other know modes of transmission of SARS-CoV-2. Hence, this model implies that proper physical distancing, good hand hygiene and other barrier measures are ensured.

    It is based on current scientific data and can be used to measures the effectiveness of different mitigation measures.

    Note that this model is based on a deterministic approach, i.e., at least one person is infected and shedding viruses into the volume. Nonetheless, it is also important to understand that the absolute risk of infection is uncertain as it will depend on the probability that someone infected attends the event. The model is mostly useful to compare the impact and effectiveness of mitigation measures such as ventilation, filtration, exposure time, activity and the size of the room on long-range airborne transmission of COVID-19 in indoor settings.