Skip to content

Commit

Permalink
Enhancements for Dynamic and Static Occupancy Handling
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
lrdossan committed Jan 31, 2025
1 parent 604f5aa commit e27f2c2
Show file tree
Hide file tree
Showing 15 changed files with 569 additions and 553 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from caimira.calculator.validators.virus.virus_validator import VirusFormData
from caimira.calculator.store.data_registry import DataRegistry
from caimira.calculator.models.models import ExposureModel
import caimira.calculator.report.virus_report_data as rg


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

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

return report_data
5 changes: 4 additions & 1 deletion caimira/src/caimira/calculator/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ def fraction_deposited(self, evaporation_factor: float=0.3) -> _VectorisedFloat:
# deposition fraction depends on aerosol particle diameter.
d = (self.diameter * evaporation_factor)
IFrac = 1 - 0.5 * (1 - (1 / (1 + (0.00076*(d**2.8)))))
fdep = IFrac * (0.0587
fdep = IFrac * (0.0587 # type: ignore
+ (0.911/(1 + np.exp(4.77 + 1.485 * np.log(d))))
+ (0.943/(1 + np.exp(0.508 - 2.58 * np.log(d)))))
return fdep
Expand Down Expand Up @@ -1642,6 +1642,9 @@ class ExposureModel:
#: Total people with short-range interactions
exposed_to_short_range: int = 0

#: Unique group identifier
identifier: str = 'static'

#: The number of times the exposure event is repeated (default 1).
@property
def repeats(self) -> int:
Expand Down
381 changes: 191 additions & 190 deletions caimira/src/caimira/calculator/report/virus_report_data.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import json
import re

from collections import defaultdict
import numpy as np

from .defaults import DEFAULTS, NO_DEFAULT, COFFEE_OPTIONS_INT
Expand Down
31 changes: 16 additions & 15 deletions caimira/src/caimira/calculator/validators/virus/virus_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def initialize_room(self) -> models.Room:

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

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

room: models.Room = self.initialize_room()
Expand Down Expand Up @@ -295,30 +295,31 @@ def build_mc_model(self) -> typing.Union[mc.ExposureModel, mc.ExposureModelGroup
exposed=exposed_population,
geographical_data=geographical_data,
exposed_to_short_range=self.short_range_occupants,
identifier=exposure_group,
)
exposure_model_set.append(exposure_model)

if len(list(self.dynamic_exposed_occupancy.keys())) == 1:
return exposure_model_set[0]
else:
return mc.ExposureModelGroup(
data_registry=self.data_registry,
exposure_models=[individual_model.build_model(size) for individual_model in exposure_model_set]
)
return mc.ExposureModelGroup(
data_registry=self.data_registry,
exposure_models=[individual_model.build_model(size) for individual_model in exposure_model_set]
)

elif self.occupancy_format == 'static':
exposed_population = self.exposed_population()
short_range_tuple = tuple(item for sublist in short_range.values() for item in sublist)
return mc.ExposureModel(
return mc.ExposureModelGroup(
data_registry=self.data_registry,
concentration_model=concentration_model,
short_range=short_range_tuple,
exposed=exposed_population,
geographical_data=geographical_data,
exposed_to_short_range=self.short_range_occupants,
exposure_models = [mc.ExposureModel(
data_registry=self.data_registry,
concentration_model=concentration_model,
short_range=short_range_tuple,
exposed=exposed_population,
geographical_data=geographical_data,
exposed_to_short_range=self.short_range_occupants,
).build_model(size)]
)

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

Expand Down
2 changes: 1 addition & 1 deletion caimira/tests/apps/calculator/test_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

def test_model_from_dict(baseline_form_data, data_registry):
form = virus_validator.VirusFormData.from_dict(baseline_form_data, data_registry)
assert isinstance(form.build_model(), models.ExposureModel)
assert isinstance(form.build_model(), models.ExposureModelGroup)


def test_model_from_dict_invalid(baseline_form_data, data_registry):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ def prepare_context(
data_registry_version: typing.Optional[str] = f"v{model.data_registry.version}" if model.data_registry.version else None

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

# Alternative viral load data
if form.conditional_probability_viral_loads:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ p.notes {
padding: 15px;
page-break-inside: avoid;
}
#button_full_exposure, #button_hide_high_concentration {
#button_full_exposure-group_static, #button_hide_high_concentration-group_static {
display: none!important;
}
#long_range_cumulative_checkbox, #lr_cumulative_checkbox_label {
#long_range_cumulative_checkbox-group_static, #lr_cumulative_checkbox_label-group_static {
display: none!important;
}
#button_alternative_full_exposure, #button_alternative_hide_high_concentration {
#button_alternative_full_exposure-group_static, #button_alternative_hide_high_concentration-group_static {
display: none!important;
}
#export-csv {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ function displayFittingData(json_response) {
// Not needed for the form submission
delete json_response["CO2_plot_img"];
delete json_response["predictive_CO2"];
delete json_response["CO2_plot_data"];
// Convert nulls to empty strings in the JSON response
if (json_response["room_capacity"] === null) json_response["room_capacity"] = '';
if (json_response["ventilation_lsp_values"] === null) json_response["ventilation_lsp_values"] = '';
Expand Down
38 changes: 22 additions & 16 deletions cern_caimira/src/cern_caimira/apps/calculator/static/js/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ function on_report_load(conditional_probability_viral_loads) {
}

/* Generate the concentration plot using d3 library. */
function draw_plot(svg_id) {
function draw_plot(svg_id, group_id, times, concentrations_zoomed,
concentrations, cumulative_doses, long_range_cumulative_doses,
exposed_presence_intervals, short_range_interactions) {

// Used for controlling the short-range interactions
let button_full_exposure = document.getElementById("button_full_exposure");
let button_hide_high_concentration = document.getElementById("button_hide_high_concentration");
let long_range_checkbox = document.getElementById('long_range_cumulative_checkbox')
let show_sr_legend = short_range_expirations?.length > 0;
let button_full_exposure = document.getElementById(`button_full_exposure-group_${group_id}`);
let button_hide_high_concentration = document.getElementById(`button_hide_high_concentration-group_${group_id}`);
let long_range_checkbox = document.getElementById(`long_range_cumulative_checkbox-group_${group_id}`);
let show_sr_legend = short_range_interactions.length > 0;

let short_range_intervals = short_range_interactions.map((interaction) => interaction["presence_interval"]);
let short_range_expirations = short_range_interactions.map((interaction) => interaction["expiration"]);

var data_for_graphs = {
'concentrations': [],
Expand Down Expand Up @@ -521,12 +526,12 @@ function draw_plot(svg_id) {
}

// Draw for the first time to initialize.
redraw();
redraw(svg_id);
update_concentration_plot(concentrations, cumulative_doses);

// Redraw based on the new size whenever the browser window is resized.
window.addEventListener("resize", e => {
redraw();
redraw(svg_id);
if (button_full_exposure && button_full_exposure.disabled) update_concentration_plot(concentrations, cumulative_doses);
else update_concentration_plot(concentrations_zoomed, long_range_cumulative_doses)
});
Expand All @@ -536,12 +541,13 @@ function draw_plot(svg_id) {
// 'list_of_scenarios' is a dictionary with all the scenarios
// 'times' is a list of times for all the scenarios
function draw_generic_concentration_plot(
plot_svg_id,
svg_id,
times,
y_axis_label,
h_lines,
) {

if (plot_svg_id === 'CO2_concentration_graph') {
if (svg_id === 'CO2_concentration_graph') {
list_of_scenarios = {'CO₂ concentration': {'concentrations': CO2_concentrations}};
min_y_axis_domain = 400;
}
Expand Down Expand Up @@ -575,7 +581,7 @@ function draw_generic_concentration_plot(
var first_scenario = Object.values(data_for_scenarios)[0]

// Add main SVG element
var plot_div = document.getElementById(plot_svg_id);
var plot_div = document.getElementById(svg_id);
var vis = d3.select(plot_div).append('svg');

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

function update_concentration_plot(concentration_data) {
list_of_scenarios = (plot_svg_id === 'CO2_concentration_graph') ? {'CO₂ concentration': {'concentrations': CO2_concentrations}} : alternative_scenarios
list_of_scenarios = (svg_id === 'CO2_concentration_graph') ? {'CO₂ concentration': {'concentrations': CO2_concentrations}} : alternative_scenarios
var highest_concentration = 0.

for (scenario in list_of_scenarios) {
Expand Down Expand Up @@ -739,11 +745,11 @@ function draw_generic_concentration_plot(
var graph_width;
var graph_height;

function redraw() {
function redraw(svg_id) {
// Define width and height according to the screen size. Always use an already defined
var window_width = document.getElementById('concentration_plot').clientWidth;
var window_width = document.getElementById(svg_id).clientWidth;
var div_width = window_width;
var div_height = document.getElementById('concentration_plot').clientHeight;
var div_height = document.getElementById(svg_id).clientHeight;
graph_width = div_width;
graph_height = div_height;
var margins = { top: 30, right: 20, bottom: 50, left: 60 };
Expand Down Expand Up @@ -882,12 +888,12 @@ function draw_generic_concentration_plot(
}

// Draw for the first time to initialize.
redraw();
redraw(svg_id);
update_concentration_plot('concentrations');

// Redraw based on the new size whenever the browser window is resized.
window.addEventListener("resize", e => {
redraw();
redraw(svg_id);
update_concentration_plot('concentrations');
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@
</div><br>

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

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

0 comments on commit e27f2c2

Please sign in to comment.