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.
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
+ For use of the CARA model:
+