Final implementation for total_probability_rule with geographic population and cases

This commit is contained in:
Luis Aleixo 2022-01-10 15:06:52 +01:00
parent a3a44e5e89
commit e487953a61
8 changed files with 131 additions and 19 deletions

View file

@ -55,6 +55,9 @@ class FormData:
location_name: str
location_latitude: float
location_longitude: float
geographic_population: int
geographic_cases: int
p_recurrent_option: str
mask_type: str
mask_wearing_option: str
mechanical_ventilation_type: str
@ -107,6 +110,9 @@ class FormData:
'infected_start': '08:30',
'location_latitude': _NO_DEFAULT,
'location_longitude': _NO_DEFAULT,
'geographic_population': 0,
'geographic_cases': 0,
'p_recurrent_option': 'p_recurrent_event',
'location_name': _NO_DEFAULT,
'mask_type': 'Type I',
'mask_wearing_option': 'mask_off',
@ -249,6 +255,8 @@ class FormData:
evaporation_factor=0.3,
),
exposed=self.exposed_population(),
geographic_population=self.geographic_population,
geographic_cases=self.geographic_cases
)
def build_model(self, sample_size=_DEFAULT_MC_SAMPLE_SIZE) -> models.ExposureModel:

View file

@ -104,7 +104,8 @@ def calculate_report_data(model: models.ExposureModel):
for time in times
]
highest_const = max(concentrations)
prob = np.array(model.total_probability_rule()).mean()
prob = np.array(model.infection_probability()).mean()
prob_specific_event = 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()
@ -120,6 +121,7 @@ def calculate_report_data(model: models.ExposureModel):
"concentrations": concentrations,
"highest_const": highest_const,
"prob_inf": prob,
"prob_specific_event": prob_specific_event,
"emission_rate": er,
"exposed_occupants": exposed_occupants,
"expected_new_cases": expected_new_cases,

View file

