Final implementation for total_probability_rule with geographic population and cases
This commit is contained in:
parent
a3a44e5e89
commit
e487953a61
8 changed files with 131 additions and 19 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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%">
|
||||
|
||||
|
|
|
|||
|
|
@ -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" %}
|
||||
|
|
|
|||
|
|
@ -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" %}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
Loading…
Reference in a new issue