Merge branch 'develop/form-updates' into 'master'

Form UI updates

Closes #97 and #79

See merge request cara/cara!84
This commit is contained in:
Philip James Elson 2020-11-12 19:26:09 +00:00
commit e3d0a6a4d7
5 changed files with 167 additions and 52 deletions

View file

@ -42,7 +42,6 @@ class FormData:
ventilation_type: str
volume_type: str
window_height: float
window_width: float
windows_number: int
windows_open: str
@ -113,7 +112,6 @@ class FormData:
ventilation_type=form_data['ventilation_type'],
volume_type=form_data['volume_type'],
window_height=float(form_data['window_height']),
window_width=float(form_data['window_width']),
windows_number=int(form_data['windows_number']),
windows_open=form_data['windows_open'],
infected_start=time_string_to_minutes(form_data['infected_start']),
@ -314,7 +312,6 @@ def baseline_raw_form_data():
'ventilation_type': 'natural',
'volume_type': 'room_volume',
'window_height': '2',
'window_width': '2',
'windows_number': '1',
'windows_open': 'interval'
}

View file

@ -12,4 +12,93 @@
.red_text {
color: red;
}
#disclaimer,#code_license {
font-size: 9pt;
}
/* -------Tool tip ------- */
.tooltip_text {
position: relative;
background: rgba(59, 72, 80, 0.9);
padding: 0px 8px;
margin: 5px;
font-size: 13px;
border-radius: 100%;
color: #FFF;
}
[data-tooltip] {
position: relative;
display: inline-block;
z-index: 10;
}
/* Positioning and visibility settings */
[data-tooltip]:before,
[data-tooltip]:after {
position: absolute;
visibility: hidden;
opacity: 0;
left: 50%;
bottom: calc(100% + 5px);
pointer-events: none;
transition: 0.2s;
will-change: transform;
}
/* The actual tooltip with a dynamic width */
[data-tooltip]:before {
content: attr(data-tooltip);
padding: 10px 18px;
min-width: 50px;
max-width: 200px;
width: max-content;
width: -moz-max-content;
border-radius: 6px;
font-size: 14px;
background-color: rgba(59, 72, 80, 0.9);
background-image: linear-gradient(30deg,
rgba(59, 72, 80, 0.44),
rgba(59, 68, 75, 0.44),
rgba(60, 82, 88, 0.44));
box-shadow: 0px 0px 24px rgba(0, 0, 0, 0.2);
color: #fff;
text-align: center;
white-space: pre-wrap;
transform: translate(-50%, -5px) scale(0.5);
}
/* Tooltip arrow */
[data-tooltip]:after {
content: '';
border-style: solid;
border-width: 5px 5px 0px 5px;
border-color: rgba(55, 64, 70, 0.9) transparent transparent transparent;
transition-duration: 0s; /* If the mouse leaves the element,
the transition effects for the
tooltip arrow are "turned off" */
transform-origin: top; /* Orientation setting for the
slide-down effect */
transform: translateX(-50%) scaleY(0);
}
/* Tooltip becomes visible at hover */
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
visibility: visible;
opacity: 1;
}
/* Scales from 0.5 to 1 -> grow effect */
[data-tooltip]:hover:before {
transition-delay: 0.3s;
transform: translate(-50%, -5px) scale(1);
}
/* Slide down effect only on mouseenter (NOT on mouseleave) */
[data-tooltip]:hover:after {
transition-delay: 0.5s; /* Starting after the grow effect */
transition-duration: 0.2s;
transform: translateX(-50%) scaleY(1);
}

View file

