From bc9a25a7e4824d49813133e072d858a5e334041d Mon Sep 17 00:00:00 2001
From: Luis Aleixo
Date: Tue, 8 Feb 2022 09:57:37 +0000
Subject: [PATCH] UI adjustments on the short range interactions
Removed unused code
---
cara/apps/calculator/model_generator.py | 63 ++++----
cara/apps/calculator/report_generator.py | 42 +++--
cara/apps/calculator/static/js/form.js | 15 +-
cara/apps/calculator/static/js/report.js | 55 ++++++-
cara/apps/expert.py | 1 -
.../templates/base/calculator.report.html.j2 | 15 +-
cara/models.py | 148 +++++++++++++++---
cara/monte_carlo/data.py | 15 +-
8 files changed, 266 insertions(+), 88 deletions(-)
diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py
index da43e39c..b05da082 100644
--- a/cara/apps/calculator/model_generator.py
+++ b/cara/apps/calculator/model_generator.py
@@ -13,8 +13,8 @@ from cara import data
import cara.data.weather
import cara.monte_carlo as mc
from .. import calculator
-from cara.monte_carlo.data import activity_distributions, dilution_factor, virus_distributions, mask_distributions, initial_concentrations_mouth
-from cara.monte_carlo.data import expiration_distribution, expiration_BLO_factors, expiration_distributions
+from cara.monte_carlo.data import activity_distributions, virus_distributions, mask_distributions, dilution_factor
+from cara.monte_carlo.data import expiration_distribution, expiration_BLO_factors, expiration_distributions, short_range_expiration_distributions
LOG = logging.getLogger(__name__)
@@ -234,7 +234,7 @@ class FormData:
raise ValueError("mechanical_ventilation_type cannot be 'not-applicable' if "
"ventilation_type is 'mechanical_ventilation'")
- def build_mc_model(self) -> mc.ExposureModel:
+ def build_mc_model(self) -> mc.SimulationModel:
# Initializes room with volume either given directly or as product of area and height
if self.volume_type == 'room_volume_explicit':
volume = self.room_volume
@@ -253,28 +253,29 @@ class FormData:
sr_presence=self.short_range_intervals()
sr_activities=self.short_range_activities()
- jet_origin_concentration = [initial_concentrations_mouth[activity] for activity in sr_activities]
-
- short_range = models.ShortRangeModel(
- presence=sr_presence,
- activities=sr_activities,
- dilutions=dilution_factor(activities=sr_activities, distance=np.random.uniform(0.5, 1.5, 250000)),
- jet_origin_concentrations=jet_origin_concentration,
- )
+ short_range_expirations = [short_range_expiration_distributions[activity] for activity in sr_activities]
# Initializes and returns a model with the attributes defined above
- return mc.ExposureModel(
- concentration_model=mc.ConcentrationModel(
- room=room,
- ventilation=self.ventilation(),
- infected=self.infected_population(),
- short_range=short_range,
- evaporation_factor=0.3,
+ return mc.SimulationModel(
+ mc.ExposureModel(
+ concentration_model=mc.ConcentrationModel(
+ room=room,
+ ventilation=self.ventilation(),
+ infected=self.infected_population(),
+ evaporation_factor=0.3,
+ ),
+ exposed=self.exposed_population(),
+ ),
+ mc.ShortRangeModel(
+ presence=sr_presence,
+ long_range_presence=self.long_range_intervals(),
+ activities=sr_activities,
+ expirations=short_range_expirations,
+ dilutions=dilution_factor(activities=sr_activities, distance=np.random.uniform(0.5, 1.5, 250000)),
),
- exposed=self.exposed_population(),
)
- def build_model(self, sample_size=_DEFAULT_MC_SAMPLE_SIZE) -> models.ExposureModel:
+ def build_model(self, sample_size=_DEFAULT_MC_SAMPLE_SIZE) -> models.SimulationModel:
return self.build_mc_model().build_model(size=sample_size)
def tz_name_and_utc_offset(self) -> typing.Tuple[str, float]:
@@ -643,7 +644,7 @@ class FormData:
def infected_present_interval(self) -> models.Interval:
return self.present_interval(
self.infected_start, self.infected_finish,
- breaks=self.infected_lunch_break_times() + self.infected_coffee_break_times(),
+ breaks=self.infected_lunch_break_times() + self.infected_coffee_break_times()
)
def short_range_intervals(self) -> typing.List[models.SpecificInterval]:
@@ -657,10 +658,22 @@ class FormData:
else:
return []
+ def long_range_intervals(self) -> models.Interval:
+ short_range_intervals = []
+ for interval in self.short_range_interactions:
+ short_range_intervals.append(
+ (time_string_to_minutes(interval['start_time']),
+ time_string_to_minutes(interval['start_time']) + float(interval['duration'])),
+ )
+ return self.present_interval(
+ self.exposed_start, self.exposed_finish,
+ breaks=self.infected_lunch_break_times() + self.infected_coffee_break_times() + tuple(short_range_intervals),
+ )
+
def exposed_present_interval(self) -> models.Interval:
return self.present_interval(
self.exposed_start, self.exposed_finish,
- breaks=self.exposed_lunch_break_times() + self.exposed_coffee_break_times(),
+ breaks=self.exposed_lunch_break_times() + self.exposed_coffee_break_times()
)
def short_range_activities(self) -> typing.List[str]:
@@ -678,12 +691,6 @@ def build_expiration(expiration_definition) -> models._ExpirationBase:
for exp_type, weight in expiration_definition.items()
], axis=0)
return expiration_distribution(tuple(BLO_factors))
-
-
-def build_concentration_mouth(short_range_def) -> models._ExpirationBase:
- expiration_definition = short_range_def[1]['activity']
- if isinstance(expiration_definition, str):
- return initial_concentrations_mouth[expiration_definition]
def baseline_raw_form_data():
diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py
index 258ed6bf..ef8bafc4 100644
--- a/cara/apps/calculator/report_generator.py
+++ b/cara/apps/calculator/report_generator.py
@@ -96,18 +96,29 @@ def interesting_times(model: models.ExposureModel, approx_n_pts=100) -> typing.L
return nice_times
-def calculate_report_data(model: models.ExposureModel):
- times = interesting_times(model)
+def short_range_interesting_times(model: models.ExposureModel, times: typing.List[float]) -> typing.List[float]:
+ short_range_times : typing.List[float] = []
+ for period in model.concentration_model.infected.short_range_presence:
+ start, finish = tuple(period.boundaries())
+ short_range_times = short_range_times + [time for time in times if time >= start and time <= finish]
+ return short_range_times
+
+
+def calculate_report_data(model: models.SimulationModel):
+ times = interesting_times(model.exposure_model)
+ short_range_intervals = []
+ for interval in model.short_range.presence:
+ short_range_intervals.append(list(interval.boundaries()))
concentrations = [
- np.array(model.concentration_model.concentration(float(time))).mean()
+ np.array(model.concentration(float(time))).mean()
for time in times
]
highest_const = max(concentrations)
prob = np.array(model.infection_probability()).mean()
- er = np.array(model.concentration_model.infected.emission_rate_when_present()).mean()
- exposed_occupants = model.exposed.number
- expected_new_cases = np.array(model.expected_new_cases()).mean()
+ er = np.array(model.exposure_model.concentration_model.infected.emission_rate_when_present()).mean()
+ exposed_occupants = model.exposure_model.exposed.number
+ expected_new_cases = np.array(model.exposure_model.expected_new_cases()).mean()
cumulative_doses = np.cumsum([
np.array(model.deposited_exposure_between_bounds(float(time1), float(time2))).mean()
for time1, time2 in zip(times[:-1], times[1:])
@@ -115,7 +126,8 @@ def calculate_report_data(model: models.ExposureModel):
return {
"times": list(times),
- "exposed_presence_intervals": [list(interval) for interval in model.exposed.presence.boundaries()],
+ "short_range_intervals": short_range_intervals,
+ "exposed_presence_intervals": [list(interval) for interval in model.exposure_model.exposed.presence.boundaries()],
"cumulative_doses": list(cumulative_doses),
"concentrations": concentrations,
"highest_const": highest_const,
@@ -234,20 +246,20 @@ def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, mc.Exp
return scenarios
-def scenario_statistics(mc_model: mc.ExposureModel, sample_times: np.ndarray):
+def scenario_statistics(mc_model: mc.SimulationModel, sample_times: np.ndarray):
model = mc_model.build_model(size=_DEFAULT_MC_SAMPLE_SIZE)
return {
- 'probability_of_infection': np.mean(model.infection_probability()),
- 'expected_new_cases': np.mean(model.expected_new_cases()),
+ 'probability_of_infection': np.mean(model.exposure_model.infection_probability()),
+ 'expected_new_cases': np.mean(model.exposure_model.expected_new_cases()),
'concentrations': [
- np.mean(model.concentration_model.concentration(time))
+ np.mean(model.exposure_model.concentration_model.concentration(time))
for time in sample_times
],
}
def comparison_report(
- scenarios: typing.Dict[str, mc.ExposureModel],
+ scenarios: typing.Dict[str, mc.SimulationModel],
sample_times: typing.List[float],
executor_factory: typing.Callable[[], concurrent.futures.Executor],
):
@@ -285,7 +297,7 @@ class ReportGenerator:
def prepare_context(
self,
base_url: str,
- model: models.ExposureModel,
+ model: models.SimulationModel,
form: FormData,
executor_factory: typing.Callable[[], concurrent.futures.Executor],
) -> dict:
@@ -293,12 +305,12 @@ class ReportGenerator:
time = now.strftime("%Y-%m-%d %H:%M:%S UTC")
context = {
- 'model': model,
+ 'model': model.exposure_model,
'form': form,
'creation_date': time,
}
- scenario_sample_times = interesting_times(model)
+ scenario_sample_times = interesting_times(model.exposure_model)
context.update(calculate_report_data(model))
alternative_scenarios = manufacture_alternative_scenarios(form)
diff --git a/cara/apps/calculator/static/js/form.js b/cara/apps/calculator/static/js/form.js
index 575179bd..b041ffa3 100644
--- a/cara/apps/calculator/static/js/form.js
+++ b/cara/apps/calculator/static/js/form.js
@@ -404,6 +404,10 @@ function validate_form(form) {
short_range_interactions.push(JSON.stringify(obj));
});
$("input[type=text][name=short_range_interactions]").val('[' + short_range_interactions + ']');
+ if (short_range_interactions.length == 0) {
+ $("input[type=radio][id=short_range_no]").prop("checked", true);
+ on_short_range_option_change();
+ }
if (submit) {
$("#generate_report").prop("disabled", true);
@@ -859,7 +863,11 @@ $(document).ready(function () {
//Short range modal - save button
$("body").on("click", ".save_btn_frm_field", function() {
var last_element = $(".form_field_outer").find(".form_field_outer_row").last().find(".short_range_option").prop("id");
- if (!last_element) $('#short_range_dialog').modal('hide');
+ if (!last_element) {
+ $('#short_range_dialog').modal('hide');
+ $("input[type=radio][id=short_range_no]").prop("checked", true);
+ on_short_range_option_change();
+ }
else {
let index = last_element.split("_").slice(-1)[0];
let activity = validate_sr_parameter('#sr_activity_no_' + String(index)[0], "You must specify the activity type.");
@@ -875,13 +883,14 @@ $(document).ready(function () {
}
});
- //Short range modal - close button
+ //Short range modal - reset button
$("body").on("click", ".dismiss_btn_frm_field", function() {
$(".form_field_outer_row").remove();
$("#sr_interactions").text(0);
+ $('input[type=radio][id=short_range_no]').prop("checked", true);
+ on_short_range_option_change();
});
-
});
diff --git a/cara/apps/calculator/static/js/report.js b/cara/apps/calculator/static/js/report.js
index c9c79841..a185b094 100644
--- a/cara/apps/calculator/static/js/report.js
+++ b/cara/apps/calculator/static/js/report.js
@@ -1,5 +1,5 @@
/* Generate the concentration plot using d3 library. */
-function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses, exposed_presence_intervals) {
+function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses, exposed_presence_intervals, short_range_intervals) {
var time_format = d3.timeFormat('%H:%M');
@@ -50,6 +50,16 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
.attr('fill-opacity', '0.1');
});
+ // Area representing the short range interaction(s).
+ var shortRangeArea = {};
+ var drawShortRangeArea = {};
+ short_range_intervals.forEach((b, index) => {
+ shortRangeArea[index] = d3.area();
+ drawShortRangeArea[index] = vis.append('svg:path')
+ .attr('fill', '#1f00b4')
+ .attr('fill-opacity', '0.1');
+ });
+
// Plot tittle.
var plotTitleEl = vis.append('svg:foreignObject')
.attr("background-color", "transparent")
@@ -105,10 +115,16 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
var legendAreaIcon = vis.append('rect')
.attr('width', 20)
- .attr('height', 20)
+ .attr('height', 15)
.attr('fill', '#1f77b4')
.attr('fill-opacity', '0.1');
+ var legendShortRangeAreaIcon = vis.append('rect')
+ .attr('width', 20)
+ .attr('height', 15)
+ .attr('fill', '#1f00b4')
+ .attr('fill-opacity', '0.1');
+
var legendLineText = vis.append('text')
.text('Mean concentration')
.style('font-size', '15px')
@@ -124,10 +140,15 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
.style('font-size', '15px')
.attr('alignment-baseline', 'central');
+ var legendShortRangeText = vis.append('text')
+ .text('Short range interaction(s)')
+ .style('font-size', '15px')
+ .attr('alignment-baseline', 'central');
+
// Legend bounding
var legendBBox = vis.append('rect')
.attr('width', 255)
- .attr('height', 70)
+ .attr('height', 90)
.attr('stroke', 'lightgrey')
.attr('stroke-width', '2')
.attr('rx', '5px')
@@ -217,7 +238,7 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
.y(d => yRange(d.concentration));
draw_line.attr("d", lineFunc(data_for_graphs.concentrations));
- // Cumulative line
+ // Cumulative line.
lineCumulative.defined(d => !isNaN(d.concentration))
.x(d => xTimeRange(d.time))
.y(d => yCumulativeRange(d.concentration));
@@ -234,6 +255,18 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
})));
});
+ // Short Range Area.
+ short_range_intervals.forEach((b, index) => {
+ shortRangeArea[index].x(d => xTimeRange(d.time))
+ .y0(graph_height - margins.bottom)
+ .y1(d => yRange(d.concentration));
+
+ drawShortRangeArea[index].attr('d', shortRangeArea[index](data_for_graphs.concentrations.filter(d => {
+ return d.time >= b[0] && d.time <= b[1]
+ })))
+ })
+
+
// Title.
plotTitleEl.attr('width', graph_width);
@@ -282,10 +315,15 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
.attr('y', margins.top + 2 * size);
legendAreaIcon.attr('x', graph_width + size * 2.5)
- .attr('y', margins.top + 2.5 * size);
+ .attr('y', margins.top + 2.6 * size);
legendAreaText.attr('x', graph_width + 4 * size)
.attr('y', margins.top + 3 * size);
+ legendShortRangeAreaIcon.attr('x', graph_width + size * 2.5)
+ .attr('y', margins.top + 3.6 * size);
+ legendShortRangeText.attr('x', graph_width + 4 * size)
+ .attr('y', margins.top + 4 * size);
+
legendBBox.attr('x', graph_width * 1.07)
.attr('y', margins.top * 1.2);
}
@@ -306,7 +344,12 @@ function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses
legendAreaIcon.attr('x', size * 0.50)
.attr('y', graph_height * 1.09 + size);
legendAreaText.attr('x', 2 * size)
- .attr('y', graph_height + 2.7 * size);
+ .attr('y', graph_height + 2.6 * size);
+
+ legendShortRangeAreaIcon.attr('x', size * 0.50)
+ .attr('y', graph_height * 1.175 + size);
+ legendShortRangeText.attr('x', 2 * size)
+ .attr('y', graph_height + 3.65 * size);
legendBBox.attr('x', 1)
.attr('y', graph_height);
diff --git a/cara/apps/expert.py b/cara/apps/expert.py
index a7f82243..bba12b6b 100644
--- a/cara/apps/expert.py
+++ b/cara/apps/expert.py
@@ -501,7 +501,6 @@ baseline_model = models.ExposureModel(
expiration=models.Expiration.types['Speaking'],
host_immunity=0.,
),
- short_range=[],
evaporation_factor=0.3,
),
exposed=models.Population(
diff --git a/cara/apps/templates/base/calculator.report.html.j2 b/cara/apps/templates/base/calculator.report.html.j2
index b113710b..7278aab5 100644
--- a/cara/apps/templates/base/calculator.report.html.j2
+++ b/cara/apps/templates/base/calculator.report.html.j2
@@ -96,7 +96,8 @@
var concentrations = {{ concentrations | JSONify }}
var cumulative_doses = {{ cumulative_doses | JSONify }}
var exposed_presence_intervals = {{ exposed_presence_intervals | JSONify }}
- draw_concentration_plot("concentration_plot", times, concentrations, cumulative_doses, exposed_presence_intervals);
+ var short_range_intervals = {{ short_range_intervals | JSONify }}
+ draw_concentration_plot("concentration_plot", times, concentrations, cumulative_doses, exposed_presence_intervals, short_range_intervals);
@@ -309,6 +310,18 @@
Gym = For comparison only, all persons doing heavy physical exercise, breathing and not speaking.
{% endif %}
+ {% if form.short_range_option == "short_range_yes" %}
+
+ Short range interactions: {{ form.short_range_interactions|length }}
+
+
+ {% for interaction in form.short_range_interactions %}
+ - Expiratory activity {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.activity }}
+ - Start time {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.start_time }}
+ - Duration {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.duration }} {{ "minutes" if interaction.duration|int > 1 else "minute" }}
+ {% endfor %}
+
+ {% endif %}
Exposed occupant(s) activity time: