Skip to content

Commit e27f2c2

Browse files
committed
Enhancements for Dynamic and Static Occupancy Handling
- Added default 'static' keyword to single group with static occupancy - Added default values for 'dynamic_exposed_occupancy' and 'dynamic_infected_occupancy' - Adapted default model to use ExposureModelGroup instance when static occupancy is defined - Updated JS methods to generate multiple plots when dynamic occupancy is defined - Handled the alternative scenario generation when dynamic occupancy is defined - Improved results generation for exposure groups - Updated HTML report for visualising results across multiple groups - Enhanced model representations
1 parent 604f5aa commit e27f2c2

File tree

15 files changed

+569
-553
lines changed

15 files changed

+569
-553
lines changed

caimira/src/caimira/api/controller/virus_report_controller.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from caimira.calculator.validators.virus.virus_validator import VirusFormData
66
from caimira.calculator.store.data_registry import DataRegistry
7-
from caimira.calculator.models.models import ExposureModel
87
import caimira.calculator.report.virus_report_data as rg
98

109

@@ -33,5 +32,6 @@ def submit_virus_form(form_data: typing.Dict, report_generation_parallelism: typ
3332

3433
# Handle model representation
3534
if report_data['model']: report_data['model'] = repr(report_data['model'])
35+
for single_group_output in report_data['groups'].values(): del single_group_output['model'] # Model representation per group not needed
3636

3737
return report_data

caimira/src/caimira/calculator/models/models.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def fraction_deposited(self, evaporation_factor: float=0.3) -> _VectorisedFloat:
666666
# deposition fraction depends on aerosol particle diameter.
667667
d = (self.diameter * evaporation_factor)
668668
IFrac = 1 - 0.5 * (1 - (1 / (1 + (0.00076*(d**2.8)))))
669-
fdep = IFrac * (0.0587
669+
fdep = IFrac * (0.0587 # type: ignore
670670
+ (0.911/(1 + np.exp(4.77 + 1.485 * np.log(d))))
671671
+ (0.943/(1 + np.exp(0.508 - 2.58 * np.log(d)))))
672672
return fdep
@@ -1642,6 +1642,9 @@ class ExposureModel:
16421642
#: Total people with short-range interactions
16431643
exposed_to_short_range: int = 0
16441644

1645+
#: Unique group identifier
1646+
identifier: str = 'static'
1647+
16451648
#: The number of times the exposure event is repeated (default 1).
16461649
@property
16471650
def repeats(self) -> int:

caimira/src/caimira/calculator/report/virus_report_data.py

+191-190
Large diffs are not rendered by default.

caimira/src/caimira/calculator/validators/form_validator.py

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import json
77
import re
88

9-
from collections import defaultdict
109
import numpy as np
1110

1211
from .defaults import DEFAULTS, NO_DEFAULT, COFFEE_OPTIONS_INT

caimira/src/caimira/calculator/validators/virus/virus_validator.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def initialize_room(self) -> models.Room:
245245

246246
return models.Room(volume=volume, inside_temp=models.PiecewiseConstant((0, 24), (inside_temp,)), humidity=humidity) # type: ignore
247247

248-
def build_mc_model(self) -> typing.Union[mc.ExposureModel, mc.ExposureModelGroup]:
248+
def build_mc_model(self) -> mc.ExposureModelGroup:
249249
size = self.data_registry.monte_carlo['sample_size']
250250

251251
room: models.Room = self.initialize_room()
@@ -295,30 +295,31 @@ def build_mc_model(self) -> typing.Union[mc.ExposureModel, mc.ExposureModelGroup
295295
exposed=exposed_population,
296296
geographical_data=geographical_data,
297297
exposed_to_short_range=self.short_range_occupants,
298+
identifier=exposure_group,
298299
)
299300
exposure_model_set.append(exposure_model)
300301

301-
if len(list(self.dynamic_exposed_occupancy.keys())) == 1:
302-
return exposure_model_set[0]
303-
else:
304-
return mc.ExposureModelGroup(
305-
data_registry=self.data_registry,
306-
exposure_models=[individual_model.build_model(size) for individual_model in exposure_model_set]
307-
)
302+
return mc.ExposureModelGroup(
303+
data_registry=self.data_registry,
304+
exposure_models=[individual_model.build_model(size) for individual_model in exposure_model_set]
305+
)
308306

309307
elif self.occupancy_format == 'static':
310308
exposed_population = self.exposed_population()
311309
short_range_tuple = tuple(item for sublist in short_range.values() for item in sublist)
312-
return mc.ExposureModel(
310+
return mc.ExposureModelGroup(
313311
data_registry=self.data_registry,
314-
concentration_model=concentration_model,
315-
short_range=short_range_tuple,
316-
exposed=exposed_population,
317-
geographical_data=geographical_data,
318-
exposed_to_short_range=self.short_range_occupants,
312+
exposure_models = [mc.ExposureModel(
313+
data_registry=self.data_registry,
314+
concentration_model=concentration_model,
315+
short_range=short_range_tuple,
316+
exposed=exposed_population,
317+
geographical_data=geographical_data,
318+
exposed_to_short_range=self.short_range_occupants,
319+
).build_model(size)]
319320
)
320321

321-
def build_model(self, sample_size=None) -> typing.Union[models.ExposureModel, models.ExposureModelGroup]:
322+
def build_model(self, sample_size=None) -> models.ExposureModelGroup:
322323
size = self.data_registry.monte_carlo['sample_size'] if not sample_size else sample_size
323324
return self.build_mc_model().build_model(size=size)
324325

caimira/tests/apps/calculator/test_model_generator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
def test_model_from_dict(baseline_form_data, data_registry):
1919
form = virus_validator.VirusFormData.from_dict(baseline_form_data, data_registry)
20-
assert isinstance(form.build_model(), models.ExposureModel)
20+
assert isinstance(form.build_model(), models.ExposureModelGroup)
2121

2222

2323
def test_model_from_dict_invalid(baseline_form_data, data_registry):

cern_caimira/src/cern_caimira/apps/calculator/report/virus_report.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ def prepare_context(
123123
data_registry_version: typing.Optional[str] = f"v{model.data_registry.version}" if model.data_registry.version else None
124124

125125
# Alternative scenarios data
126-
alternative_scenarios: typing.Dict[str,typing.Any] = alternative_scenarios_data(form, report_data, executor_factory)
127-
context.update(alternative_scenarios)
126+
if form.occupancy_format == 'static':
127+
context.update(alternative_scenarios_data(form, report_data, executor_factory))
128128

129129
# Alternative viral load data
130130
if form.conditional_probability_viral_loads:

cern_caimira/src/cern_caimira/apps/calculator/static/css/report.css

+3-3
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,13 @@ p.notes {
126126
padding: 15px;
127127
page-break-inside: avoid;
128128
}
129-
#button_full_exposure, #button_hide_high_concentration {
129+
#button_full_exposure-group_static, #button_hide_high_concentration-group_static {
130130
display: none!important;
131131
}
132-
#long_range_cumulative_checkbox, #lr_cumulative_checkbox_label {
132+
#long_range_cumulative_checkbox-group_static, #lr_cumulative_checkbox_label-group_static {
133133
display: none!important;
134134
}
135-
#button_alternative_full_exposure, #button_alternative_hide_high_concentration {
135+
#button_alternative_full_exposure-group_static, #button_alternative_hide_high_concentration-group_static {
136136
display: none!important;
137137
}
138138
#export-csv {

cern_caimira/src/cern_caimira/apps/calculator/static/js/co2_form.js

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ function displayFittingData(json_response) {
301301
// Not needed for the form submission
302302
delete json_response["CO2_plot_img"];
303303
delete json_response["predictive_CO2"];
304+
delete json_response["CO2_plot_data"];
304305
// Convert nulls to empty strings in the JSON response
305306
if (json_response["room_capacity"] === null) json_response["room_capacity"] = '';
306307
if (json_response["ventilation_lsp_values"] === null) json_response["ventilation_lsp_values"] = '';

cern_caimira/src/cern_caimira/apps/calculator/static/js/report.js

+22-16
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ function on_report_load(conditional_probability_viral_loads) {
44
}
55

66
/* Generate the concentration plot using d3 library. */
7-
function draw_plot(svg_id) {
7+
function draw_plot(svg_id, group_id, times, concentrations_zoomed,
8+
concentrations, cumulative_doses, long_range_cumulative_doses,
9+
exposed_presence_intervals, short_range_interactions) {
810

911
// Used for controlling the short-range interactions
10-
let button_full_exposure = document.getElementById("button_full_exposure");
11-
let button_hide_high_concentration = document.getElementById("button_hide_high_concentration");
12-
let long_range_checkbox = document.getElementById('long_range_cumulative_checkbox')
13-
let show_sr_legend = short_range_expirations?.length > 0;
12+
let button_full_exposure = document.getElementById(`button_full_exposure-group_${group_id}`);
13+
let button_hide_high_concentration = document.getElementById(`button_hide_high_concentration-group_${group_id}`);
14+
let long_range_checkbox = document.getElementById(`long_range_cumulative_checkbox-group_${group_id}`);
15+
let show_sr_legend = short_range_interactions.length > 0;
16+
17+
let short_range_intervals = short_range_interactions.map((interaction) => interaction["presence_interval"]);
18+
let short_range_expirations = short_range_interactions.map((interaction) => interaction["expiration"]);
1419

1520
var data_for_graphs = {
1621
'concentrations': [],
@@ -521,12 +526,12 @@ function draw_plot(svg_id) {
521526
}
522527

523528
// Draw for the first time to initialize.
524-
redraw();
529+
redraw(svg_id);
525530
update_concentration_plot(concentrations, cumulative_doses);
526531

527532
// Redraw based on the new size whenever the browser window is resized.
528533
window.addEventListener("resize", e => {
529-
redraw();
534+
redraw(svg_id);
530535
if (button_full_exposure && button_full_exposure.disabled) update_concentration_plot(concentrations, cumulative_doses);
531536
else update_concentration_plot(concentrations_zoomed, long_range_cumulative_doses)
532537
});
@@ -536,12 +541,13 @@ function draw_plot(svg_id) {
536541
// 'list_of_scenarios' is a dictionary with all the scenarios
537542
// 'times' is a list of times for all the scenarios
538543
function draw_generic_concentration_plot(
539-
plot_svg_id,
544+
svg_id,
545+
times,
540546
y_axis_label,
541547
h_lines,
542548
) {
543549

544-
if (plot_svg_id === 'CO2_concentration_graph') {
550+
if (svg_id === 'CO2_concentration_graph') {
545551
list_of_scenarios = {'CO₂ concentration': {'concentrations': CO2_concentrations}};
546552
min_y_axis_domain = 400;
547553
}
@@ -575,7 +581,7 @@ function draw_generic_concentration_plot(
575581
var first_scenario = Object.values(data_for_scenarios)[0]
576582

577583
// Add main SVG element
578-
var plot_div = document.getElementById(plot_svg_id);
584+
var plot_div = document.getElementById(svg_id);
579585
var vis = d3.select(plot_div).append('svg');
580586

581587
var xRange = d3.scaleTime().domain([first_scenario[0].hour, first_scenario[first_scenario.length - 1].hour]);
@@ -706,7 +712,7 @@ function draw_generic_concentration_plot(
706712
}
707713

708714
function update_concentration_plot(concentration_data) {
709-
list_of_scenarios = (plot_svg_id === 'CO2_concentration_graph') ? {'CO₂ concentration': {'concentrations': CO2_concentrations}} : alternative_scenarios
715+
list_of_scenarios = (svg_id === 'CO2_concentration_graph') ? {'CO₂ concentration': {'concentrations': CO2_concentrations}} : alternative_scenarios
710716
var highest_concentration = 0.
711717

712718
for (scenario in list_of_scenarios) {
@@ -739,11 +745,11 @@ function draw_generic_concentration_plot(
739745
var graph_width;
740746
var graph_height;
741747

742-
function redraw() {
748+
function redraw(svg_id) {
743749
// Define width and height according to the screen size. Always use an already defined
744-
var window_width = document.getElementById('concentration_plot').clientWidth;
750+
var window_width = document.getElementById(svg_id).clientWidth;
745751
var div_width = window_width;
746-
var div_height = document.getElementById('concentration_plot').clientHeight;
752+
var div_height = document.getElementById(svg_id).clientHeight;
747753
graph_width = div_width;
748754
graph_height = div_height;
749755
var margins = { top: 30, right: 20, bottom: 50, left: 60 };
@@ -882,12 +888,12 @@ function draw_generic_concentration_plot(
882888
}
883889

884890
// Draw for the first time to initialize.
885-
redraw();
891+
redraw(svg_id);
886892
update_concentration_plot('concentrations');
887893

888894
// Redraw based on the new size whenever the browser window is resized.
889895
window.addEventListener("resize", e => {
890-
redraw();
896+
redraw(svg_id);
891897
update_concentration_plot('concentrations');
892898
});
893899
}

cern_caimira/src/cern_caimira/apps/templates/base/calculator.form.html.j2

+2-2
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,8 @@
455455
</div><br>
456456

457457
<input type="text" class="form-control d-none" name="occupancy_format" value="static" required> {# "static" vs. "dynamic" #}
458-
<input type="text" class="form-control d-none" name="dynamic_exposed_occupancy">
459-
<input type="text" class="form-control d-none" name="dynamic_infected_occupancy">
458+
<input type="text" class="form-control d-none" name="dynamic_exposed_occupancy" value="{}">
459+
<input type="text" class="form-control d-none" name="dynamic_infected_occupancy" value="[]">
460460

461461
<div class="form-group row">
462462
<div class="col-sm-4"><label class="col-form-label">Total number of occupants:</label></div>

0 commit comments

Comments
 (0)