Merge branch 'feature/p_one_infected' into 'master'
Probability that one individual is infected See merge request cara/caimira!316
This commit is contained in:
commit
6c0d334270
20 changed files with 368 additions and 36 deletions
|
|
@ -33,7 +33,7 @@ from .user import AuthenticatedUser, AnonymousUser
|
|||
# calculator version. If the calculator needs to make breaking changes (e.g. change
|
||||
# form attributes) then it can also increase its MAJOR version without needing to
|
||||
# increase the overall CAiMIRA version (found at ``caimira.__version__``).
|
||||
__version__ = "4.3"
|
||||
__version__ = "4.4"
|
||||
|
||||
|
||||
class BaseRequestHandler(RequestHandler):
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ class FormData:
|
|||
location_name: str
|
||||
location_latitude: float
|
||||
location_longitude: float
|
||||
geographic_population: int
|
||||
geographic_cases: int
|
||||
ascertainment_bias: str
|
||||
exposure_option: str
|
||||
mask_type: str
|
||||
mask_wearing_option: str
|
||||
mechanical_ventilation_type: str
|
||||
|
|
@ -110,12 +114,16 @@ class FormData:
|
|||
'infected_lunch_finish': '13:30',
|
||||
'infected_lunch_option': True,
|
||||
'infected_lunch_start': '12:30',
|
||||
'infected_people': _NO_DEFAULT,
|
||||
'infected_people': 1,
|
||||
'infected_start': '08:30',
|
||||
'inside_temp': _NO_DEFAULT,
|
||||
'location_latitude': _NO_DEFAULT,
|
||||
'location_longitude': _NO_DEFAULT,
|
||||
'location_name': _NO_DEFAULT,
|
||||
'geographic_population': 0,
|
||||
'geographic_cases': 0,
|
||||
'ascertainment_bias': 'confidence_low',
|
||||
'exposure_option': 'p_deterministic_exposure',
|
||||
'mask_type': 'Type I',
|
||||
'mask_wearing_option': 'mask_off',
|
||||
'mechanical_ventilation_type': 'not-applicable',
|
||||
|
|
@ -261,7 +269,9 @@ class FormData:
|
|||
('volume_type', VOLUME_TYPES),
|
||||
('window_opening_regime', WINDOWS_OPENING_REGIMES),
|
||||
('window_type', WINDOWS_TYPES),
|
||||
('event_month', MONTH_NAMES)]
|
||||
('event_month', MONTH_NAMES),
|
||||
('ascertainment_bias', CONFIDENCE_LEVEL_OPTIONS),]
|
||||
|
||||
for attr_name, valid_set in validation_tuples:
|
||||
if getattr(self, attr_name) not in valid_set:
|
||||
raise ValueError(f"{getattr(self, attr_name)} is not a valid value for {attr_name}")
|
||||
|
|
@ -329,6 +339,11 @@ class FormData:
|
|||
),
|
||||
short_range = tuple(short_range),
|
||||
exposed=self.exposed_population(),
|
||||
geographical_data=mc.Cases(
|
||||
geographic_population=self.geographic_population,
|
||||
geographic_cases=self.geographic_cases,
|
||||
ascertainment_bias=CONFIDENCE_LEVEL_OPTIONS[self.ascertainment_bias],
|
||||
),
|
||||
)
|
||||
|
||||
def build_model(self, sample_size=_DEFAULT_MC_SAMPLE_SIZE) -> models.ExposureModel:
|
||||
|
|
@ -477,6 +492,7 @@ class FormData:
|
|||
'callcentre': ('Seated', 'Speaking'),
|
||||
'library': ('Seated', 'Breathing'),
|
||||
'training': ('Standing', 'Speaking'),
|
||||
'training_attendee': ('Seated', 'Breathing'),
|
||||
'lab': (
|
||||
'Light activity',
|
||||
#Model 1/2 of time spent speaking in a lab.
|
||||
|
|
@ -515,6 +531,7 @@ class FormData:
|
|||
'callcentre': 'Seated',
|
||||
'library': 'Seated',
|
||||
'training': 'Seated',
|
||||
'training_attendee': 'Seated',
|
||||
'workshop': 'Moderate activity',
|
||||
'lab':'Light activity',
|
||||
'gym':'Heavy exercise',
|
||||
|
|
@ -759,6 +776,9 @@ def baseline_raw_form_data() -> typing.Dict[str, typing.Union[str, float]]:
|
|||
'location_latitude': 46.20833,
|
||||
'location_longitude': 6.14275,
|
||||
'location_name': 'Geneva',
|
||||
'geographic_population': 0,
|
||||
'geographic_cases': 0,
|
||||
'ascertainment_bias': 'confidence_low',
|
||||
'mask_type': 'Type I',
|
||||
'mask_wearing_option': 'mask_off',
|
||||
'mechanical_ventilation_type': '',
|
||||
|
|
@ -785,7 +805,7 @@ def baseline_raw_form_data() -> typing.Dict[str, typing.Union[str, float]]:
|
|||
}
|
||||
|
||||
|
||||
ACTIVITY_TYPES = {'office', 'smallmeeting', 'largemeeting', 'training', 'callcentre', 'controlroom-day', 'controlroom-night', 'library', 'workshop', 'lab', 'gym'}
|
||||
ACTIVITY_TYPES = {'office', 'smallmeeting', 'largemeeting', 'training', 'training_attendee', 'callcentre', 'controlroom-day', 'controlroom-night', 'library', 'workshop', 'lab', 'gym'}
|
||||
MECHANICAL_VENTILATION_TYPES = {'mech_type_air_changes', 'mech_type_air_supply', 'not-applicable'}
|
||||
MASK_TYPES = {'Type I', 'FFP2'}
|
||||
MASK_WEARING_OPTIONS = {'mask_on', 'mask_off'}
|
||||
|
|
@ -794,9 +814,8 @@ VIRUS_TYPES = {'SARS_CoV_2', 'SARS_CoV_2_ALPHA', 'SARS_CoV_2_BETA','SARS_CoV_2_G
|
|||
VOLUME_TYPES = {'room_volume_explicit', 'room_volume_from_dimensions'}
|
||||
WINDOWS_OPENING_REGIMES = {'windows_open_permanently', 'windows_open_periodically', 'not-applicable'}
|
||||
WINDOWS_TYPES = {'window_sliding', 'window_hinged', 'not-applicable'}
|
||||
|
||||
COFFEE_OPTIONS_INT = {'coffee_break_0': 0, 'coffee_break_1': 1, 'coffee_break_2': 2, 'coffee_break_4': 4}
|
||||
|
||||
CONFIDENCE_LEVEL_OPTIONS = {'confidence_low': 10, 'confidence_medium': 5, 'confidence_high': 2}
|
||||
MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June', 'July',
|
||||
'August', 'September', 'October', 'November', 'December',
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ def calculate_report_data(form: FormData, model: models.ExposureModel) -> typing
|
|||
])
|
||||
|
||||
prob = np.array(model.infection_probability()).mean()
|
||||
prob_probabilistic_exposure = np.array(model.total_probability_rule()).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()
|
||||
|
|
@ -147,6 +148,7 @@ def calculate_report_data(form: FormData, model: models.ExposureModel) -> typing
|
|||
"cumulative_doses": list(cumulative_doses),
|
||||
"long_range_cumulative_doses": list(long_range_cumulative_doses),
|
||||
"prob_inf": prob,
|
||||
"prob_probabilistic_exposure": prob_probabilistic_exposure,
|
||||
"emission_rate": er,
|
||||
"exposed_occupants": exposed_occupants,
|
||||
"expected_new_cases": expected_new_cases,
|
||||
|
|
@ -272,8 +274,13 @@ def manufacture_alternative_scenarios(form: FormData) -> typing.Dict[str, mc.Exp
|
|||
return scenarios
|
||||
|
||||
|
||||
def scenario_statistics(mc_model: mc.ExposureModel, sample_times: typing.List[float]):
|
||||
def scenario_statistics(mc_model: mc.ExposureModel, sample_times: typing.List[float], compute_prob_exposure: bool):
|
||||
model = mc_model.build_model(size=_DEFAULT_MC_SAMPLE_SIZE)
|
||||
if (compute_prob_exposure):
|
||||
# It means we have data to calculate the total_probability_rule
|
||||
prob_probabilistic_exposure = model.total_probability_rule()
|
||||
else:
|
||||
prob_probabilistic_exposure = 0.
|
||||
|
||||
return {
|
||||
'probability_of_infection': np.mean(model.infection_probability()),
|
||||
|
|
@ -282,6 +289,7 @@ def scenario_statistics(mc_model: mc.ExposureModel, sample_times: typing.List[fl
|
|||
np.mean(model.concentration(time))
|
||||
for time in sample_times
|
||||
],
|
||||
'prob_probabilistic_exposure': prob_probabilistic_exposure,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -303,11 +311,17 @@ def comparison_report(
|
|||
else:
|
||||
statistics = {}
|
||||
|
||||
if (form.short_range_option == "short_range_yes" and form.exposure_option == "p_probabilistic_exposure"):
|
||||
compute_prob_exposure = True
|
||||
else:
|
||||
compute_prob_exposure = False
|
||||
|
||||
with executor_factory() as executor:
|
||||
results = executor.map(
|
||||
scenario_statistics,
|
||||
scenarios.values(),
|
||||
[sample_times] * len(scenarios),
|
||||
[compute_prob_exposure] * len(scenarios),
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -113,4 +113,4 @@
|
|||
transition-delay: 0.5s; /* Starting after the grow effect */
|
||||
transition-duration: 0.2s;
|
||||
transform: translateX(-50%) scaleY(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@ function require_fields(obj) {
|
|||
case "hepa_no":
|
||||
require_hepa(false);
|
||||
break;
|
||||
case "p_probabilistic_exposure":
|
||||
require_population(true);
|
||||
require_infected(false);
|
||||
break;
|
||||
case "p_deterministic_exposure":
|
||||
require_population(false);
|
||||
require_infected(true);
|
||||
break;
|
||||
case "mask_on":
|
||||
require_mask(true);
|
||||
break;
|
||||
|
|
@ -172,6 +180,16 @@ function require_lunch(id, option) {
|
|||
}
|
||||
}
|
||||
|
||||
function require_population(option) {
|
||||
require_input_field("#geographic_population", option);
|
||||
require_input_field("#geographic_cases", option);
|
||||
require_input_field("#ascertainment_bias", option);
|
||||
}
|
||||
|
||||
function require_infected(option) {
|
||||
require_input_field("#infected_people", option);
|
||||
}
|
||||
|
||||
function require_mask(option) {
|
||||
$("#mask_type_1").prop('required', option);
|
||||
$("#mask_type_ffp2").prop('required', option);
|
||||
|
|
@ -269,6 +287,23 @@ function on_hepa_option_change() {
|
|||
})
|
||||
}
|
||||
|
||||
function on_exposure_change() {
|
||||
p_recurrent = $('input[type=radio][name=exposure_option]')
|
||||
p_recurrent.each(function (index) {
|
||||
if (this.checked) {
|
||||
getChildElement($(this)).show();
|
||||
require_fields(this);
|
||||
}
|
||||
else {
|
||||
getChildElement($(this)).hide();
|
||||
unrequire_fields(this);
|
||||
|
||||
//Clear invalid inputs for this newly hidden child element
|
||||
removeInvalid("#"+getChildElement($(this)).find('input').not('input[type=radio]').attr('id'));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function on_wearing_mask_change() {
|
||||
wearing_mask = $('input[type=radio][name=mask_wearing_option]')
|
||||
wearing_mask.each(function (index) {
|
||||
|
|
@ -538,6 +573,20 @@ function validate_form(form) {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate cases < population
|
||||
if ($("#p_probabilistic_exposure").prop('checked')) {
|
||||
// Set number of infected people as 1
|
||||
$("#infected_people").val(1);
|
||||
var geographicPopulationObj = document.getElementById("geographic_population");
|
||||
var geographicCasesObj = document.getElementById("geographic_cases");
|
||||
removeErrorFor(geographicCasesObj);
|
||||
|
||||
if (parseInt(geographicPopulationObj.value) < parseInt(geographicCasesObj.value)) {
|
||||
insertErrorFor(geographicCasesObj, "Cases > Population");
|
||||
submit = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the short-range interactions list
|
||||
var short_range_interactions = [];
|
||||
$(".form_field_outer_row").each(function (index, element){
|
||||
|
|
@ -870,6 +919,12 @@ $(document).ready(function () {
|
|||
// Call the function now to handle forward/back button presses in the browser.
|
||||
on_hepa_option_change();
|
||||
|
||||
// When the exposure_option changes we want to make its respective
|
||||
// children show/hide.
|
||||
$("input[type=radio][name=exposure_option]").change(on_exposure_change);
|
||||
// Call the function now to handle forward/back button presses in the browser.
|
||||
on_exposure_change();
|
||||
|
||||
// When the mask_wearing_option changes we want to make its respective
|
||||
// children show/hide.
|
||||
$("input[type=radio][name=mask_wearing_option]").change(on_wearing_mask_change);
|
||||
|
|
|
|||
|
|
@ -847,6 +847,7 @@ baseline_model = models.ExposureModel(
|
|||
mask=models.Mask.types['No mask'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -276,6 +276,10 @@ footer img {
|
|||
display: inline!important;
|
||||
}
|
||||
|
||||
#event_data_tooltip:before{
|
||||
max-width: 350px!important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 40em) {
|
||||
|
|
|
|||
|
|
@ -303,7 +303,9 @@
|
|||
|
||||
<!-- Event Options -->
|
||||
<b>Event data:</b>
|
||||
<div data-tooltip="The total no. of occupants in the room and how many of them you assume are infected.">
|
||||
<div id="event_data_tooltip" data-tooltip="The total no. of occupants in the room.
|
||||
Deterministic exposure: add no. occupants that are infected.
|
||||
Probabilistic exposure: event at a given time & location (e.g. meeting or conference), considering the incidence rate in that area.">
|
||||
<span class="tooltip_text">?</span>
|
||||
</div><br>
|
||||
|
||||
|
|
@ -312,11 +314,44 @@
|
|||
<div class="col-sm-6 align-self-center"><input type="number" id="total_people" class="form-control" name="total_people" placeholder="Number" min=1 required></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-4" style="top: -2px"><label>Number of infected people: </label></div>
|
||||
<div class="col-sm-6"><input type="number" id="infected_people" class="form-control" name="infected_people" min=1 value=1 required></div>
|
||||
<div class="form-group mb-1">
|
||||
<input type="radio" id="p_deterministic_exposure" name="exposure_option" value="p_deterministic_exposure" checked="checked" data-enables="#DIVp_deterministic_exposure">
|
||||
<label for="p_deterministic_exposure">Deterministic exposure</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="radio" id="p_probabilistic_exposure" name="exposure_option" value="p_probabilistic_exposure" data-enables="#DIVp_probabilistic_exposure">
|
||||
<label for="p_probabilistic_exposure">Probabilistic exposure (incidence rate)</label>
|
||||
</div>
|
||||
|
||||
<div id="DIVp_deterministic_exposure" class="tabbed" style="display: none">
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-4"><label class="col-form-label">Number of infected people: </label></div>
|
||||
<div class="col-sm-6 pl-0 align-self-center"><input type="number" id="infected_people" class="form-control" name="infected_people" min=1 value=1></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="DIVp_probabilistic_exposure" class="tabbed" style="display: none">
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-4"><label class="col-form-label">Population:</label></div>
|
||||
<div class="col-sm-6 pl-0 align-self-center"><input type="number" step="any" id="geographic_population" class="non_zero form-control" name="geographic_population" placeholder="Inhabitants (#)" min="0"></div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-4"><label class="col-form-label">New confirmed cases (weekly):</label></div>
|
||||
<div class="col-sm-6 pl-0 align-self-center"><input type="number" step="any" id="geographic_cases" class="non_zero form-control" name="geographic_cases" placeholder="Cases (#7-day rolling avg)" min="0"></div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-4"><label class="col-form-label">Confidence level:</label></div>
|
||||
<div class="col-sm-6 pl-0 align-self-center">
|
||||
<select id="ascertainment_bias" name="ascertainment_bias" class="form-control">
|
||||
<option value="confidence_low">Low - surveillance only for sympotmatic patients</option>
|
||||
<option value="confidence_medium">Medium - recommended population wide surveillance</option>
|
||||
<option value="confidence_high">High - mandatory population wide surveillance</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="training_limit_error" class="red_text" hidden>Conference/Training activities limited to 1 infected<br></span>
|
||||
<hr width="80%">
|
||||
|
||||
<div class="form-group row">
|
||||
|
|
@ -332,7 +367,8 @@
|
|||
<option value="library">Library</option>
|
||||
<option value="lab">Laboratory</option>
|
||||
<option value="workshop">Workshop</option>
|
||||
<option value="training">Conference/Training</option>
|
||||
<option value="training">Conference/Training (speaker infected)</option>
|
||||
<option value="training_attendee">Conference/Training (attendee infected)</option>
|
||||
<option value="gym">Gym</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -615,8 +651,9 @@
|
|||
<li>Library = all seated, no talking, just breathing,</li>
|
||||
<li>Laboratory = light physical activity, talking 50% of the time,</li>
|
||||
<li>Workshop = moderate physical activity, talking 50% of the time,</li>
|
||||
<li>Conference/Training = speaker/trainer standing and talking, rest seated and talking quietly.
|
||||
<li>Conference/Training (speaker infected) = speaker/trainer standing and talking, rest seated and talking quietly.
|
||||
Speaker/Trainer assumed infected (worst case scenario),</li>
|
||||
<li>Conference/Training (attendee infected) = someone in the audience is infected, all are seated and breathing.</li>
|
||||
<li>Gym = heavy exercise, no talking, just breathing.</li>
|
||||
</ul>
|
||||
<b>Activity breaks:</b><br>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
{% if form.short_range_option == "short_range_yes" %}
|
||||
{% set scenario = alternative_scenarios.stats.values() | first %}
|
||||
{% set long_range_prob_inf = scenario.probability_of_infection %}
|
||||
{% set long_range_prob_probabilistic_exposure = scenario.prob_probabilistic_exposure if form.exposure_option == 'p_probabilistic_exposure' %}
|
||||
{% else %}
|
||||
{% set long_range_prob_inf = prob_inf %}
|
||||
{% endif %}
|
||||
|
|
@ -120,19 +121,38 @@
|
|||
{% endif %}
|
||||
<div class="d-flex">
|
||||
{% block report_summary %}
|
||||
<br>
|
||||
<div class="flex-row align-self-center">
|
||||
<div class="flex-row align-self-center">
|
||||
<div class="align-self-center alert alert-dark mb-0" role="alert">
|
||||
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases is {{ expected_new_cases | float_format }}</b>*.
|
||||
</div>
|
||||
{% if form.short_range_option == "short_range_yes" %}
|
||||
<br>
|
||||
<div class="align-self-center alert alert-dark mb-0" role="alert">
|
||||
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases is {{ expected_new_cases | float_format }}</b>*.
|
||||
In this scenario, assuming <b>short-range interactions</b> occur, the <b>probability of one exposed occupant getting infected can go as high as {{ prob_inf | non_zero_percentage }}</b>.
|
||||
</div>
|
||||
{% if form.short_range_option == "short_range_yes" %}
|
||||
{% endif %}
|
||||
{% block probabilistic_exposure_probability %}
|
||||
{% if form.exposure_option == "p_probabilistic_exposure" %}
|
||||
<br>
|
||||
<div class="align-self-center alert alert-dark mb-0" role="alert">
|
||||
In this scenario, assuming <b>short-range interactions</b> occur, the <b>probability of one exposed occupant getting infected can go as high as {{ prob_inf | non_zero_percentage }}</b>.
|
||||
The above {{ "result assumes" if form.short_range_option == "short_range_no" else "results assume" }} that <b>{{ form.infected_people }}
|
||||
{{ "occupant is infected" if form.infected_people == 1 else "occupants are infected" }}
|
||||
</b> in the room.
|
||||
By taking into account the estimate of cases currently circulating in <b>{{ form.location_name }}</b>,
|
||||
the probability of on-site transmission, having at least 1 new infection in an <b>event
|
||||
with {{ form.total_people }} occupants</b>, is
|
||||
{% if form.short_range_option == 'short_range_yes' %}:
|
||||
<ul>
|
||||
<li><b>{{ long_range_prob_probabilistic_exposure | non_zero_percentage }}</b>, assuming all occupants are exposed equally (i.e. without short-range interactions).</li>
|
||||
<li><b>{{ prob_probabilistic_exposure | non_zero_percentage }}</b>, assuming short-range interactions occur with the infector(s).</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<b>{{ prob_probabilistic_exposure | non_zero_percentage }}</b>.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock probabilistic_exposure_probability %}
|
||||
</div>
|
||||
{% endblock report_summary %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -419,6 +439,18 @@
|
|||
<li><p class="data_text">Number of attendees and infected people: {{ form.total_people }} in attendance, of whom {{ form.infected_people }}
|
||||
{{ "is" if form.infected_people == 1 else "are" }}
|
||||
infected.</p></li>
|
||||
{% if form.exposure_option == "p_probabilistic_exposure" %}
|
||||
{% if form.ascertainment_bias == "confidence_high" %}
|
||||
{% set conf_level = "High - Mandatory population surveillance." %}
|
||||
{% elif form.ascertainment_bias == "confidence_medium" %}
|
||||
{% set conf_level = "Medium - Recommended population wide surveillance." %}
|
||||
{% else %}
|
||||
{% set conf_level = "Low - Surveillance only for sympotmatic patients." %}
|
||||
{% endif %}
|
||||
<li><p class="data_text">Population in {{ form.location_name }}: {{ form.geographic_population }}</p></li>
|
||||
<li><p class="data_text">New reported cases in {{ form.location_name }} (7-day average): {{ form.geographic_cases }}</p></li>
|
||||
<li><p class="data_text">Confidence level: {{ conf_level }} </p></li>
|
||||
{% endif %}
|
||||
<li><p class="data_text">
|
||||
Activity type:
|
||||
{% if form.activity_type == "office" %}
|
||||
|
|
@ -438,7 +470,9 @@
|
|||
{% elif form.activity_type == "workshop" %}
|
||||
Workshop = assembly workshop environment, all persons doing moderate physical activity, speaking 50% of the time.
|
||||
{% elif form.activity_type == "training" %}
|
||||
Conference/Training – one person (the speaker/trainer) standing, talking, all others seated, talking quietly (whispering). It is assumed the speaker/trainer is the infected person, for the worst case scenario.
|
||||
Conference/Training (speaker infected) – one person (the speaker/trainer) standing, talking, all others seated, talking quietly (whispering). It is assumed the speaker/trainer is the infected person, for the worst case scenario.
|
||||
{% elif form.activity_type == "training_attendee" %}
|
||||
Conference/Training (attendee infected) – the infected person(s) are in the audience. All persons seated and breathing.
|
||||
{% elif form.activity_type == "lab" %}
|
||||
Laboratory = Lab or technical environment, all persons doing light physical activity, speaking 50% of the time.
|
||||
{% elif form.activity_type == "gym" %}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,23 @@ The recommended airflow rate for the HEPA filter should correspond to a total ai
|
|||
<p>Here we capture the information about the event being simulated.
|
||||
First enter the number of occupants in the space, if you have a (small) variation in the number of people, please input the average or consider using the expert tool.
|
||||
Within the number of people occupying the space, you should specify how many are infected.</p>
|
||||
<p>As an example, for a shared office with 4 people, where one person is infected, we enter 4 occupants and 1 infected person.</p>
|
||||
<p>In case one would like to simulate an event happening at a given time and location, where the epidemiological situation is known, the tool allows for an estimation of the probability of on-site transmission, considering the chances that a given person in the event is infected.
|
||||
The user will need to select <b>Probabilistic event</b>, input the number of inhabitants and the the weekly (7-day rolling average) value of new reported laboratory - confirmed cases at the event location, as well as the confidence level of these inputs.
|
||||
The 7-day rolling average consists in the average of the previous 3 days to subsequent 3 days, generally reported by the different public health authorities (e.g. in Switzerland <a href="https://www.covid19.admin.ch/en/epidemiologic/case/d/development?epiRelDev=abs">here</a>).
|
||||
These two inputs need to the related, i.e. the values of reported new cases and the number of inhabitants shall correspond to the a same geographical location. For example:</p>
|
||||
<ul>
|
||||
<li>Population of Geneva, CH: 508 000 inhabitants</li>
|
||||
<li>New lab reported cases in the canton of Geneva: 1000 (7-day rolling avg)</li>
|
||||
</ul>
|
||||
<p>The confidence level allows for an ascertainment bias to the data. The user can add the following options:</p>
|
||||
<ul>
|
||||
<li>High - mandatory population wide surveillance</li>
|
||||
<li>Medium - recommended population-wide surveillance</li>
|
||||
<li>Low - surveillance only for symptomatic patients</li>
|
||||
</ul>
|
||||
<p>Depending on the epidemiological situation in the choosen location, the public health surveillance can be more or less active. The confidence level will provide an ascertainment bias to the data collected by the user.</p>
|
||||
<p>The higher the incidence rate (i.e. new cases / population) the higher are the chances of having at least one infected occupant participating to the event.</p><br>
|
||||
<p>For general and recurrent layout simply select the <b>Deterministic exposure</b> option. As an example, for a shared office with 4 people, where one person is infected, we enter 4 occupants and 1 infected person.</p><br/>
|
||||
<br>
|
||||
<h4>Activity type</h4>
|
||||
<br>
|
||||
|
|
@ -128,9 +144,11 @@ Within the number of people occupying the space, you should specify how many are
|
|||
<li><strong>Control Room (night shift)</strong> = All persons seated, all talking 10% of the time. Everyone (exposed and infected occupants) is treated the same in this model.</li>
|
||||
<li><strong>Lab</strong> = Based on a typical lab or technical working area, all persons are doing light activity and talking 50% of the time. Everyone (exposed and infected occupants) is treated the same in this model.</li>
|
||||
<li><strong>Workshop</strong> = Based on a mechanical assembly workshop or equipment installation scenario, all persons are doing moderate activity and talking 50% of the time. This activity is equally applicable to bicycling, or walking on a gradient, in the LHC tunnels. Everyone (exposed and infected occupants) is treated the same in this model.</li>
|
||||
<li><strong>Conference/Training</strong> = Based on a typical conference/training course scenario.
|
||||
<li><strong>Conference/Training (speaker infected)</strong> = Based on a typical conference/training course scenario.
|
||||
One individual (the speaker/trainer) is standing and talking, with all other individuals seated and talking quietly (whispering).
|
||||
In this case it is assumed that the infected person is the speaker/trainer, because this is the worst case in terms of viral shedding.</li>
|
||||
<li><strong>Conference/Training (attendee infected)</strong> = All individuals seated and breathing.
|
||||
In this case it is assumed that the infected person is not the speaker/trainer.</li>
|
||||
<li><strong>Gym</strong> = All persons are doing heavy exercise and breathing (not talking). Everyone (exposed and infected occupants) is treated the same in this model.</li>
|
||||
</ul>
|
||||
<br><h3>Timings</h3>
|
||||
|
|
@ -181,7 +199,7 @@ If not, then you can input separate breaks. This is particularly different when
|
|||
<p>The model allows for a simulation with either a continuous wearing of face masks throughout the duration of the event, or have the removed at all times - i.e. all occupants (infected and exposed alike) wear or not masks for the duration of the simulation.
|
||||
Please bear in mind the user inputs shall be aligned with the current applicable public health & safety instructions.
|
||||
Please check what are the applicable rules, before deciding which assumptions are used for the simulation.</p>
|
||||
<p>If you have selected the Conference/Training activity type, this equates to the speakr/trainer and all participants either wearing masks throughout the conference/training (Yes), or removing them when seated/standing at their socially distanced positions within the conference/training room (No).
|
||||
<p>If you have selected the Conference/Training activity type, this equates to the speaker/trainer and all participants either wearing masks throughout the conference/training (Yes), or removing them when seated/standing at their socially distanced positions within the conference/training room (No).
|
||||
Please confirm what are the applicable rules, before deciding which assumptions are used for the simulation</p>
|
||||
<p>For the time being only the Type 1 surgical and FFP2 masks can be selected.</p>
|
||||
<br>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@
|
|||
In this scenario, assuming <b>short-range interactions</b> occur, the <b>probability of one exposed occupant getting infected can go as high as {{prob_inf | non_zero_percentage}}</b>.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block probabilistic_exposure_probability %}
|
||||
{{ super() }}
|
||||
{% endblock probabilistic_exposure_probability %}
|
||||
|
||||
{% if (prob_inf > 2) %}
|
||||
<br>
|
||||
{% if cern_level == "green-1" %}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import typing
|
|||
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
import scipy.stats as sct
|
||||
|
||||
if not typing.TYPE_CHECKING:
|
||||
from memoization import cached
|
||||
|
|
@ -438,6 +439,9 @@ class Virus:
|
|||
#: Pre-populated examples of Viruses.
|
||||
types: typing.ClassVar[typing.Dict[str, "Virus"]]
|
||||
|
||||
#: Number of days the infector is contagious
|
||||
infectiousness_days: int
|
||||
|
||||
def halflife(self, humidity: _VectorisedFloat, inside_temp: _VectorisedFloat) -> _VectorisedFloat:
|
||||
# Biological decay (inactivation of the virus in air) - virus
|
||||
# dependent and function of humidity
|
||||
|
|
@ -450,7 +454,9 @@ class Virus:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class SARSCoV2(Virus):
|
||||
|
||||
#: Number of days the infector is contagious
|
||||
infectiousness_days: int = 14
|
||||
|
||||
def halflife(self, humidity: _VectorisedFloat, inside_temp: _VectorisedFloat) -> _VectorisedFloat:
|
||||
"""
|
||||
Half-life changes with humidity level. Here is implemented a simple
|
||||
|
|
@ -911,6 +917,33 @@ class InfectedPopulation(_PopulationWithVirus):
|
|||
return self.expiration.particle
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Cases:
|
||||
"""
|
||||
The geographical data to calculate the probability of having at least 1
|
||||
new infection in a probabilistic exposure.
|
||||
"""
|
||||
#: Geographic location population
|
||||
geographic_population: int = 0
|
||||
|
||||
#: Geographic location new cases
|
||||
geographic_cases: int = 0
|
||||
|
||||
#: Number of new cases confidence level
|
||||
ascertainment_bias: int = 0
|
||||
|
||||
def probability_random_individual(self, virus: Virus) -> _VectorisedFloat:
|
||||
"""Probability that a randomly selected individual in a focal population is infected."""
|
||||
return self.geographic_cases*virus.infectiousness_days*self.ascertainment_bias/self.geographic_population
|
||||
|
||||
def probability_meet_infected_person(self, virus: Virus, n_infected: int, event_population: int) -> _VectorisedFloat:
|
||||
"""
|
||||
Probability to meet n_infected persons in an event.
|
||||
From https://doi.org/10.1038/s41562-020-01000-9.
|
||||
"""
|
||||
return sct.binom.pmf(n_infected, event_population, self.probability_random_individual(virus))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConcentrationModel:
|
||||
room: Room
|
||||
|
|
@ -1280,6 +1313,9 @@ class ExposureModel:
|
|||
#: The population of non-infected people to be used in the model.
|
||||
exposed: Population
|
||||
|
||||
#: Geographical data
|
||||
geographical_data: Cases
|
||||
|
||||
#: The number of times the exposure event is repeated (default 1).
|
||||
repeats: int = 1
|
||||
|
||||
|
|
@ -1435,6 +1471,29 @@ class ExposureModel:
|
|||
return (1 - np.exp(-((vD * (1 - self.exposed.host_immunity))/(infectious_dose *
|
||||
self.concentration_model.virus.transmissibility_factor)))) * 100
|
||||
|
||||
def total_probability_rule(self) -> _VectorisedFloat:
|
||||
if (self.geographical_data.geographic_population != 0 and self.geographical_data.geographic_cases != 0):
|
||||
sum_probability = 0.0
|
||||
# Create an equivalent exposure model but changing the number of infected cases.
|
||||
total_people = self.concentration_model.infected.number + self.exposed.number
|
||||
max_num_infected = (total_people if total_people < 10 else 10)
|
||||
# The influence of a higher number of simultainious infected people (> 4 - 5) yields an almost negligible contirbution to the total probability.
|
||||
# To be on the safe side, a hard coded limit with a safety margin of 2x was set.
|
||||
# Therefore we decided a hard limit of 10 infected people.
|
||||
for num_infected in range(1, max_num_infected + 1):
|
||||
exposure_model = nested_replace(
|
||||
self, {'concentration_model.infected.number': num_infected}
|
||||
)
|
||||
prob_ind = exposure_model.infection_probability().mean() / 100
|
||||
n = total_people - num_infected
|
||||
# By means of the total probability rule
|
||||
prob_at_least_one_infected = 1 - (1 - prob_ind)**n
|
||||
sum_probability += (prob_at_least_one_infected *
|
||||
self.geographical_data.probability_meet_infected_person(self.concentration_model.infected.virus, num_infected, total_people))
|
||||
return sum_probability * 100
|
||||
else:
|
||||
return 0
|
||||
|
||||
def expected_new_cases(self) -> _VectorisedFloat:
|
||||
# Create an equivalent exposure model without short-range interactions, if any.
|
||||
if (len(self.short_range) == 0):
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ def baseline_exposure_model(baseline_concentration_model, baseline_sr_model):
|
|||
mask=baseline_concentration_model.infected.mask,
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,8 @@ def known_concentrations(func):
|
|||
])
|
||||
def test_exposure_model_ndarray(population, cm,
|
||||
expected_exposure, expected_probability, sr_model):
|
||||
model = ExposureModel(cm, sr_model, population)
|
||||
geographical_data = models.Cases()
|
||||
model = ExposureModel(cm, sr_model, population, geographical_data)
|
||||
np.testing.assert_almost_equal(
|
||||
model.deposited_exposure(), expected_exposure
|
||||
)
|
||||
|
|
@ -113,7 +114,8 @@ def test_exposure_model_ndarray(population, cm,
|
|||
def test_exposure_model_ndarray_and_float_mix(population, expected_deposited_exposure, sr_model):
|
||||
cm = known_concentrations(
|
||||
lambda t: 0. if np.floor(t) % 2 else np.array([1.2, 1.2]))
|
||||
model = ExposureModel(cm, sr_model, population)
|
||||
geographical_data = models.Cases()
|
||||
model = ExposureModel(cm, sr_model, population, geographical_data)
|
||||
|
||||
np.testing.assert_almost_equal(
|
||||
model.deposited_exposure(), expected_deposited_exposure
|
||||
|
|
@ -130,7 +132,8 @@ def test_exposure_model_ndarray_and_float_mix(population, expected_deposited_exp
|
|||
])
|
||||
def test_exposure_model_vector(population, expected_deposited_exposure, sr_model):
|
||||
cm_array = known_concentrations(lambda t: np.array([1.2, 1.2]))
|
||||
model_array = ExposureModel(cm_array, sr_model, population)
|
||||
geographical_data = models.Cases()
|
||||
model_array = ExposureModel(cm_array, sr_model, population, geographical_data)
|
||||
np.testing.assert_almost_equal(
|
||||
model_array.deposited_exposure(), np.array(expected_deposited_exposure)
|
||||
)
|
||||
|
|
@ -138,7 +141,8 @@ def test_exposure_model_vector(population, expected_deposited_exposure, sr_model
|
|||
|
||||
def test_exposure_model_scalar(sr_model):
|
||||
cm_scalar = known_concentrations(lambda t: 1.2)
|
||||
model_scalar = ExposureModel(cm_scalar, sr_model, populations[0])
|
||||
geographical_data = models.Cases()
|
||||
model_scalar = ExposureModel(cm_scalar, sr_model, populations[0], geographical_data)
|
||||
expected_deposited_exposure = 1.52436206
|
||||
np.testing.assert_almost_equal(
|
||||
model_scalar.deposited_exposure(), expected_deposited_exposure
|
||||
|
|
@ -194,7 +198,8 @@ def test_exposure_model_integral_accuracy(exposed_time_interval,
|
|||
10, presence_interval, models.Mask.types['Type I'],
|
||||
models.Activity.types['Standing'], 0.,
|
||||
)
|
||||
model = ExposureModel(conc_model, sr_model, population)
|
||||
geographical_data = models.Cases()
|
||||
model = ExposureModel(conc_model, sr_model, population, geographical_data)
|
||||
np.testing.assert_allclose(model.deposited_exposure(), expected_deposited_exposure)
|
||||
|
||||
|
||||
|
|
@ -221,7 +226,71 @@ def test_infectious_dose_vectorisation(sr_model):
|
|||
10, presence_interval, models.Mask.types['Type I'],
|
||||
models.Activity.types['Standing'], 0.,
|
||||
)
|
||||
model = ExposureModel(cm, sr_model, population)
|
||||
geographical_data = models.Cases()
|
||||
model = ExposureModel(cm, sr_model, population, geographical_data)
|
||||
inf_probability = model.infection_probability()
|
||||
assert isinstance(inf_probability, np.ndarray)
|
||||
assert inf_probability.shape == (3, )
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pop, cases, infectiousness_days, AB, prob_random_individual", [
|
||||
[100_000, 68, 7, 5, 0.023800],
|
||||
[200_000, 121, np.array([7, 14]), 5, np.array([0.021175, 0.042350])],
|
||||
[np.array([100_000, 200_000]), 68, 14, 10, np.array([0.0952, 0.0476])],
|
||||
[150_000, np.array([68, 121]), 14, 2, np.array([0.012693, 0.022587])],
|
||||
[np.array([100_000, 200_000]), np.array([68, 121]), 7, 5, np.array([0.023450, 0.021175])]
|
||||
]
|
||||
)
|
||||
def test_probability_random_individual(pop, cases, infectiousness_days, AB, prob_random_individual):
|
||||
cases = models.Cases(geographic_population=pop, geographic_cases=cases,
|
||||
ascertainment_bias=AB)
|
||||
virus=models.SARSCoV2(
|
||||
viral_load_in_sputum=1e9,
|
||||
infectious_dose=50.,
|
||||
viable_to_RNA_ratio = 0.5,
|
||||
transmissibility_factor=1,
|
||||
infectiousness_days=infectiousness_days,
|
||||
)
|
||||
np.testing.assert_allclose(
|
||||
cases.probability_random_individual(virus), prob_random_individual, rtol=0.05
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pop, cases, AB, exposed, infected, prob_meet_infected_person", [
|
||||
[100000, 68, 5, 10, 1, 0.321509274],
|
||||
[100000, 121, 5, 20, 1, 0.302950694],
|
||||
[100000, np.array([68, 121]), 5, np.array([10, 20]), 1, np.array([0.321509274, 0.302950694])],
|
||||
]
|
||||
)
|
||||
def test_prob_meet_infected_person(pop, cases, AB, exposed, infected, prob_meet_infected_person):
|
||||
cases = models.Cases(geographic_population=pop, geographic_cases=cases,
|
||||
ascertainment_bias=AB)
|
||||
virus = models.Virus.types['SARS_CoV_2']
|
||||
np.testing.assert_allclose(cases.probability_meet_infected_person(virus, infected, exposed+infected),
|
||||
prob_meet_infected_person, rtol=0.05)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exposed_population, cm, pop, cases, AB, probabilistic_exposure_probability",[
|
||||
[10, known_concentrations(lambda t: 36.),
|
||||
100000, 68, 5, 41.50971131],
|
||||
[10, known_concentrations(lambda t: 0.2),
|
||||
100000, 68, 5, 2.185785075],
|
||||
[20, known_concentrations(lambda t: 72.),
|
||||
100000, 68, 5, 64.09068488],
|
||||
[30, known_concentrations(lambda t: 1.2),
|
||||
100000, 68, 5, 55.93154502],
|
||||
])
|
||||
def test_probabilistic_exposure_probability(exposed_population, cm,
|
||||
pop, AB, cases, probabilistic_exposure_probability):
|
||||
|
||||
population = models.Population(
|
||||
exposed_population, models.PeriodicInterval(120, 60), models.Mask.types['Type I'],
|
||||
models.Activity.types['Standing'], host_immunity=0.,)
|
||||
model = ExposureModel(cm, (), population, models.Cases(geographic_population=pop,
|
||||
geographic_cases=cases, ascertainment_bias=AB),)
|
||||
np.testing.assert_allclose(
|
||||
model.total_probability_rule(), probabilistic_exposure_probability, rtol=0.05
|
||||
)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ def test_short_range_exposure_with_ndarray_mask():
|
|||
activity=models.Activity.types['Light activity'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data = mc_models.Cases(),
|
||||
).build_model(SAMPLE_SIZE)
|
||||
assert isinstance(e_model.deposited_exposure(), np.ndarray)
|
||||
assert len(e_model.deposited_exposure()) == 3
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ def expo_sr_model(c_model,sr_models) -> mc.ExposureModel:
|
|||
activity=models.Activity.types['Seated'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -605,6 +606,7 @@ def expo_sr_model_distr(c_model_distr) -> mc.ExposureModel:
|
|||
activity=models.Activity.types['Seated'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -696,6 +698,7 @@ def test_longrange_exposure(c_model):
|
|||
activity=models.Activity.types['Seated'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
).build_model(SAMPLE_SIZE)
|
||||
npt.assert_allclose(
|
||||
expo_model.deposited_exposure().mean(),
|
||||
|
|
@ -756,6 +759,7 @@ def test_longrange_exposure_with_distributions(c_model_distr):
|
|||
activity=activity_distributions['Seated'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
).build_model(SAMPLE_SIZE)
|
||||
npt.assert_allclose(
|
||||
expo_model.deposited_exposure().mean(),
|
||||
|
|
@ -781,6 +785,7 @@ def test_concentration_with_shortrange(expo_sr_model,simple_expo_sr_model,time):
|
|||
)
|
||||
|
||||
|
||||
@retry(tries=10)
|
||||
def test_exposure_with_shortrange(expo_sr_model,simple_expo_sr_model):
|
||||
npt.assert_allclose(
|
||||
expo_sr_model.build_model(SAMPLE_SIZE).deposited_exposure().mean(),
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def test_infected_population_vectorisation(override_params):
|
|||
0.51,
|
||||
defaults['exhalation_rate'],
|
||||
),
|
||||
virus=caimira.models.Virus(
|
||||
virus=caimira.models.SARSCoV2(
|
||||
viral_load_in_sputum=defaults['viral_load_in_sputum'],
|
||||
infectious_dose=50.,
|
||||
viable_to_RNA_ratio = 0.5,
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ def build_exposure_model(concentration_model, short_range_model):
|
|||
mask=infected.mask,
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ def baseline_mc_exposure_model(baseline_mc_concentration_model, baseline_mc_sr_m
|
|||
activity=baseline_mc_concentration_model.infected.activity,
|
||||
mask=baseline_mc_concentration_model.infected.mask,
|
||||
host_immunity=0.,
|
||||
)
|
||||
),
|
||||
geographical_data=caimira.models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ def shared_office_mc():
|
|||
activity=activity_distributions['Seated'],
|
||||
mask=models.Mask.types['No mask'],
|
||||
host_immunity=0.,
|
||||
)
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -117,6 +118,7 @@ def classroom_mc():
|
|||
mask=models.Mask.types["No mask"],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -151,6 +153,7 @@ def ski_cabin_mc():
|
|||
mask=models.Mask.types['No mask'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -190,7 +193,8 @@ def skagit_chorale_mc():
|
|||
activity=activity_distributions['Moderate activity'],
|
||||
mask=models.Mask.types['No mask'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -230,7 +234,8 @@ def bus_ride_mc():
|
|||
activity=activity_distributions['Seated'],
|
||||
mask=models.Mask.types['No mask'],
|
||||
host_immunity=0.,
|
||||
),
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -266,6 +271,7 @@ def gym_mc():
|
|||
mask=concentration_mc.infected.mask,
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -301,6 +307,7 @@ def waiting_room_mc():
|
|||
mask=concentration_mc.infected.mask,
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -380,6 +387,7 @@ def test_small_shared_office_Geneva(mask_type, month, expected_pi,
|
|||
mask=concentration_mc.infected.mask,
|
||||
host_immunity=0.,
|
||||
),
|
||||
geographical_data=models.Cases(),
|
||||
)
|
||||
exposure_model = exposure_mc.build_model(size=SAMPLE_SIZE)
|
||||
npt.assert_allclose(exposure_model.infection_probability().mean(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue