diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..4ec6bba3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,96 @@ +# 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. +name: CI + +on: + push: + branches: + - master + - 'feature/*' + pull_request: + branches: + - master + - 'feature/*' + workflow_dispatch: + inputs: + reason: + description: 'Reason' + required: false + default: 'Manual trigger' + +jobs: + test-install: + runs-on: ubuntu-latest + env: + PROJECT_ROOT: ./ + PROJECT_NAME: cara + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + + - name: Create LFS file list + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + + - name: Restore LFS cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 + + - name: Git LFS Pull + run: git lfs pull + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install ${PROJECT_ROOT}[test] + + - name: Run tests + run: | + mkdir -p ~/not-the-source-dir && cd ~/not-the-source-dir + python -m pytest --pyargs ${PROJECT_NAME} + test-dev: + runs-on: ubuntu-20.04 + env: + PROJECT_ROOT: ./ + PROJECT_NAME: cara + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + + - name: Create LFS file list + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + + - name: Restore LFS cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 + + - name: Git LFS Pull + run: git lfs pull + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install -e ${PROJECT_ROOT}[test] + python -m pip install pytest-cov + + - name: Run tests + run: | + cd ${PROJECT_ROOT} + python -m pytest ./${PROJECT_NAME} --cov=${PROJECT_NAME} --junitxml=report.xml diff --git a/README.md b/README.md index 832d39f2..02b95b78 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,14 @@ Andre Henriques1, Luis Aleixo1, Marco Andreini1 5Information Technology Department, Collaboration, Devices & Applications Group, CERN
6Norwegian University of Science and Technology (NTNU)
-### 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). +### Reference and Citation +**For the use of the CARA web app** +CARA – COVID Airborne Risk Assessment tool +© Copyright 2020-2021 CERN. All rights not expressly granted are reserved. + +**For use of the model** +Henriques A, Mounet N, Aleixo L, Elson P, Devine J, Azzopardi G, Andreini M, Rognlien M, Tarocco N, Tang J. (2022). Modelling airborne transmission of SARS-CoV-2 using CARA: risk assessment for enclosed spaces. _Interface Focus 20210076_. https://doi.org/10.1098/rsfs.2021.0076 ## Applications @@ -76,6 +81,8 @@ The CARA repository makes use of Git's Large File Storage (LFS) feature. You will need a working installation of git-lfs in order to run CARA in development mode. See https://git-lfs.github.com/ for installation instructions. +CARA is also mirrored to Github if you wish to collaborate on development and can be found at: https://github.com/CERN/cara + ### Installing CARA in editable mode ``` @@ -292,4 +299,4 @@ $ oc process -f deploymentconfig.yaml --param PROJECT_NAME='cara-test' | oc repl ``` Be aware that if you create/recreate the environment you must manually create a **route** in OpenShift, -specifying the respective annotation to be exposed outside CERN. \ No newline at end of file +specifying the respective annotation to be exposed outside CERN. diff --git a/app-config/auth-service/Dockerfile b/app-config/auth-service/Dockerfile index 75ef9310..0a78f5f4 100644 --- a/app-config/auth-service/Dockerfile +++ b/app-config/auth-service/Dockerfile @@ -21,5 +21,5 @@ FROM registry.cern.ch/docker.io/library/debian COPY --from=conda /opt/app /opt/app CMD [ \ - "/opt/app/bin/python", "-m", "auth_service" \ + "/opt/app/bin/python", "-m", "auth_service", "--no-debug" \ ] diff --git a/app-config/auth-service/auth_service/__init__.py b/app-config/auth-service/auth_service/__init__.py index 6a1acfb2..74dbd5cc 100644 --- a/app-config/auth-service/auth_service/__init__.py +++ b/app-config/auth-service/auth_service/__init__.py @@ -10,15 +10,14 @@ import typing import aiohttp from keycloak.aio.realm import KeycloakRealm -import tornado.ioloop +from tornado.web import Application, RequestHandler import tornado.log -import tornado.web LOG = logging.getLogger(__name__) -class BaseHandler(tornado.web.RequestHandler): +class BaseHandler(RequestHandler): def set_session_cookie(self, session_data: dict, expiry_in_seconds: int) -> None: seconds_per_day = 60 * 60 * 24 self.set_secure_cookie( @@ -152,18 +151,19 @@ class MainHandler(BaseHandler): session = self.get_session_cookie() if session is None: return self.finish(""" - You are currently not logged in: Login + You are currently not logged in: Login """) else: return self.finish(f""" You are currently logged in as "{session['username']}": - Logout + Logout """) -def make_app(): - tornado.log.enable_pretty_logging() - return tornado.web.Application( +def make_app(debug: bool = False) -> Application: + if debug: + tornado.log.enable_pretty_logging() + return Application( [ (r"/", MainHandler), (r"/auth/probe", ProbeAuthentication), @@ -174,7 +174,7 @@ def make_app(): (r'/auth/logout', Logout), ], cookie_secret=os.environ['COOKIE_SECRET'], - debug=True, + debug=debug, oicd_server=os.environ['OIDC_SERVER'], oicd_realm=os.environ['OIDC_REALM'], client_id=os.environ['CLIENT_ID'], diff --git a/app-config/auth-service/auth_service/__main__.py b/app-config/auth-service/auth_service/__main__.py index f8e10d12..1dafbe7c 100644 --- a/app-config/auth-service/auth_service/__main__.py +++ b/app-config/auth-service/auth_service/__main__.py @@ -1,9 +1,30 @@ -import tornado.ioloop +import argparse + +from tornado.ioloop import IOLoop from . import make_app +def configure_parser(parser) -> argparse.ArgumentParser: + parser.add_argument( + "--no-debug", help="Don't enable debug mode", + action="store_false", + ) + parser.add_argument( + "--port", + help="The port to listen on", + default="8080" + ) + return parser + + +def main(): + parser = configure_parser(argparse.ArgumentParser()) + args = parser.parse_args() + app = make_app(debug=args.no_debug) + app.listen(args.port) + IOLoop.instance().start() + + if __name__ == "__main__": - app = make_app() - app.listen(8080) - tornado.ioloop.IOLoop.current().start() + main() diff --git a/cara/apps/calculator/static/js/report.js b/cara/apps/calculator/static/js/report.js index a185b094..4a69f18a 100644 --- a/cara/apps/calculator/static/js/report.js +++ b/cara/apps/calculator/static/js/report.js @@ -60,13 +60,6 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses .attr('fill-opacity', '0.1'); }); - // Plot tittle. - var plotTitleEl = vis.append('svg:foreignObject') - .attr("background-color", "transparent") - .attr('height', 30) - .style('text-align', 'center') - .html('Mean concentration of virions'); - // X axis declaration. var xAxisEl = vis.append('svg:g') .attr('class', 'x axis'); @@ -100,7 +93,7 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses .attr('class', 'y label') .attr('fill', 'black') .attr('text-anchor', 'middle') - .text('Mean cumulative dose (virions)'); + .text('Mean cumulative dose (infectious virus)'); // Legend for the plot elements - line and area. var legendLineIcon = vis.append('rect') @@ -266,10 +259,6 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses }))) }) - - // Title. - plotTitleEl.attr('width', graph_width); - // Axis. var xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)); var yAxis = d3.axisLeft(yRange); @@ -471,15 +460,7 @@ function draw_alternative_scenarios_plot(concentration_plot_svg_id, alternative_ .attr('alignment-baseline', 'central'); } - - // Plot title. - var plotTitleEl = vis.append('svg:foreignObject') - .attr("background-color", "transparent") - .attr('height', 30) - .style('text-align', 'center') - .html('Mean concentration of virions'); - // X axis. var xAxisEl = vis.append('svg:g') .attr('class', 'x axis'); @@ -610,9 +591,6 @@ function draw_alternative_scenarios_plot(concentration_plot_svg_id, alternative_ } - // Title. - plotTitleEl.attr('width', graph_width); - // Axis. var xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)); var yAxis = d3.axisLeft(yRange); diff --git a/cara/apps/static/css/style.css b/cara/apps/static/css/style.css index 022b4ca6..93eb34c7 100644 --- a/cara/apps/static/css/style.css +++ b/cara/apps/static/css/style.css @@ -121,6 +121,11 @@ body { font-size: 1.25rem; } +.acknowledgements { + text-align: left; + font-size: 1.5rem; +} + .center { position: relative; display: flex; @@ -220,11 +225,6 @@ footer img { text-align: left; font-size: 2rem; } - - .developers { - text-align: left; - font-size: 1.5rem; - } .split > * + * { margin-left: 2em; diff --git a/cara/apps/templates/about.html.j2 b/cara/apps/templates/about.html.j2 index c4c414a8..50d2a8bf 100644 --- a/cara/apps/templates/about.html.j2 +++ b/cara/apps/templates/about.html.j2 @@ -8,7 +8,7 @@ Currently, the existing public health measures point to the importance of proper building and environmental engineering control measures, such as proper Indoor Air Quality (IAQ). This pandemic clearly raised increased awareness on airborne transmission of respiratory viruses in indoor settings. Out of the main modes of viral transmission, the airborne route of SARS-CoV-2 seems to have a significant importance to the spread of COVID-19 infections world-wide, hence proper guidance to building engineers or facility managers, on how to prevent on-site transmission, is essential.
-For information on the Airborne Transmission of SARS-CoV-2, feel free to check out the HSE Seminar: https://cds.cern.ch/record/2743403.
+For information on the Airborne Transmission of SARS-CoV-2, feel free to check out the special issue on the Interface Focus journal from Royal Society publishing: Interface Focus: Volume 12, Issue 2 and an CERN HSE Seminar: https://cds.cern.ch/record/2743403.


What is CARA?


CARA stands for COVID Airborne Risk Assessment and was developed in the spring of 2020 to better understand and quantify the risk of long-range airborne spread of SARS-CoV-2 virus in workplaces. CARA comes with different applications that allow more or less flexibility in the input parameters: @@ -17,8 +17,8 @@ CARA stands for COVID Airborne Risk Assessment and was developed in the spring o
  • CARA expert app
  • -The mathematical and physical model simulate the long-range airborne spread of SARS-CoV-2 virus in a finite volume, assuming a homogenous mixture, and estimates the risk of COVID-19 airborne transmission 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.
    -

    The methodology, mathematical equations and parameters of the model are described here in the CARA paper: CERN-OPEN-2021-004.

    +The mathematical and physical model simulate the long-range airborne spread of SARS-CoV-2 virus in a finite volume, assuming a homogenous mixture, and estimates the risk of COVID-19 airborne transmission therein. The results DO NOT include (for now) 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.
    +

    The methodology, mathematical equations and parameters of the model are published here in the CARA paper: Modelling airborne transmission of SARS-CoV-2 using CARA: risk assessment for enclosed spaces.

    The model used is based on scientific publications relating to airborne transmission of infectious diseases, virology, epidemiology and aerosol science. It can be used to compare the effectiveness of different airborne-related risk mitigation measures. @@ -48,11 +48,15 @@ Although the user is able to calculate the infection probability of a stand-alon

    Code Contributors:


    {{ text_blocks['Code Contributors'] }}
    -

    Acknowledgements:


    -{{ text_blocks['Acknowledgements'] }} -

    References:


    {{ text_blocks['References'] }} +
    +

    Acknowledgements:


    +
    + Click to expand +
    + {{ text_blocks['Acknowledgements'] }} +

    diff --git a/cara/apps/templates/base/index.html.j2 b/cara/apps/templates/base/index.html.j2 index 3df8598f..d6ee53d6 100644 --- a/cara/apps/templates/base/index.html.j2 +++ b/cara/apps/templates/base/index.html.j2 @@ -49,10 +49,35 @@ {% block cara_at_cern %} {% endblock cara_at_cern %} + +
    +
    +

    Reference & Citation


    +

    + For use of the CARA model:
    +

    + For use of the CARA web app:
    + +

    +

    -

    Acknowledgements


    - {{ text_blocks['Acknowledgements'] }} +

    Acknowledgements


    +
    + Click to expand +
    + {{ text_blocks['Acknowledgements'] }} +
    diff --git a/cara/apps/templates/base/layout.html.j2 b/cara/apps/templates/base/layout.html.j2 index cfa9bb09..406ecb23 100644 --- a/cara/apps/templates/base/layout.html.j2 +++ b/cara/apps/templates/base/layout.html.j2 @@ -34,26 +34,26 @@