@ -61,6 +61,12 @@ function require_fields(obj) {
case "hepa_no":
require_hepa(false);
break;
case "p_not_recurrent_event":
require_population(true);
break;
case "p_recurrent_event":
require_population(false);
break;
case "mask_on":
require_mask(true);
break;
@ -176,6 +182,11 @@ function require_lunch(id, option) {
}
}
function require_population(option) {
require_input_field("#geographic_population", option);
require_input_field("#geographic_cases", option);
}
function require_mask(option) {
$("#mask_type_1").prop('required', option);
$("#mask_type_ffp2").prop('required', option);
@ -236,6 +247,23 @@ function on_ventilation_type_change() {
});
}
function on_p_recurrent_change() {
p_recurrent = $('input[type=radio][name=p_recurrent_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) {
@ -572,6 +600,12 @@ $(document).ready(function () {
// Call the function now to handle forward/back button presses in the browser.
on_ventilation_type_change();
// When the p_recurrent_option changes we want to make its respective
// children show/hide.
$("input[type=radio][name=p_recurrent_option]").change(on_p_recurrent_change);
// Call the function now to handle forward/back button presses in the browser.
on_p_recurrent_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);

View file

@ -309,6 +309,25 @@
<div class="col-sm-6 align-self-center"><input type="number" id="infected_people" class="form-control" name="infected_people" min=1 value=1 required></div>
</div>
<input type="radio" id="p_recurrent_event" name="p_recurrent_option" value="p_recurrent_event" checked="checked">
<label for="p_recurrent_event">Recurrent exposure</label>
<input class="ml-2" type="radio" id="p_not_recurrent_event" name="p_recurrent_option" value="p_not_recurrent_event" data-enables="#DIVp_not_recurrent_event">
<label for="p_not_recurrent_event">Specific event</label>
<div data-tooltip="Specific event occurring at a given time (e.g. meeting or conference). Indicate the 7-day average of new reported cases and the population of a given location.">
<span class="tooltip_text">?</span>
</div>
<div id="DIVp_not_recurrent_event" 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 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:</label></div>
<div class="col-sm-6 align-self-center"><input type="number" step="any" id="geographic_cases" class="non_zero form-control" name="geographic_cases" placeholder="Cases (# 7-day average)" min="0"></div>
</div>
</div>
<span id="training_limit_error" class="red_text" hidden>Conference/Training activities limited to 1 infected<br></span>
<hr width="80%">

View file

@ -77,9 +77,27 @@
</div>
<div class="col-md-8 pr-0 pl-0 d-flex">
{% block report_summary %}
<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, the <b>probability of one exposed occupant getting infected is {{ prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases is {{ expected_new_cases | float_format }}</b>*.
</div>
{% block specific_event_probability %}
{% if form.p_recurrent_option == "p_not_recurrent_event" %}
<br>
<div class="align-self-center alert alert-dark mb-0" role="alert">
The above result assumes that <b>{{ form.infected_people }}
{% if form.infected_people == 1 %}
occupant is infected
{% else %}
occupants are infected
{% endif %}</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, is {{ prob_specific_event | non_zero_percentage }}</b>.
</div>
{% endif %}
{% endblock %}
</div>
{% endblock report_summary %}
</div>
</div>
@ -285,6 +303,10 @@
<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.p_recurrent_option == "p_not_recurrent_event" %}
<li><p class="data_text">Geographic population: {{ form.geographic_population }}</p></li>
<li><p class="data_text">Geographic cumulative reported cases: {{ form.geographic_cases }}</p></li>
{% endif %}
<li><p class="data_text">
Activity type:
{% if form.activity_type == "office" %}

View file

@ -46,6 +46,10 @@
</div>
{% endif %}
{% block specific_event_probability %}
{{ super() }}
{% endblock specific_event_probability %}
{% if (prob_inf > 2) %}
<br>
{% if scale_warning.level == "green-1" %}

View file

@ -1075,6 +1075,12 @@ class ExposureModel:
#: The population of non-infected people to be used in the model.
exposed: Population
#: Geographic location population
geographic_population: int = 0
#: Geographic location cases
geographic_cases: int = 0
#: The number of times the exposure event is repeated (default 1).
repeats: int = 1
@ -1154,10 +1160,9 @@ class ExposureModel:
return cases*AB/population
def probability_meet_infected_person(self, population, cases, event, i) -> _VectorisedFloat:
"""Probability to meet x infected person in an event with 100 people."""
AB = 2
return sct.binom.pmf(i, event, self.probability_random_individual(cases, population, AB), loc=0)
"""Probability to meet x infected person in an event."""
AB = 5
return sct.binom.pmf(i, event, self.probability_random_individual(cases, population, AB))
def infection_probability(self) -> _VectorisedFloat:
exposure = self.exposure()
@ -1185,20 +1190,21 @@ class ExposureModel:
self.concentration_model.virus.transmissibility_factor)))) * 100
def total_probability_rule(self) -> _VectorisedFloat:
population = 267197
cases = 68
sum_probability = 0.0
# Create an equivalent exposure model but with two infected cases
for i in range(1, 10):
single_exposure_model = nested_replace(
self, {'concentration_model.infected.number': i}
)
prob_exposed_occupant = single_exposure_model.infection_probability()
# By means of a Binomial Distribution
sum_probability += ((1 - (1 - prob_exposed_occupant)**(self.exposed.number-1))*self.probability_meet_infected_person(population, cases, self.exposed.number, i))
return sum_probability * 100
if (self.geographic_population != 0 and self.geographic_cases != 0):
sum_probability = 0.0
# Create an equivalent exposure model but with i infected cases
total_people = self.concentration_model.infected.number + self.exposed.number
I = (total_people if total_people < 10 else 10)
for i in range(1, I):
exposure_model = nested_replace(
self, {'concentration_model.infected.number': i}
)
prob_exposed_occupant = exposure_model.infection_probability().mean() / 100
# By means of a Binomial Distribution
sum_probability += (prob_exposed_occupant)*self.probability_meet_infected_person(self.geographic_population, self.geographic_cases, self.exposed.number, i)
return sum_probability * 100
else:
return 0
def expected_new_cases(self) -> _VectorisedFloat:
prob = self.infection_probability()

View file

@ -220,3 +220,20 @@ def test_infectious_dose_vectorisation():
inf_probability = model.infection_probability()
assert isinstance(inf_probability, np.ndarray)
assert inf_probability.shape == (3, )
@pytest.mark.parametrize(
"population, cm, pop, cases, specific_event_probability",[
[populations[1], known_concentrations(lambda t: 36.),
100000, 68, 3.24287],
[populations[0], known_concentrations(lambda t: 36.),
100000, 68, 3.067046],
])
def test_specific_event_probability(population, cm,
pop, cases, specific_event_probability):
model = ExposureModel(cm, population, geographic_population=pop,
geographic_cases=cases)
np.testing.assert_allclose(
model.total_probability_rule(), specific_event_probability, rtol=0.05
)