@ -109,7 +109,6 @@ function require_mechanical_ventilation(option) {
function require_natural_ventilation(option) {
$("#windows_number").prop('required', option);
$("#window_height").prop('required', option);
$("#window_width").prop('required', option);
$("#opening_distance").prop('required', option);
$("#always").prop('required', option);
$("#interval").prop('required', option);
@ -133,12 +132,20 @@ function require_recurrent_event(option) {
function require_lunch(option) {
$("#lunch_start").prop('required', option);
$("#mask_ffp2").prop('required', option);
$("#lunch_finish").prop('required', option);
if (option) {
document.getElementById("lunch_start").value = "12:30";
document.getElementById("lunch_finish").value = "13:30";
}
else {
document.getElementById("lunch_start").value = "";
document.getElementById("lunch_finish").value = "";
}
}
function require_lunch(option) {
function require_mask(option) {
$("#mask_type1").prop('required', option);
$("#lunch_finish").prop('required', option);
$("#mask_ffp2").prop('required', option);
}
function require_hepa(option) {

View file

@ -29,7 +29,7 @@
<form id="covid_calculator" name="covid_calculator" action="/calculator/report" onsubmit="return validate_form(this)" method="POST">
{% endif %}
<input type="hidden" name="model_version" value={{ MODEL_VERSION }}></input>
<input type="hidden" name="model_version" value={{ MODEL_VERSION }}>
<div style="width: 33%; float:left;">
@ -37,7 +37,10 @@
<b>Simulation name:</b> <input type="text" name="simulation_name" placeholder="E.g. Workshop without masks" required><br>
Room number: <input type="text" name="room_number" placeholder="E.g. 17/R-033" required><br>
<hr width="80%">
<b>Room data:</b><br>
<b>Room data:</b>
<div data-tooltip="The area you wish to study (choose one of the 2 options). Use GIS Portal or measure.">
<span class="tooltip_text">?</span>
</div><br>
<input type="radio" id="room_type_volume" name="volume_type" value="room_volume" onclick="require_fields(this)" required>
Room volume: &nbsp;&nbsp; <input type="number" step=0.01 id="room_volume" name="room_volume" placeholder="Room volume (m³)" min="0.01"><br>
<input type="radio" id="room_type_dimensions" name="volume_type" value="room_dimensions" onclick="require_fields(this)" required>
@ -46,10 +49,14 @@
<hr width="80%">
<!-- Ventilation Options -->
<b>Ventilation data:</b>
<div data-tooltip="The available means of venting / filtration of indoor spaces.">
<span class="tooltip_text">?</span>
</div><br>
Ventilation type:
<input type="radio" id="no-ventilation" name="ventilation_type" value="no-ventilation" checked>No ventilation</input>
<input type="radio" id="mechanical" name="ventilation_type" value="mechanical" data-enables="DIVmechanical_ventilation">Mechanical</input>
<input type="radio" id="natural" name="ventilation_type" value="natural" data-enables="DIVnatural_ventilation">Natural</input><br>
<input type="radio" id="no-ventilation" name="ventilation_type" value="no-ventilation" checked>No ventilation
<input type="radio" id="mechanical" name="ventilation_type" value="mechanical" data-enables="DIVmechanical_ventilation">Mechanical
<input type="radio" id="natural" name="ventilation_type" value="natural" data-enables="DIVnatural_ventilation">Natural<br>
<div id="DIVmechanical_ventilation" style="display:none">
<input type="radio" id="air_type_supply" name="mechanical_ventilation_type" value="air_supply" onclick="require_fields(this)">
@ -61,7 +68,6 @@
<div id="DIVnatural_ventilation" style="display:none">
Number of windows: <input type="number" id="windows_number" name="windows_number" min="1"><br>
Height of window: <input type="number" step=0.01 id="window_height" name="window_height" placeholder="meters" min="0"><br>
Width of window: <input type="number" step=0.01 id="window_width" name="window_width" placeholder="meters" min="0"><br>
Opening distance: <input type="number" step=0.01 id="opening_distance" name="opening_distance" placeholder="meters" min="0"><br>
Windows open: <input type="radio" id="always" name="windows_open" value="always">
<label for="always">Always</label>
@ -78,7 +84,10 @@
<input type="number" step=0.01 id="hepa_amount" name="hepa_amount" placeholder="(m³ / hour)" min="0">
<hr width="80%">
<b>Face masks:</b><br>
<b>Face masks:</b>
<div data-tooltip="Masks worn at workstations or removed when a 2m physical distance is respected and proper venting is ensured.">
<span class="tooltip_text">?</span>
</div><br>
Are masks worn when occupants are at workstations?
<input type="radio" id="mask_on" name="mask_wearing" value="continuous" required>Yes
<input type="radio" id="mask_off" name="mask_wearing" value="removed" required checked="checked">No<br>
@ -91,8 +100,10 @@
<div style="width: 33%; float:left;">
<!-- Event Options -->
<b>Event data:</b><br>
Attendees:<br>
<b>Event data:</b>
<div data-tooltip="The total no. of occupants in the room and how many of them you assume are infected.">
<span class="tooltip_text">?</span>
</div><br>
Total number of occupants: <input type="number" id="total_people" name="total_people" min=1 required><br>
Number of infected people: <input type="number" id="infected_people" name="infected_people" min=1 required><br>
<hr width="80%">
@ -111,13 +122,17 @@
<span id="infected_time_error" class="red_text" hidden>Finish time must be after start</span><br>
<hr width="80%">
When is the event?<br>
<input type="radio" id="event_type_single" name="event_type" value="single_event" onclick="require_fields(this)" required></input>
When is the event?
<div data-tooltip="Select the date for one-off events or the month if the event is recurrent in the same space.">
<span class="tooltip_text">?</span>
</div><br>
<input type="radio" id="event_type_single" name="event_type" value="single_event" onclick="require_fields(this)" required>
<label for="event_type_single">Single event</label> &nbsp;&nbsp;
<label for="single_event_date">Date: </label>
<input type="text" id="single_event_date" class="datepicker" name="single_event_date" placeholder="dd/mm/yyyy">
<span id="event_time_error" class="red_text" hidden> Incorrect date format</span><br>
<input type="radio" id="event_type_recurrent" name="event_type" value="recurrent_event" onclick="require_fields(this)" required></input>
<input type="radio" id="event_type_recurrent" name="event_type" value="recurrent_event" onclick="require_fields(this)" required>
<label for="event_type_recurrent">Recurrent usage</label>
<select id="recurrent_event_month" name="recurrent_event_month">
<option value="January">January</option>
@ -135,26 +150,27 @@
</select><br>
<hr width="80%">
<b>Activity breaks:</b><br>
<!-- Lunch Options -->
Lunch break:
<input type="radio" id="lunch_option_no" name="lunch_option" value=0 checked="checked" onclick="require_fields(this)">
<input type="radio" id="lunch_option_no" name="lunch_option" value=0 onclick="require_fields(this)">
<label for="lunch_option">No</label>
<input type="radio" id="lunch_option_yes" name="lunch_option" value=1 onclick="require_fields(this)"></input>
<input type="radio" id="lunch_option_yes" name="lunch_option" value=1 checked="checked" onclick="require_fields(this)">
<label for="lunch_option">Yes</label><br>
<div id="DIVlunch_break">
Start: <input type="time" id="lunch_start" name="lunch_start" unrequired> &nbsp;&nbsp;
Finish: <input type="time" id="lunch_finish" class="finish_time" name="lunch_finish" unrequired>
Start: <input type="time" id="lunch_start" name="lunch_start" value="12:30" required> &nbsp;&nbsp;
Finish: <input type="time" id="lunch_finish" class="finish_time" name="lunch_finish" value="13:30" required>
<span id="lunch_time_error" class="red_text" hidden>Finish time must be after start</span><br>
</div>
<!-- Coffee Options -->
Coffee Breaks
<input type="radio" name="coffee_breaks" value="0" checked="checked"</input>
<input type="radio" name="coffee_breaks" value="0" checked="checked">
<label for="lunch_option" >No breaks</label>
<input type="radio" name="coffee_breaks" value="2" </input>
<input type="radio" name="coffee_breaks" value="2">
<label for="lunch_option">2</label>
<input type="radio" name="coffee_breaks" value="4"</input>
<input type="radio" name="coffee_breaks" value="4">
<label for="lunch_option">4</label>
<br>
Duration (minutes): <select id="break_duration" name="coffee_duration">
@ -171,26 +187,33 @@
</div>
<div style="width: 33%; float:left;">
<b>Quick Guide:</b><br>
This tool simulates the long range airborne spread SARS-CoV-2 virus in a finite volume and estimates the risk of COVID-19 infection. It is based on current scientific data and can be used to measures the effectiveness of different mitigation measures.<br>
For detailed explanations on how to use this tool please see the <a href="/calculator/user-guide"> COVID Calculator user-guide </a>. <br>
<b>Usage summary:</b><br>
<b>Room data</b><br>
Enter the data about the area you wish to study. You can find these figures in GIS Portal, or by measuring them yourself.<br>
<b>Ventilation data</b> <br>
Enter the data on the available means for venting of indoor spaces. For mechanical ventilation, you should check with a specialist for the air flow or air change rate.<br>
For HEPA filtration, you should enter the air flow of the device. As a first estimate, you may use the following values based on the differnet fan velocities of a specific commercial device proposed by the HSE Unit - Level 6 (max) = 430 m^3/h (noisy); Level 5 = 250 m^3/h (ok w.r.t. noise); Level 4 = 130 m^3/h (silent); Level 3 = 95 m^3/h (silent).<br>
<b>Event data</b><br>
Enter the total number of occupants in the room and how many of them you assume are infected. We have provided common activity types:<br>
Office/Meeting = typical scenario all persons seated, talking.<br>
Workshop = assembly workshop environment, all persons doing light exercise, talking.<br>
Training = one person (the trainer) standing, talking, all others seated, talking quietly (whispering). It is assumed the trainer is the infected person, for the worst case scenario.<br>
You should specify if the event is a one off (give date) or recurrent use of the same space for the same activity, in which case select the month when the activity takes place.<br>
Specify if a lunch break should be included, and when it starts/stops. <br>
If you will take coffee breaks, they are spread out evenly throughout the day, in addition to lunch.<br>
Mask wearing: Specify if they are worn at occupant workstations, or are removed when a physical distance of 2m is respected.
<b>Ventilation data:</b> <br>
<ul>
<li>Mechanical ventilation = check the rates with a specialist.</li>
<li>Natural ventilation = the window opening distance is between the fixed frame and movable part when open.</li>
<li>HEPA filtration = the air flow of the device. The following values are based on the different fan velocities of a specific commercial device proposed by the HSE Unit:</li>
<ul>
<li>Level 6 (max) = 430 m^3/h (noisy)</li>
<li>Level 5 = 250 m^3/h (ok w.r.t. noise)</li>
<li>Level 4 = 130 m^3/h (silent)</li>
<li>Level 3 = 95 m^3/h (silent)</li>
</ul>
</ul>
<b>Activity types:</b><br>
The type of activity that includes both the infected and exposed persons:
<ul>
<li>Office/Meeting = typical scenario all persons seated, talking.</li>
<li>Workshop = assembly workshop environment, all persons doing light exercise, talking.</li>
<li>Training = one person (the trainer) standing, talking, all others seated, talking quietly (whispering). It is assumed the trainer is the infected person, for the worst case scenario.</li>
</ul>
<b>Activity breaks:</b><br>
<ul>
<li>If coffee breaks are included, they are spread out evenly throughout the day, in addition to lunch.</li>
</ul>
Refer to <a href="/calculator/user-guide"> COVID Calculator user-guide </a> for more detailed explanations on how to use this tool. <br>
</div>
<button type='submit'>Generate report</button><br><br><br><br>
@ -200,13 +223,13 @@ You should specify if the event is a one off (give date) or recurrent use of the
<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 style="font-size:9pt;">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.<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 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.<br><br>
It is based on current scientific data and can be used to measures the effectiveness of different mitigation measures.<br><br>
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.<br><br>
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.<br><br>
<b>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.</b></span>
<br><br><b>Code License:</b><br><br>
<span style="font-size:9pt;">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</span></p><br>
<span id="code_license">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</span></p><br>
<button onclick="show_disclaimer()" id="myBtn">Read more</button><br><br>
</div>

View file

@ -47,7 +47,6 @@
<ul>
<li><p class="data_subtext">Number of windows: {{ form.windows_number }}</p></li>
<li><p class="data_subtext">Height of window: {{ form.window_height }}</p></li>
<li><p class="data_subtext">Width of window: {{ form.window_width }}</p></li>
<li><p class="data_subtext">Opening distance: {{ form.opening_distance }}</p></li>
<li><p class="data_subtext">Windows open: {{ form.windows_open }}</p></li>
</ul>
@ -125,21 +124,21 @@
<p class="result_title">Results:</p>
<p class="data_text">
In this scenario, the estimated probability of one exposed occupant getting infected P(i) is {{ prob_inf | int_format }}% and the estimated basic reproduction rate (R0) is {{ R0 | float_format }}.
In this scenario, the estimated probability of one exposed occupant getting infected P(i) is {{ prob_inf | int_format }}% and the expected number of new cases is {{ R0 | float_format }}.
<p>
<p class="data_title">Exposure graph:</p>
<img id="scenario_concentration_plot" src="{{ scenario_plot_src }}">
<p class="data_title">Repeated events:</p>
<p class="data_text">
The P(i) and R0 if repeating this scenario event - provided the infected person emits the same amount of viruses each day and the exposed person is subject to the same daily exposure time:
The P(i) and expected number of new cases if repeating this scenario event - provided the infected person emits the same amount of viruses each day and the exposed person is subject to the same daily exposure time:
<table class="table table-striped w-auto">
<thead class="thead-light">
<tr>
<th># of repeated events</th>
<th>P(i)</th>
<th>R0</th>
<th>Expected new cases</th>
</tr>
</thead>
<tbody>
@ -147,7 +146,7 @@
<tr>
<td>{{ repeat_event.repeats }}</td>
<td>{{ repeat_event.probability_of_infection | int_format }}%</td>
<td>{{ repeat_event.R0 | float_format }}</td>
<td style="text-align:right">{{ repeat_event.R0 | float_format }}</td>
</tr>
{% endfor %}
</tbody>