From 8936b0db48aa753d91ba80be889e9d523e684294 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Wed, 21 Sep 2022 09:14:33 +0200 Subject: [PATCH] added UI input fields and updated report for the new P(I) --- caimira/apps/calculator/report_generator.py | 16 ++++++- caimira/apps/calculator/static/js/form.js | 47 +++++++++++++++++++ .../templates/base/calculator.form.html.j2 | 30 ++++++++++++ .../templates/base/calculator.report.html.j2 | 47 ++++++++++++++++--- caimira/apps/templates/base/userguide.html.j2 | 17 ++++++- .../templates/cern/calculator.report.html.j2 | 5 ++ 6 files changed, 154 insertions(+), 8 deletions(-) diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index 1a7cdf02..7317f2b0 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -131,6 +131,7 @@ def calculate_report_data(form: FormData, model: models.ExposureModel) -> typing ]) 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() @@ -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_specific_event": prob_specific_event, "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], specific_event: bool): model = mc_model.build_model(size=_DEFAULT_MC_SAMPLE_SIZE) + if (specific_event): + # It means we have data to calculate the total_probability_rule + prob_specific_event = np.array(model.total_probability_rule()).mean() + else: + prob_specific_event = 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_specific_event': prob_specific_event, } @@ -303,11 +311,17 @@ def comparison_report( else: statistics = {} + if (form.short_range_option == "short_range_yes" and form.p_recurrent_option == "p_specific_event"): + specific_event = True + else: + specific_event = False + with executor_factory() as executor: results = executor.map( scenario_statistics, scenarios.values(), [sample_times] * len(scenarios), + [specific_event] * len(scenarios), timeout=60, ) diff --git a/caimira/apps/calculator/static/js/form.js b/caimira/apps/calculator/static/js/form.js index 45656c78..5e551b7d 100644 --- a/caimira/apps/calculator/static/js/form.js +++ b/caimira/apps/calculator/static/js/form.js @@ -61,6 +61,12 @@ function require_fields(obj) { case "hepa_no": require_hepa(false); break; + case "p_specific_event": + require_population(true); + break; + case "p_recurrent_event": + require_population(false); + break; case "mask_on": require_mask(true); break; @@ -172,6 +178,12 @@ 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_mask(option) { $("#mask_type_1").prop('required', option); $("#mask_type_ffp2").prop('required', option); @@ -269,6 +281,23 @@ function on_hepa_option_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) { @@ -538,6 +567,18 @@ function validate_form(form) { } } + // Validate cases < population + if ($("#p_specific_event").prop('checked')) { + 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 +911,12 @@ $(document).ready(function () { // Call the function now to handle forward/back button presses in the browser. on_hepa_option_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); diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2 index 37d8d932..5278a040 100644 --- a/caimira/apps/templates/base/calculator.form.html.j2 +++ b/caimira/apps/templates/base/calculator.form.html.j2 @@ -317,6 +317,36 @@
+ + + + +
+ ? +
+ + + +
diff --git a/caimira/apps/templates/base/calculator.report.html.j2 b/caimira/apps/templates/base/calculator.report.html.j2 index 34d12776..1b247015 100644 --- a/caimira/apps/templates/base/calculator.report.html.j2 +++ b/caimira/apps/templates/base/calculator.report.html.j2 @@ -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_specific_event = scenario.prob_specific_event if form.p_recurrent_option == 'p_specific_event' %} {% else %} {% set long_range_prob_inf = prob_inf %} {% endif %} @@ -120,19 +121,41 @@ {% endif %}
{% block report_summary %} -
-
+
+ + {% if form.short_range_option == "short_range_yes" %}
- {% if form.short_range_option == "short_range_yes" %} + {% endif %} + {% block specific_event_probability %} + {% if form.p_recurrent_option == "p_specific_event" %}
{% endif %} -
+ {% endblock %} +
{% endblock report_summary %}
@@ -419,6 +442,18 @@
  • 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.

  • + {% if form.p_recurrent_option == "p_specific_event" %} + {% 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 %} +
  • Population in {{ form.location_name }}: {{ form.geographic_population }}

  • +
  • New reported cases in {{ form.location_name }} (7-day average): {{ form.geographic_cases }}

  • +
  • Confidence level: {{ conf_level }}

  • + {% endif %}
  • Activity type: {% if form.activity_type == "office" %} diff --git a/caimira/apps/templates/base/userguide.html.j2 b/caimira/apps/templates/base/userguide.html.j2 index 7775a7ec..976ef328 100644 --- a/caimira/apps/templates/base/userguide.html.j2 +++ b/caimira/apps/templates/base/userguide.html.j2 @@ -113,7 +113,22 @@ The recommended airflow rate for the HEPA filter should correspond to a total ai

    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.

    -

    As an example, for a shared office with 4 people, where one person is infected, we enter 4 occupants and 1 infected person.

    +

    As an example, for a shared office with 4 people, where one person is infected, we enter 4 occupants and 1 infected person.


    +

    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 Specific event, input the number of inhabitants and the cumulative weekly (7-day average) value of new reported positive cases at the event location, as well as the confidence level of the inputs. The first 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:

    + +

    The confidence level has the following options:

    + +

    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.

    +

    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. +For general and recurrent layout simply select the Recurrent exposure option.


    Activity type


    diff --git a/caimira/apps/templates/cern/calculator.report.html.j2 b/caimira/apps/templates/cern/calculator.report.html.j2 index f98c5a23..48ec1188 100644 --- a/caimira/apps/templates/cern/calculator.report.html.j2 +++ b/caimira/apps/templates/cern/calculator.report.html.j2 @@ -87,6 +87,11 @@ In this scenario, assuming short-range interactions occur, the probability of one exposed occupant getting infected can go as high as {{prob_inf | non_zero_percentage}}. {% endif %} + + {% block specific_event_probability %} + {{ super() }} + {% endblock specific_event_probability %} + {% if (prob_inf > 2) %}
    {% if cern_level == "green-1" %}