Merge branch 'master' of https://gitlab.cern.ch/cara/cara into feature/model_vectorisation_pt3
This commit is contained in:
commit
2e928b5fef
22 changed files with 368 additions and 108 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Copyright 2020-2021 CERN. All rights not expressly granted are reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
56
README.md
56
README.md
|
|
@ -1,32 +1,62 @@
|
|||
# CARA - COVID Airborne Risk Assessment
|
||||
|
||||
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
|
||||
|
||||
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
|
||||
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
|
||||
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
|
||||
|
||||
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 therein.
|
||||
The results DO NOT include short-range airborne exposure (where the physical distance is a significant factor) nor the other known modes of SARS-CoV-2 transmission.
|
||||
Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.
|
||||
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of February 2021.
|
||||
It can be used to compare the effectiveness of different airborne-related risk mitigation measures.
|
||||
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and
|
||||
the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.
|
||||
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities.
|
||||
The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk.
|
||||
While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or 'completely safe scenario' does not exist.
|
||||
Each event modelled is unique, and the results generated therein are only as accurate as the inputs and assumptions.
|
||||
|
||||
## Authors
|
||||
CARA was developed by following members of CERN - European Council for Nuclear Research (visit https://home.cern/):
|
||||
|
||||
Andre Henriques<sup>1</sup>, Marco Andreini<sup>1</sup>, Gabriella Azzopardi<sup>2</sup>, James Devine<sup>3</sup>, Philip Elson<sup>4</sup>, Nicolas Mounet<sup>2</sup>, Markus Kongstein Rognlien<sup>2,6</sup>, Nicola Tarocco<sup>5</sup>
|
||||
|
||||
<sup>1</sup>HSE Unit, Occupational Health & Safety Group, CERN<br>
|
||||
<sup>2</sup>Beams Department, Accelerators and Beam Physics Group, CERN<br>
|
||||
<sup>3</sup>Experimental Physics Department, Safety Office, CERN<br>
|
||||
<sup>4</sup>Beams Department, Controls Group, CERN<br>
|
||||
<sup>5</sup>Information Technology Department, Collaboration, Devices & Applications Group, CERN<br>
|
||||
<sup>6</sup>Norwegian University of Science and Technology<br>
|
||||
|
||||
###citation
|
||||
A. Henriques, M. Andreini, G. Azzopardi, J. Devine, P. Elson, N. Mounet, M. Kongstein, N. Tarocco. CARA - COVID Airborne Risk Assessment tools. CERN (2021).
|
||||
|
||||
|
||||
## Applications
|
||||
|
||||
### COVID Calculator
|
||||
|
||||
A risk assessment tool which simulates the long range airborne spread of the
|
||||
SARS-CoV-2 virus for space managers.
|
||||
A risk assessment tool which simulates the long range airborne spread of the SARS-CoV-2 virus for space managers.
|
||||
|
||||
You can find the CARA COVID Calculator at https://cara.web.cern.ch/calculator/.
|
||||
Please see the [COVID Calculator README for detailed usage instructions](cara/apps/calculator/README.md).
|
||||
|
||||
### CARA Expert App
|
||||
|
||||
A tool to interact with various parameters of the CARA model.
|
||||
This is currently in beta, and can be found at https://cara.web.cern.ch/expert-app.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The code and data of this repository are provided to promote reproducible research.
|
||||
They are not intended for clinical care or commercial use.
|
||||
CARA has not undergone review, approval or certification by competent authorities, and as a result, it cannot be considered as a fully endorsed and reliable tool, namely in the assessment of potential viral emissions from infected hosts to be modelled.
|
||||
|
||||
The software is provided "as is", without warranty of any kind, express or implied,
|
||||
including but not limited to the warranties of merchantability, fitness for a particular
|
||||
purpose and non infringement.
|
||||
In no event shall the authors or copyright holders be liable for any claim, damages
|
||||
or other liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
out of or in connection with the software or the use or other dealings in the software.
|
||||
The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and non-infringement.
|
||||
In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
|
||||
|
||||
|
||||
## Development guide
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
"<img src=\"../files/static/images/cara_logo_text_cern.png\" align=\"center\" style=\"text-align: center; height: 12em;\" /></div>\n",
|
||||
"<p style=\"color: blue; font-weight: bold; font-size: x-large; text-align: center;\" align=\"center\">\n",
|
||||
"Airborne Transmission of SARS-CoV-2\n",
|
||||
"</p>"
|
||||
"</p>\n",
|
||||
"<p style=\"text-align: center;\">\n",
|
||||
"Please see the <a href=\"https://cara.web.cern.ch/\">CARA homepage</a> for details on the methodology, assumptions and limitations of CARA.</p>"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# This module is part of CARA. Please see the repository at
|
||||
# https://gitlab.cern.ch/cara/cara for details of the license and terms of use.
|
||||
"""
|
||||
Documentation for the CARA package
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
# This module is part of CARA. Please see the repository at
|
||||
# https://gitlab.cern.ch/cara/cara for details of the license and terms of use.
|
||||
|
||||
import datetime
|
||||
import base64
|
||||
import html
|
||||
|
|
@ -12,6 +15,7 @@ import zlib
|
|||
import jinja2
|
||||
from tornado.web import Application, RequestHandler, StaticFileHandler
|
||||
|
||||
from . import markdown_tools
|
||||
from . import model_generator
|
||||
from .report_generator import ReportGenerator
|
||||
from .user import AuthenticatedUser, AnonymousUser
|
||||
|
|
@ -183,6 +187,10 @@ def make_app(
|
|||
loader=loader,
|
||||
)
|
||||
|
||||
template_environment.globals['common_text'] = markdown_tools.extract_rendered_markdown_blocks(
|
||||
template_environment.get_template('common_text.md.j2')
|
||||
)
|
||||
|
||||
return Application(
|
||||
urls,
|
||||
debug=debug,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from tornado.ioloop import IOLoop
|
|||
from . import make_app
|
||||
|
||||
|
||||
def configure_parser(parser):
|
||||
def configure_parser(parser) -> argparse.ArgumentParser:
|
||||
parser.add_argument(
|
||||
"--no-debug", help="Don't enable debug mode",
|
||||
action="store_false",
|
||||
|
|
@ -20,8 +20,7 @@ def configure_parser(parser):
|
|||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
configure_parser(parser)
|
||||
parser = configure_parser(argparse.ArgumentParser())
|
||||
args = parser.parse_args()
|
||||
theme_dir = args.theme
|
||||
if theme_dir is not None:
|
||||
|
|
|
|||
73
cara/apps/calculator/markdown_tools.py
Normal file
73
cara/apps/calculator/markdown_tools.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import re
|
||||
|
||||
import jinja2
|
||||
import mistune
|
||||
|
||||
|
||||
HEADER_PATTERN = re.compile(r'\n(#+)(.*)')
|
||||
|
||||
|
||||
def _block_headings(contents: str):
|
||||
"""
|
||||
Return the headings (and the start/end positions of their blocks) of
|
||||
markdown, in reverse order.
|
||||
|
||||
Note that a block ends when the next heading is found, even if that heading
|
||||
is a sub-block of the current one.
|
||||
|
||||
"""
|
||||
all_block_headings = HEADER_PATTERN.finditer(
|
||||
'\n' + contents, re.MULTILINE & re.DOTALL
|
||||
)
|
||||
|
||||
end_pos = None
|
||||
for result in list(all_block_headings)[::-1]:
|
||||
heading = {
|
||||
'start_pos': result.end(),
|
||||
'end_pos': end_pos,
|
||||
'depth': len(result[1]),
|
||||
'heading': result[2].strip(),
|
||||
}
|
||||
end_pos = result.start()
|
||||
yield heading
|
||||
|
||||
|
||||
def extract_block(block_name: str, contents: str) -> str:
|
||||
"""
|
||||
Extract the given header block from the given markdown.
|
||||
|
||||
The result *does not* contain the children headers of the block.
|
||||
|
||||
"""
|
||||
for block in _block_headings(contents):
|
||||
if block['heading'] == block_name:
|
||||
return contents[block['start_pos']: block['end_pos']]
|
||||
else:
|
||||
raise ValueError(f"Heading \"{ block_name }\" not found")
|
||||
|
||||
|
||||
def extract_headings(contents: str) -> list:
|
||||
"""
|
||||
Extract all headers from the given markdown.
|
||||
|
||||
"""
|
||||
headings = []
|
||||
for block in _block_headings(contents):
|
||||
headings.append(block['heading'])
|
||||
return headings
|
||||
|
||||
|
||||
def extract_rendered_markdown_blocks(template: jinja2.Template) -> dict:
|
||||
"""
|
||||
Return a dictionary of all common markdown text blocks, rendered to HTML and
|
||||
uniquely identified by their headings, from the given Jinja2 template.
|
||||
|
||||
"""
|
||||
common_text = template.render()
|
||||
headings = extract_headings(common_text)
|
||||
text_blocks = {}
|
||||
for heading in headings:
|
||||
block = extract_block(heading, common_text)
|
||||
html_content = mistune.markdown(block, escape=False)
|
||||
text_blocks[heading] = html_content
|
||||
return text_blocks
|
||||
|
|
@ -458,14 +458,10 @@ class FormData:
|
|||
|
||||
present_intervals = []
|
||||
|
||||
def hours2time(hours: float):
|
||||
# Convert times like 14.5 to strings, like "14:30"
|
||||
return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}"
|
||||
|
||||
# def add_interval(start, end):
|
||||
|
||||
current_time = start
|
||||
LOG.debug(f"starting time march at {hours2time(current_time/60)} to {hours2time(finish/60)}")
|
||||
LOG.debug(f"starting time march at {_hours2timestring(current_time/60)} to {_hours2timestring(finish/60)}")
|
||||
|
||||
# As we step through the breaks. For each break there are 6 important cases
|
||||
# we must cover. Let S=start; E=end; Bs=Break start; Be=Break end:
|
||||
|
|
@ -480,8 +476,8 @@ class FormData:
|
|||
if current_time >= finish:
|
||||
break
|
||||
|
||||
LOG.debug(f"handling break {hours2time(current_break[0]/60)}-{hours2time(current_break[1]/60)} "
|
||||
f" (current time: {hours2time(current_time/60)})")
|
||||
LOG.debug(f"handling break {_hours2timestring(current_break[0]/60)}-{_hours2timestring(current_break[1]/60)} "
|
||||
f" (current time: {_hours2timestring(current_time/60)})")
|
||||
|
||||
break_s, break_e = current_break
|
||||
case1 = finish <= break_s
|
||||
|
|
@ -494,22 +490,22 @@ class FormData:
|
|||
if case1:
|
||||
LOG.debug(f"case 1: interval entirely before break")
|
||||
present_intervals.append((current_time / 60, finish / 60))
|
||||
LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} "
|
||||
f"- {hours2time(present_intervals[-1][1])}")
|
||||
LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} "
|
||||
f"- {_hours2timestring(present_intervals[-1][1])}")
|
||||
current_time = finish
|
||||
elif case2:
|
||||
LOG.debug(f"case 2: interval straddles start of break")
|
||||
present_intervals.append((current_time / 60, break_s / 60))
|
||||
LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} "
|
||||
f"- {hours2time(present_intervals[-1][1])}")
|
||||
LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} "
|
||||
f"- {_hours2timestring(present_intervals[-1][1])}")
|
||||
current_time = break_e
|
||||
elif case3:
|
||||
LOG.debug(f"case 3: break entirely inside interval")
|
||||
# We add the bit before the break, but not the bit afterwards,
|
||||
# as it may hit another break.
|
||||
present_intervals.append((current_time / 60, break_s / 60))
|
||||
LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} "
|
||||
f"- {hours2time(present_intervals[-1][1])}")
|
||||
LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} "
|
||||
f"- {_hours2timestring(present_intervals[-1][1])}")
|
||||
current_time = break_e
|
||||
elif case4:
|
||||
LOG.debug(f"case 4: interval entirely inside break")
|
||||
|
|
@ -652,6 +648,11 @@ WINDOWS_TYPES = {'window_sliding', 'window_hinged', 'not-applicable'}
|
|||
COFFEE_OPTIONS_INT = {'coffee_break_0': 0, 'coffee_break_1': 1, 'coffee_break_2': 2, 'coffee_break_4': 4}
|
||||
|
||||
|
||||
def _hours2timestring(hours: float):
|
||||
# Convert times like 14.5 to strings, like "14:30"
|
||||
return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}"
|
||||
|
||||
|
||||
def time_string_to_minutes(time: str) -> minutes_since_midnight:
|
||||
"""
|
||||
Converts time from string-format to an integer number of minutes after 00:00
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import base64
|
||||
import dataclasses
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import io
|
||||
import typing
|
||||
import zlib
|
||||
|
|
@ -126,18 +126,20 @@ def img2base64(img_data) -> str:
|
|||
def plot(times, concentrations, model: models.ExposureModel):
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot(1, 1, 1)
|
||||
ax.plot(times, concentrations, lw=2, color='#1f77b4', label='Concentration')
|
||||
datetimes = [datetime(1970, 1, 1) + timedelta(hours=time) for time in times]
|
||||
ax.plot(datetimes, concentrations, lw=2, color='#1f77b4', label='Concentration')
|
||||
ax.spines['right'].set_visible(False)
|
||||
ax.spines['top'].set_visible(False)
|
||||
|
||||
ax.set_xlabel('Time (hour of day)')
|
||||
ax.set_xlabel('Time of day')
|
||||
ax.set_ylabel('Concentration ($q/m^3$)')
|
||||
ax.set_title('Concentration of infectious quanta')
|
||||
ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
|
||||
|
||||
# Plot presence of exposed person
|
||||
for i, (presence_start, presence_finish) in enumerate(model.exposed.presence.boundaries()):
|
||||
plt.fill_between(
|
||||
times, concentrations, 0,
|
||||
datetimes, concentrations, 0,
|
||||
where=(np.array(times) > presence_start) & (np.array(times) < presence_finish),
|
||||
color="#1f77b4", alpha=0.1,
|
||||
label="Presence of exposed person(s)" if i == 0 else ""
|
||||
|
|
@ -240,19 +242,21 @@ def comparison_plot(scenarios: typing.Dict[str, models.ExposureModel]):
|
|||
if times is None:
|
||||
t_start, t_end = model_start_end(model)
|
||||
times = np.linspace(t_start, t_end, resolution)
|
||||
datetimes = [datetime(1970, 1, 1) + timedelta(hours=time) for time in times]
|
||||
concentrations = [model.concentration_model.concentration(time) for time in times]
|
||||
|
||||
if name in dash_styled_scenarios:
|
||||
ax.plot(times, concentrations, label=name, linestyle='--')
|
||||
ax.plot(datetimes, concentrations, label=name, linestyle='--')
|
||||
else:
|
||||
ax.plot(times, concentrations, label=name, linestyle='-', alpha=0.5)
|
||||
ax.plot(datetimes, concentrations, label=name, linestyle='-', alpha=0.5)
|
||||
|
||||
# Place a legend outside of the axes itself.
|
||||
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
|
||||
ax.spines['right'].set_visible(False)
|
||||
ax.spines['top'].set_visible(False)
|
||||
ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
|
||||
|
||||
ax.set_xlabel('Time (hour of day)')
|
||||
ax.set_xlabel('Time of day')
|
||||
ax.set_ylabel('Concentration ($q/m^3$)')
|
||||
ax.set_title('Concentration of infectious quanta')
|
||||
|
||||
|
|
|
|||
|
|
@ -293,11 +293,41 @@
|
|||
<div style="border: 2px solid black; padding: 15px;">
|
||||
{% block disclaimer %}
|
||||
<p class="image"> <img align="middle" src="/calculator/static/images/disclaimer.jpg" width="40" height="40"><b>Disclaimer:</b><br><br></p>
|
||||
<p class="discalimer">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 therein. The results DO NOT include short-range airborne exposure (where the physical distance plays a factor) nor the other known modes of SARS-CoV-2 transmission. Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.<br><br>
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of December 2020 . It can be used to compare the effectiveness of different airborne-related risk mitigation measures.<br><br>
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.<br><br>
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities. The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk. While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or a 'completely safe scenario' does not exist. Each event modelled is unique and the results generated therein are only as accurate as the inputs and assumptions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
|
||||
</p>
|
||||
<p>
|
||||
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
|
||||
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
|
||||
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
|
||||
</p>
|
||||
<p>
|
||||
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 therein.
|
||||
The results DO NOT include short-range airborne exposure (where the physical distance is a significant factor) nor the other known modes of SARS-CoV-2 transmission.
|
||||
Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.
|
||||
</p>
|
||||
<p>
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of February 2021.
|
||||
It can be used to compare the effectiveness of different airborne-related risk mitigation measures.
|
||||
</p>
|
||||
<p>
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and
|
||||
the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.
|
||||
</p>
|
||||
<p>
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities.
|
||||
The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk.
|
||||
While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or 'completely safe scenario' does not exist.
|
||||
Each event modelled is unique, and the results generated therein are only as accurate as the inputs and assumptions.
|
||||
</p>
|
||||
<p>
|
||||
CARA has not undergone review, approval or certification by competent authorities, and as a result, it cannot be considered
|
||||
as a fully endorsed and reliable tool, namely in the assessment of potential viral emissions from infected hosts to be modelled.
|
||||
</p>
|
||||
|
||||
{% endblock disclaimer %}
|
||||
</div>
|
||||
{% endblock disclaimer_container %}
|
||||
|
|
|
|||
|
|
@ -318,17 +318,41 @@ v{{ calculator_version }} <span style="float:right; font-weight:bold">Please sen
|
|||
<button type='submit' id="generate_report">Generate report</button><br><br><br><br>
|
||||
</form>
|
||||
|
||||
<!-- Dialog boxes -->
|
||||
<div id="DIALOG_welcome" title="Welcome to CARA!" class="dialog">
|
||||
<p>This software is provided with a disclaimer and code license.<span id="dots"></span><span id="more" style="display: none;">
|
||||
<br><br><b>Disclaimer:</b><br><br>
|
||||
<span id="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 therein. The results DO NOT include short-range airborne exposure (where the physical distance plays a factor) nor the other known modes of SARS-CoV-2 transmission. Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.<br><br>
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of December 2020.<br><br>
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.<br><br>
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities. The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk. While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or a 'completely safe scenario' does not exist. Each event modelled is unique and the results generated therein are only as accurate as the inputs and assumptions.<br><br>
|
||||
CARA is made available for internal CERN use only. It is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites. For use outside of this scope, please contact CERN Knowledge Transfer (<a href="mailto:kt@cern.ch">kt@cern.ch</a>).<br><br></span>
|
||||
</p><br>
|
||||
<button onclick="show_disclaimer()" id="myBtn" tabindex="-1">Read more</button><br><br>
|
||||
<div>
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
|
||||
</p>
|
||||
<p>
|
||||
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
|
||||
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
|
||||
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
|
||||
</p>
|
||||
<p>
|
||||
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 therein.
|
||||
The results DO NOT include short-range airborne exposure (where the physical distance is a significant factor) nor the other known modes of SARS-CoV-2 transmission.
|
||||
Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.
|
||||
</p>
|
||||
<p>
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of February 2021.
|
||||
It can be used to compare the effectiveness of different airborne-related risk mitigation measures.
|
||||
</p>
|
||||
<p>
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and
|
||||
the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.
|
||||
</p>
|
||||
<p>
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities.
|
||||
The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk.
|
||||
While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or 'completely safe scenario' does not exist.
|
||||
Each event modelled is unique, and the results generated therein are only as accurate as the inputs and assumptions.
|
||||
</p>
|
||||
<p>
|
||||
CARA has not undergone review, approval or certification by competent authorities, and as a result, it cannot be considered
|
||||
as a fully endorsed and reliable tool, namely in the assessment of potential viral emissions from infected hosts to be modelled.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-component text-component-page clearfix"></div>
|
||||
|
|
|
|||
|
|
@ -10,16 +10,41 @@ If you are using the expert version of the tool, you should look at the expert
|
|||
<p>For more information on the Airborne Transmission of SARS-CoV-2, feel free to check out the HSE Seminar: <a href="https://cds.cern.ch/record/2743403">https://cds.cern.ch/record/2743403</a></p>
|
||||
<p>The methodology, mathematical equations and parameters of the model are described here: <a href="https://edms.cern.ch/ui/file/2566402/1/CARA_Deterministic_parameters_2020.pdf">https://edms.cern.ch/ui/file/2566402/1/CARA_Deterministic_parameters_2020.pdf</a></p>
|
||||
<h2>Disclaimer</h2>
|
||||
<p>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 therein.
|
||||
The results DO NOT include short-range airborne exposure (where the physical distance plays a factor) nor the other known modes of SARS-CoV-2 transmission.
|
||||
Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.</p>
|
||||
<p>The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of December 2020 . It can be used to compare the effectiveness of different airborne-related risk mitigation measures.</p>
|
||||
<p>Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.</p>
|
||||
<p>This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities.
|
||||
The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk. While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or a 'completely safe scenario' does not exist.
|
||||
Each event modelled is unique and the results generated therein are only as accurate as the inputs and assumptions.</p>
|
||||
|
||||
<p>
|
||||
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
|
||||
</p>
|
||||
<p>
|
||||
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
|
||||
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
|
||||
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
|
||||
</p>
|
||||
<p>
|
||||
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 therein.
|
||||
The results DO NOT include short-range airborne exposure (where the physical distance is a significant factor) nor the other known modes of SARS-CoV-2 transmission.
|
||||
Hence, the output from this model is only valid when the other recommended public health & safety instructions are observed, such as adequate physical distancing, good hand hygiene and other barrier measures.
|
||||
</p>
|
||||
<p>
|
||||
The model used is based on scientific publications relating to airborne transmission of infectious diseases, dose-response exposures and aerosol science, as of February 2021.
|
||||
It can be used to compare the effectiveness of different airborne-related risk mitigation measures.
|
||||
</p>
|
||||
<p>
|
||||
Note that this model applies a deterministic approach, i.e., it is assumed at least one person is infected and shedding viruses into the simulated 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 most useful for comparing the impact and effectiveness of different mitigation measures such as ventilation, filtration, exposure time, physical activity and
|
||||
the size of the room, only considering long-range airborne transmission of COVID-19 in indoor settings.
|
||||
</p>
|
||||
<p>
|
||||
This tool is designed to be informative, allowing the user to adapt different settings and model the relative impact on the estimated infection probabilities.
|
||||
The objective is to facilitate targeted decision-making and investment through comparisons, rather than a singular determination of absolute risk.
|
||||
While the SARS-CoV-2 virus is in circulation among the population, the notion of 'zero risk' or 'completely safe scenario' does not exist.
|
||||
Each event modelled is unique, and the results generated therein are only as accurate as the inputs and assumptions.
|
||||
</p>
|
||||
<p>
|
||||
CARA has not undergone review, approval or certification by competent authorities, and as a result, it cannot be considered
|
||||
as a fully endorsed and reliable tool, namely in the assessment of potential viral emissions from infected hosts to be modelled.
|
||||
</p>
|
||||
|
||||
<h2>How to use this tool</h2>
|
||||
<h3>Simulation Name & Room number</h3>
|
||||
<p>In order to be able to trace back the simulations in your workplace risk assessments, performed with the tool, you can give each one a unique name - for example "Office use on Tuesday mornings".
|
||||
|
|
|
|||
|
|
@ -85,9 +85,7 @@
|
|||
{% block disclaimer %}
|
||||
{{ super() }}
|
||||
<p>
|
||||
CARA is made available for internal CERN use only.
|
||||
It is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites.
|
||||
For use outside of this scope, it has to be performed under a pre-defined framework and licence agreement issued by CERN Knowledge Transfer (<a href="mailto:kt@cern.ch">kt@cern.ch</a>).
|
||||
At CERN, CARA is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites.
|
||||
</p>
|
||||
{% endblock disclaimer %}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,9 +136,9 @@ body > footer {
|
|||
color: #fffeee !important; }
|
||||
body > footer h2:after {
|
||||
background: #fffeee !important; }
|
||||
body > footer nav ul.menu.nav li a {
|
||||
body > footer a, footer a {
|
||||
color: #fffefe; }
|
||||
body > footer nav ul.menu.nav li a:hover, body > footer nav ul.menu.nav li a.is-active {
|
||||
body > footer a:hover, body > footer a.is-active {
|
||||
color: #fffefe; }
|
||||
body > footer nav ul.menu.nav li a:hover:before, body > footer nav ul.menu.nav li a.is-active:before {
|
||||
color: #2d8af1; }
|
||||
|
|
|
|||
0
cara/apps/templates/common_text.md.j2
Normal file
0
cara/apps/templates/common_text.md.j2
Normal file
|
|
@ -16,44 +16,51 @@
|
|||
<h2 class="text-component-title" style="display:inline-block; vertical-align:middle; align:center;">
|
||||
CARA: COVID Airborne Risk Assessment Tools
|
||||
</h2>
|
||||
<div class="text-component-text cern_full_html" style="align:center;">
|
||||
|
||||
<h4>Please try out the CARA COVID calculator <a href="/calculator">here</a>.
|
||||
Your feedback is most welcome at <a href="mailto:CARA-dev@cern.ch">CARA-dev@cern.ch</a></h4>
|
||||
<span style="height: 5vh; display: block;"></span>
|
||||
|
||||
<span style="min-height: 50vh; height: 100%;"></span>
|
||||
|
||||
<h2>Introduction</h2><br>
|
||||
<div class="text-component-text cern_full_html" >
|
||||
<p>
|
||||
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
|
||||
It does this by simulating the long-range airborne spread SARS-CoV-2 virus in a finite volume, assuming homogenous mixing, and it estimates the risk of COVID-19 infection therein.
|
||||
Please see the <a href="https://gitlab.cern.ch/cara/cara/-/blob/master/README.md">about</a> page for more details on the methodology, assumptions and limitations of CARA.
|
||||
</p>
|
||||
<p>
|
||||
The full CARA source code can be accessed freely under an Apache 2.0 open source license from our <a href="https://gitlab.cern.ch/cara/cara">code repository</a>.
|
||||
It includes detailed instructions on how to run your own version of this tool.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Authors:</h2><br>
|
||||
<br>
|
||||
<h2>CARA@CERN</h2><br>
|
||||
<div class="text-component-text cern_full_html" >
|
||||
<p>
|
||||
<h4>Andre Henriques<sup>1</sup>, Gabriella Azzopardi<sup>2</sup>, James Devine<sup>3</sup>, Philip Elson<sup>4</sup>, Nicolas Mounet<sup>2</sup>, Markus Kongstein Rognlien<sup>2</sup>, Nicola Tarocco<sup>5</sup></h4><br>
|
||||
CARA has been developed by CERN with the intention of allowing members of personnel with roles related to supervision, health & safety or space management to simulate the concerned workplaces on CERN sites.
|
||||
A hosted <a href="/calculator">CERN version of the CARA Covid Calculator</a> is available on this site to members of the CERN personnel.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<h2>Authors</h2>
|
||||
<div class="text-component-text cern_full_html" >
|
||||
<p>
|
||||
<h4>Andre Henriques<sup>1</sup>, Marco Andreini<sup>1</sup>, Gabriella Azzopardi<sup>2</sup>, James Devine<sup>3</sup>, Philip Elson<sup>4</sup>, Nicolas Mounet<sup>2</sup>, Markus Kongstein Rognlien<sup>2</sup>, Nicola Tarocco<sup>5</sup></h4><br>
|
||||
|
||||
<sup>1</sup>HSE Unit, Occupational Health & Safety Group, CERN<br>
|
||||
<sup>2</sup>Beams Department, Accelerators and Beam Physics Group, CERN<br>
|
||||
<sup>3</sup>Experimental Physics Department, Safety Office, CERN<br>
|
||||
<sup>4</sup>Beams Department, Controls Group, CERN<br>
|
||||
<sup>5</sup>Information Technology Department, Collaboration, Devices & Applications Group, CERN<br>
|
||||
<span style="height: 5vh; display: block;"></span>
|
||||
</p>
|
||||
|
||||
<h3>Acknowledgements:</h3><br>
|
||||
<br>
|
||||
<h3>Acknowledgements:</h3>
|
||||
<p>
|
||||
We thank CERN’s HSE Unit, Beams Department, Experimental Physics Department and Information Technology Department for their continuous support.
|
||||
Thanks to Doris Forkel-Wirth, Olga Beltramello, Letizia Di Giulio, Evelyne Dho and the other members of the office occupancy working group for providing expert advice and extensively testing the tool.
|
||||
We thank Fabienne Landua and the Design and Visual Identity Service for preparing the logo.
|
||||
Thanks also to colleagues like Oriol Rios, Marco Andreini, Lina Dimovasili for the technical discussions and advice.
|
||||
Many thanks to the work and research performed by world leading scientists in this domain: Prof. Manuel Gameiro, Prof. Shelly Miller, Prof. Linsey Marr, Prof. Jose Jimenez, Dr. Lidia Morawska, Prof Yuguo Li – their contribution was indispensable for this project.
|
||||
<span style="height: 5vh; display: block;"></span>
|
||||
We wish to thank CERN’s HSE Unit, Beams Department, Experimental Physics Department, Information Technology Department, Industry, Procurement and Knowledge Transfer Department and International Relations Sector for their support to the study.
|
||||
Thanks to Doris Forkel-Wirth, Benoit Delille, Walid Fadel, Olga Beltramello, Letizia Di Giulio, Evelyne Dho, Wayne Salter, Benoit Salvant and colleagues from the COVID working group for providing expert advice and extensively testing the model.
|
||||
Finally, we wish to thank Fabienne Landua and the design service for preparing the illustrations and Alessandro Raimondo, Ana Padua and Manuela Cirilli from the Knowledge Transfer Group for their continuous support.
|
||||
Our compliments towards the work and research performed by world leading scientists in this domain: Prof. Manuel Gameiro, Prof. Shelly Miller, Prof. Linsey Marr, Prof. Jose Jimenez, Dr. Lidia Morawska, Prof Yuguo Li et al. – their scientific contribution was indispensable for this project.
|
||||
<span style="height: 3vh; display: block;"></span>
|
||||
</p>
|
||||
|
||||
<h3>Intended Users:</h3>
|
||||
<p>
|
||||
CARA is made available for internal CERN use only. It is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites. For use outside of this scope, please contact CERN Knowledge Transfer (<a href="mailto:kt@cern.ch">kt@cern.ch</a>).
|
||||
<span style="height: 10vh; display: block;"></span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -193,7 +193,6 @@
|
|||
policy issues. Any initiative is conducted on a best effort and as-is basis, without liability or
|
||||
warranty.</em></span></p>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
|
@ -231,9 +230,22 @@
|
|||
</path>
|
||||
</svg></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://gitlab.cern.ch/cara/cara" title="CARA code repository" data-extlink="">
|
||||
Source code
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
<p style="font-size:10px;">
|
||||
CARA is <a href="https://gitlab.cern.ch/cara/cara/-/blob/master/LICENSE" class="ext">Apache 2.0 licensed</a> open-source
|
||||
software developed at CERN.
|
||||
You can find the source code at <a href="https://gitlab.cern.ch/cara/cara">https://gitlab.cern.ch/cara/cara</a>,
|
||||
where we welcome contributions, feature requests and issue reports.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- general info -->
|
||||
|
|
@ -282,9 +294,7 @@
|
|||
d="M56 6H44c-1.1 0-2 0.9-2 2s0.9 2 2 2h7.2L30.6 30.6c-0.8 0.8-0.8 2 0 2.8C31 33.8 31.5 34 32 34s1-0.2 1.4-0.6L54 12.8V20c0 1.1 0.9 2 2 2s2-0.9 2-2V8C58 6.9 57.1 6 56 6z">
|
||||
</path>
|
||||
</svg></a>
|
||||
©
|
||||
2020
|
||||
CERN</div>
|
||||
© 2020 - 2021 CERN</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/js_packaged_for_theme.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# This module is part of CARA. Please see the repository at
|
||||
# https://gitlab.cern.ch/cara/cara for details of the license and terms of use.
|
||||
"""
|
||||
This module implements the core CARA models.
|
||||
|
||||
|
|
|
|||
30
cara/tests/apps/calculator/test_markdown_tools.py
Normal file
30
cara/tests/apps/calculator/test_markdown_tools.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import textwrap
|
||||
|
||||
import jinja2
|
||||
import pytest
|
||||
|
||||
import cara.apps.calculator.markdown_tools as md_tools
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example_template():
|
||||
return jinja2.Environment().from_string(textwrap.dedent("""
|
||||
# A header
|
||||
|
||||
Some *text*
|
||||
|
||||
{% block using_jinja_blocks %}
|
||||
# Another header
|
||||
|
||||
Some more **text**.
|
||||
{% endblock %}
|
||||
|
||||
"""))
|
||||
|
||||
|
||||
def test_extract_blocks(example_template):
|
||||
blocks = md_tools.extract_rendered_markdown_blocks(example_template)
|
||||
assert 'A header' in blocks
|
||||
assert blocks['A header'] == '<p>Some <em>text</em></p>\n'
|
||||
assert 'Another header' in blocks
|
||||
assert blocks['Another header'] == '<p>Some more <strong>text</strong>.</p>\n'
|
||||
|
|
@ -3,6 +3,7 @@ import dataclasses
|
|||
import pytest
|
||||
|
||||
from cara.apps.calculator import model_generator
|
||||
from cara.apps.calculator.model_generator import _hours2timestring
|
||||
from cara.apps.calculator.model_generator import minutes_since_midnight
|
||||
from cara import models
|
||||
from cara import data
|
||||
|
|
@ -272,13 +273,8 @@ def time2mins(time: str) -> minutes_since_midnight:
|
|||
return minutes_since_midnight(int(time.split(':')[0]) * 60 + int(time.split(':')[1]))
|
||||
|
||||
|
||||
def hours2time(hours: float):
|
||||
# Convert times like 14.5 to strings, like "14:30"
|
||||
return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}"
|
||||
|
||||
|
||||
def assert_boundaries(interval, boundaries_in_time_string_form):
|
||||
boundaries = [(hours2time(start), hours2time(end))
|
||||
boundaries = [(_hours2timestring(start), _hours2timestring(end))
|
||||
for start, end in interval.boundaries()]
|
||||
assert boundaries == boundaries_in_time_string_form
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ async def test_calculator_form(http_server_client):
|
|||
assert response.code == 200
|
||||
|
||||
|
||||
async def test_user_guide(http_server_client):
|
||||
resp = await http_server_client.fetch('/calculator/user-guide')
|
||||
assert resp.code == 200
|
||||
|
||||
|
||||
class TestBasicApp(tornado.testing.AsyncHTTPTestCase):
|
||||
def get_app(self):
|
||||
return cara.apps.calculator.make_app()
|
||||
|
|
|
|||
13
setup.py
13
setup.py
|
|
@ -1,3 +1,5 @@
|
|||
# This module is part of CARA. Please see the repository at
|
||||
# https://gitlab.cern.ch/cara/cara for details of the license and terms of use.
|
||||
"""
|
||||
setup.py for CARA.
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ setup(
|
|||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
"License :: OSI Approved :: Apache Software License", # Apache 2.0
|
||||
],
|
||||
|
||||
install_requires=REQUIREMENTS['core'],
|
||||
|
|
@ -73,11 +76,9 @@ setup(
|
|||
'all': [req for reqs in REQUIREMENTS.values() for req in reqs],
|
||||
},
|
||||
package_data={'cara': [
|
||||
'apps/templates/*.j2',
|
||||
'apps/calculator/templates/*.j2',
|
||||
'apps/calculator/*',
|
||||
'apps/calculator/*/*',
|
||||
'apps/calculator/*/*/*',
|
||||
'apps/calculator/*/*/*/*',
|
||||
'apps/*/*',
|
||||
'apps/*/*/*',
|
||||
'apps/*/*/*/*',
|
||||
'apps/*/*/*/*/*',
|
||||
]},
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue