Skip to content

Commit e5c3d3e

Browse files
authored
Merge pull request #214 from amath-idm/intervention-tooltips
Intervention tooltips, intervention functional changes
2 parents c62c8a0 + 71b5c53 commit e5c3d3e

File tree

6 files changed

+86
-48
lines changed

6 files changed

+86
-48
lines changed

CHANGELOG.rst

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ changes from multiple patch versions are grouped together, so numbering will not
66
strictly consecutive.
77

88

9+
Version 0.29.8 (2020-04-28)
10+
---------------------------
11+
- Update webapp UI with more detail on and control over interventions.
12+
13+
914
Version 0.29.7 (2020-04-27)
1015
---------------------------
1116
- New functions ``cv.date()`` and ``cv.daydiff()`` have been added, to ease handling of dates of different formats.

covasim/version.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
__all__ = ['__version__', '__versiondate__', '__license__']
44

5-
__version__ = '0.29.7'
6-
__versiondate__ = '2020-04-27'
5+
__version__ = '0.29.8'
6+
__versiondate__ = '2020-04-28'
77
__license__ = f'Covasim {__version__} ({__versiondate__}) — © 2020 by IDM'

covasim/webapp/cova_app.css

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ fieldset > legend {
121121
display: block;
122122
position: relative !important;
123123
width: initial;
124+
/*overflow-y: scroll; CK: for later use */
124125
height: auto !important;
125126
grid-area: sidebar;
126127
margin: 0;

covasim/webapp/cova_app.js

+53-23
Original file line numberDiff line numberDiff line change
@@ -33,45 +33,65 @@ const PlotlyChart = {
3333

3434
const interventionTableConfig = {
3535
social_distance: {
36-
formTitle: "Physical distancing",
37-
fields: [{key: 'start', type: 'number', label: 'Start day'},
38-
{key: 'end', type: 'number', label: 'End day'},
39-
{label: 'Effectiveness', key: 'level', type: 'select', options: [{label: 'Aggressive (80%)', value: 'aggressive'}, {label: 'Moderate (50%)', value: 'moderate'}, {label: 'Mild (20%)', value: 'mild'}]}],
36+
formTitle: "Distancing",
37+
toolTip: "Physical distancing and social distancing interventions",
38+
fields: [
39+
{key: 'start', type: 'number', label: 'Start day', tooltip: 'Start day of intervention', value: 0},
40+
{key: 'end', type: 'number', label: 'End day', tooltip: 'End day of intervention (leave blank for no end)', value: null},
41+
{key: 'level', type: 'number', label: 'Effectiveness', tooltip: 'Impact of social distancing (examples: 20 = mild, 50 = moderate, 80 = aggressive)', min: 0, max: 100, value: 50}
42+
],
4043
handleSubmit: function(event) {
41-
const start = parseInt(event.target.elements.start.value);
42-
const end = parseInt(event.target.elements.end.value);
44+
const start = vm.parse_day(event.target.elements.start.value);
45+
const end = vm.parse_day(event.target.elements.end.value);
4346
const level = event.target.elements.level.value;
4447
return {start, end, level};
4548
}
4649
},
4750
school_closures: {
48-
formTitle: "School closures",
49-
fields: [{key: 'start', type: 'number', label: 'Start day'}, {key: 'end', type: 'number', label: 'End day'}],
51+
formTitle: "Schools",
52+
toolTip: "School and university closures",
53+
fields: [
54+
{key: 'start', type: 'number', label: 'Start day', tooltip: 'Start day of intervention', value: 0},
55+
{key: 'end', type: 'number', label: 'End day', tooltip: 'End day of intervention (leave blank for no end)', value: null},
56+
{key: 'level', type: 'number', label: 'Effectiveness', tooltip: 'Impact of school closures (0 = no schools closed, 100 = all schools closed)', min: 0, max: 100, value: 90}
57+
],
5058
handleSubmit: function(event) {
51-
const start = parseInt(event.target.elements.start.value);
52-
const end = parseInt(event.target.elements.end.value);
53-
return {start, end};
59+
const start = vm.parse_day(event.target.elements.start.value);
60+
const end = vm.parse_day(event.target.elements.end.value);
61+
const level = event.target.elements.level.value;
62+
return {start, end, level};
5463
}
5564
},
5665
symptomatic_testing: {
57-
formTitle: "Symptomatic testing",
58-
fields: [{key: 'start', type: 'number', label: 'Start day'}, {key: 'end', type: 'number', label: 'End day'}, {label: 'Coverage', key: 'level', type: 'select', options: [{label: '10% per day', value: '10'}, {label: '30% per day', value: '30'},]}],
66+
formTitle: "Testing",
67+
toolTip: "Testing rates for people with symptoms",
68+
fields: [
69+
{key: 'start', type: 'number', label: 'Start day', tooltip: 'Start day of intervention', value: 0},
70+
{key: 'end', type: 'number', label: 'End day', tooltip: 'End day of intervention (leave blank for no end)', value: null},
71+
{key: 'level', type: 'number', label: 'Effectiveness', tooltip: 'Proportion of people tested per day (0 = no testing, 10 = 10% of people tested per day, 100 = everyone tested every day); assumes 1 day test delay', min: 0, max: 100, value: 10}
72+
],
5973
handleSubmit: function(event) {
60-
const start = parseInt(event.target.elements.start.value);
61-
const end = parseInt(event.target.elements.end.value);
62-
const level = parseInt(event.target.elements.level.value);
74+
const start = vm.parse_day(event.target.elements.start.value);
75+
const end = vm.parse_day(event.target.elements.end.value);
76+
const level = event.target.elements.level.value;
6377
return {start, end, level};
6478
}
6579
},
6680
contact_tracing: {
67-
formTitle: "Contact tracing",
68-
fields: [{key: 'start', type: 'number', label: 'Start Day'}, {key: 'end', type: 'number', label: 'End day'}],
81+
formTitle: "Tracing",
82+
toolTip: "Contact tracing of diagnosed cases (requires testing intervention)",
83+
fields: [
84+
{key: 'start', type: 'number', label: 'Start day', tooltip: 'Start day of intervention', value: 0},
85+
{key: 'end', type: 'number', label: 'End day', tooltip: 'End day of intervention (leave blank for no end)', value: null},
86+
{key: 'level', type: 'number', label: 'Effectiveness', tooltip: 'Effectiveness of contact tracing (0 = no tracing, 100 = all contacts traced); assumes 1 day tracing delay. Please note: you must implement a testing intervention as well for tracing to have any effect.', min: 0, max: 100, value: 80}
87+
],
6988
handleSubmit: function(event) {
70-
const start = parseInt(event.target.elements.start.value);
71-
const end = parseInt(event.target.elements.end.value);
72-
return {start, end};
89+
const start = vm.parse_day(event.target.elements.start.value);
90+
const end = vm.parse_day(event.target.elements.end.value);
91+
const level = event.target.elements.level.value;
92+
return {start, end, level};
7393
}
74-
}
94+
}
7595

7696
};
7797

@@ -235,7 +255,7 @@ var vm = new Vue({
235255
this.int_pars[key].push(intervention);
236256
const result = this.int_pars[key].sort((a, b) => a.start - b.start);
237257
this.$set(this.int_pars, key, result);
238-
const response = await sciris.rpc('get_gantt', undefined, {int_pars: this.int_pars, intervention_config: this.interventionTableConfig});
258+
const response = await sciris.rpc('get_gantt', undefined, {int_pars: this.int_pars, intervention_config: this.interventionTableConfig, n_days: this.sim_length.best});
239259
this.intervention_figs = response.data;
240260
},
241261
async deleteIntervention(scenarioKey, index) {
@@ -244,6 +264,16 @@ var vm = new Vue({
244264
this.intervention_figs = response.data;
245265
},
246266

267+
parse_day(day) {
268+
if (day == null || day == '') {
269+
const output = this.sim_length.best
270+
return output
271+
} else {
272+
const output = parseInt(day)
273+
return output
274+
}
275+
},
276+
247277
resize_start() {
248278
this.resizing = true;
249279
},

covasim/webapp/cova_app.py

+10-17
Original file line numberDiff line numberDiff line change
@@ -190,18 +190,18 @@ def upload_file(file):
190190

191191

192192
@app.register_RPC()
193-
def get_gantt(int_pars=None, intervention_config=None):
193+
def get_gantt(int_pars=None, intervention_config=None, n_days=90):
194194
df = []
195195
response = {'id': 'test'}
196196
for key,scenario in int_pars.items():
197197
for timeline in scenario:
198198
task = intervention_config[key]['formTitle']
199-
level = task + ' ' + str(timeline.get('level', ''))
199+
level = task + ' ' + str(timeline.get('level', '')) + '%'
200200
df.append(dict(Task=task, Start=timeline['start'], Finish=timeline['end'], Level= level))
201201
if len(df) > 0:
202202
fig = ff.create_gantt(df, height=400, index_col='Level', title='Intervention timeline',
203203
show_colorbar=True, group_tasks=True, showgrid_x=True, showgrid_y=True)
204-
fig.update_xaxes(type='linear')
204+
fig.update_xaxes(type='linear', range=[0, n_days])
205205
response['json'] = fig.to_json()
206206

207207
return response
@@ -240,27 +240,20 @@ def parse_interventions(int_pars):
240240
ikey = iconfig['ikey']
241241
start = iconfig['start']
242242
end = iconfig['end']
243+
level = float(iconfig['level'])/100
243244
if ikey == 'social_distance':
244-
level = iconfig['level']
245-
mapping = {
246-
'mild': 0.8,
247-
'moderate': 0.5,
248-
'aggressive': 0.2,
249-
}
250-
change = mapping[level]
245+
change = 1.0-level
251246
interv = cv.change_beta(days=[start, end], changes=[change, 1.0])
252247
elif ikey == 'school_closures':
253-
change = 0.0
248+
change = 1.0-level
254249
interv = cv.change_beta(days=[start, end], changes=[change, 1.0], layers='s')
255250
elif ikey == 'symptomatic_testing':
256-
level = iconfig['level']
257-
level = float(level)/100
258-
asymp_prob = 0.0
259-
delay = 0.0
251+
asymp_prob = level/10
252+
delay = 1.0
260253
interv = cv.test_prob(start_day=start, end_day=end, symp_prob=level, asymp_prob=asymp_prob, test_delay=delay)
261254
elif ikey == 'contact_tracing':
262-
trace_prob = {k:1.0 for k in 'hswc'}
263-
trace_time = {k:0.0 for k in 'hswc'}
255+
trace_prob = {k:level for k in 'hswc'}
256+
trace_time = {k:1.0 for k in 'hswc'}
264257
interv = cv.contact_tracing(start_day=start, end_day=end, trace_probs=trace_prob, trace_time=trace_time)
265258
else:
266259
raise NotImplementedError

covasim/webapp/index.html

+15-6
Original file line numberDiff line numberDiff line change
@@ -151,23 +151,29 @@
151151
<b-button block v-b-toggle.parameters-intervention variant="link">Interventions <span class="ti-angle-up"></span> <span class="ti-angle-down"></span></b-button>
152152
</b-card-header>
153153
<b-collapse id="parameters-intervention" visible role="tabpanel">
154-
<plotly-chart v-if="intervention_figs && intervention_figs.id" :graph="intervention_figs" :key="intervention_figs.id" ></plotly-chart>
155154
<b-card no-body class="border-0 rounded-0">
156155
<b-tabs fill card>
157156
<template v-for="(intervention, name, index) in interventionTableConfig">
158157
<b-tab :class="[index === 0? 'active' : '']" :id="'intervention-'+name">
159158
<template v-slot:title>
160-
{{ intervention.formTitle }}
159+
<span v-b-tooltip.hover :title="intervention.toolTip">
160+
{{ intervention.formTitle }}
161+
</span>
161162
</template>
162163
<fieldset>
163164
<form @submit.prevent="(e) => addIntervention(name, e)">
164165
<div class="form-group">
165166
<div class="input-group mb-3">
166167
<template v-for="field in intervention.fields">
167-
<input :max="sim_length.max" :min="0" :placeholder="field.label" :name="field.key" :aria-label="field.label" class="form-control" v-if="field.type !== 'select'" :type="field.type">
168+
<input :max="field.max || sim_length.max" :min="field.min || 0" :value="field.value" :name="field.key"
169+
:aria-label="field.label" class="form-control" v-if="field.type !== 'select'"
170+
:type="field.type" v-b-tooltip.hover :title="field.tooltip">
168171
<select class="form-control" :name="field.key" v-else>
169-
<option v-for="option in field.options" :value="option.value">{{option.label}}</option>
172+
<option v-for="option in field.options" :value="option.value">{{option.value}}</option>
170173
</select>
174+
<div class="input-group-append" v-if="field.key === 'level'">
175+
<span class="input-group-text">%</span>
176+
</div>
171177
</template>
172178
<div class="input-group-append">
173179
<button class="btn btn-outline-secondary" type="submit">Add intervention</button>
@@ -186,9 +192,11 @@
186192
</thead>
187193
<tbody>
188194
<tr v-for="(par, index) in int_pars[name]">
189-
<th v-for="field in intervention.fields" scope="row">{{par[field.key]}}</th>
195+
<td v-for="field in intervention.fields" scope="row">
196+
{{par[field.key]}}<span v-if="field.key=== 'level'">%</span>
197+
</td>
190198
<td>
191-
<button @click="deleteIntervention(name, index)" class="btn btn-outline-secondary" type="button">Delete</button>
199+
<button @click="deleteIntervention(name, index)" class="btn btn-outline-danger" type="button">Delete</button>
192200
</td>
193201
</tr>
194202
</tbody>
@@ -199,6 +207,7 @@
199207
</template>
200208
</b-tabs>
201209
</b-card>
210+
<plotly-chart v-if="intervention_figs && intervention_figs.id" :graph="intervention_figs" :key="intervention_figs.id" ></plotly-chart>
202211
</b-collapse>
203212
</b-card>
204213

0 commit comments

Comments
 (0)