diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..ca21a586 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.xlsx filter=lfs diff=lfs merge=lfs -text diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py index 513187e1..91f2c5d1 100644 --- a/caimira/apps/calculator/__init__.py +++ b/caimira/apps/calculator/__init__.py @@ -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.4" +__version__ = "4.5" class BaseRequestHandler(RequestHandler): diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 4896e485..51d716da 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -72,6 +72,10 @@ class FormData: room_volume: float simulation_name: str total_people: int + vaccine_option: bool + vaccine_booster_option: bool + vaccine_type: str + vaccine_booster_type: str ventilation_type: str virus_type: str volume_type: str @@ -133,6 +137,10 @@ class FormData: 'room_volume': 0., 'simulation_name': _NO_DEFAULT, 'total_people': _NO_DEFAULT, + 'vaccine_option': False, + 'vaccine_booster_option': False, + 'vaccine_type': 'AZD1222_(AstraZeneca)', + 'vaccine_booster_type': 'AZD1222_(AstraZeneca)', 'ventilation_type': 'no_ventilation', 'virus_type': 'SARS_CoV_2', 'volume_type': _NO_DEFAULT, @@ -270,8 +278,9 @@ class FormData: ('window_opening_regime', WINDOWS_OPENING_REGIMES), ('window_type', WINDOWS_TYPES), ('event_month', MONTH_NAMES), - ('ascertainment_bias', CONFIDENCE_LEVEL_OPTIONS),] - + ('ascertainment_bias', CONFIDENCE_LEVEL_OPTIONS), + ('vaccine_type', VACCINE_TYPE), + ('vaccine_booster_type', VACCINE_BOOSTER_TYPE),] 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}") @@ -517,7 +526,7 @@ class FormData: mask=self.mask(), activity=activity, expiration=expiration, - host_immunity=0., + host_immunity=0., # Vaccination status does not affect the infected population (for now) ) return infected @@ -545,12 +554,22 @@ class FormData: # minus the number of infected occupants. exposed_occupants = self.total_people - infected_occupants + if (self.vaccine_option): + if (self.vaccine_booster_option and self.vaccine_booster_type != 'Other'): + host_immunity = [vaccine['VE'] for vaccine in data.vaccine_booster_host_immunity if + vaccine['primary series vaccine'] == self.vaccine_type and + vaccine['booster vaccine'] == self.vaccine_booster_type][0] + else: + host_immunity = data.vaccine_primary_host_immunity[self.vaccine_type] + else: + host_immunity = 0. + exposed = mc.Population( number=exposed_occupants, presence=self.exposed_present_interval(), activity=activity, mask=self.mask(), - host_immunity=0., + host_immunity=host_immunity, ) return exposed @@ -790,6 +809,10 @@ def baseline_raw_form_data() -> typing.Dict[str, typing.Union[str, float]]: 'room_volume': '75', 'simulation_name': 'Test', 'total_people': '10', + 'vaccine_option': '0', + 'vaccine_booster_option': '0', + 'vaccine_type': 'Ad26.COV2.S_(Janssen)', + 'vaccine_booster_type': 'AZD1222_(AstraZeneca)', 'ventilation_type': 'natural_ventilation', 'virus_type': 'SARS_CoV_2', 'volume_type': 'room_volume_explicit', @@ -820,7 +843,12 @@ MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ] - +VACCINE_TYPE = ['Ad26.COV2.S_(Janssen)', 'Any_mRNA_-_heterologous', 'AZD1222_(AstraZeneca)', 'AZD1222_(AstraZeneca)_and_any_mRNA_-_heterologous', 'AZD1222_(AstraZeneca)_and_BNT162b2_(Pfizer)', + 'BBIBP-CorV_(Beijing_CNBG)', 'BNT162b2_(Pfizer)', 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', 'CoronaVac_(Sinovac)', 'CoronaVac_(Sinovac)_and_AZD1222_(AstraZeneca)', 'Covishield', + 'mRNA-1273_(Moderna)', 'Sputnik_V_(Gamaleya)', 'CoronaVac_(Sinovac)_and_BNT162b2_(Pfizer)'] +VACCINE_BOOSTER_TYPE = ['AZD1222_(AstraZeneca)', 'Ad26.COV2.S_(Janssen)', 'BNT162b2_(Pfizer)', 'BNT162b2_(Pfizer)_(4th_dose)', 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', + 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)', 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)_(4th_dose)', 'CoronaVac_(Sinovac)', 'Coronavac_(Sinovac)', 'Sinopharm', + 'mRNA-1273_(Moderna)', 'mRNA-1273_(Moderna)_(4th_dose)', 'Other'] def _hours2timestring(hours: float): # Convert times like 14.5 to strings, like "14:30" diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index b2d5ff25..456a64be 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -214,6 +214,10 @@ def readable_minutes(minutes: int) -> str: return time_str + unit +def percentage(absolute: float) -> float: + return absolute * 100 + + def non_zero_percentage(percentage: int) -> str: if percentage < 0.01: return "<0.01%" @@ -389,6 +393,7 @@ class ReportGenerator: env.filters['minutes_to_time'] = minutes_to_time env.filters['float_format'] = "{0:.2f}".format env.filters['int_format'] = "{:0.0f}".format + env.filters['percentage'] = percentage env.filters['JSONify'] = json.dumps return env diff --git a/caimira/apps/calculator/static/js/form.js b/caimira/apps/calculator/static/js/form.js index 6ed6c0b3..86c23880 100644 --- a/caimira/apps/calculator/static/js/form.js +++ b/caimira/apps/calculator/static/js/form.js @@ -328,6 +328,49 @@ function on_wearing_mask_change() { }) } +function update_booster_warning() { + // Check if "Other" is selected + $("#vaccine_booster_type").find(":selected").val() == "Other" ? $("#booster_warning").show() : $("#booster_warning").hide(); +} + +function update_booster_dropdown(url) { + let primary_vaccine_option = $("#vaccine_type").find(":selected").val(); + $("#vaccine_booster_type option").remove(); + vaccine_booster_host_immunity.forEach(booster => { + if (booster['primary series vaccine'] == primary_vaccine_option) + $("#vaccine_booster_type").append(``); + }); + $("#vaccine_booster_type").append(``); + + let booster_vaccine = url.searchParams.has('vaccine_booster_type') ? url.searchParams.get('vaccine_booster_type') : null; + $(`#vaccine_booster_type > option[value="${booster_vaccine}"`).attr('selected', true); + + update_booster_warning(); +} + +function on_vaccination_change(url) { + vaccination_option = $('input[type=radio][name=vaccine_option]'); + vaccination_option.each(function (index) { + if (this.checked) { + getChildElement($(this)).show(); + require_fields(this); + } + else { + getChildElement($(this)).hide(); + require_fields(this); + } + }); + update_booster_dropdown(url); +} + +function on_vaccination_booster_change() { + vaccination_booster_option = $('input[type=radio][name=vaccine_booster_option]'); + vaccination_booster_option.each(function (index) { + if (this.checked) getChildElement($(this)).show(); + else getChildElement($(this)).hide(); + }); +} + function populate_temp_hum_values(data, index) { $("#sensor_temperature").text(Math.round(data[index].Details.T) + '°C'); $("#sensor_humidity").text(Math.round(data[index].Details.RH) + '%'); @@ -818,6 +861,7 @@ window.onpagehide = function(){ $(document).ready(function () { var url = new URL(decodeURIComponent(window.location.href)); //Pre-fill form with known values + url.searchParams.forEach((value, name) => { //If element exists if(document.getElementsByName(name).length > 0) { @@ -852,8 +896,8 @@ $(document).ready(function () { $("#sr_interactions").text(index - 1); } - else if (name == 'sensor_in_use') { - // TODO - Validate if sensor exists + else if (name == 'sensor_in_use' || name == 'vaccine_type' || name == 'vaccine_booster_type') { + // Validation after } //Ignore 0 (default) values from server side @@ -865,6 +909,14 @@ $(document).ready(function () { }); // Handle default URL values if they are not explicitly defined. + + // Populate primary vaccine dropdown + $("#vaccine_type option").remove(); + let primary_vaccine = url.searchParams.has('vaccine_type') ? url.searchParams.get('vaccine_type') : null; + vaccine_primary_host_immunity.forEach(vaccine => $("#vaccine_type").append(``)); + $(`#vaccine_type > option[value="${primary_vaccine}"]`).attr('selected', true); + + // Handle geographic location input if (Array.from(url.searchParams).length > 0) { if (!url.searchParams.has('location_name')) { $('[name="location_name"]').val('Geneva') @@ -878,7 +930,6 @@ $(document).ready(function () { } } - // When the document is ready, deal with the fact that we may be here // as a result of a forward/back browser action. If that is the case, update // the visibility of some of our inputs. @@ -931,6 +982,23 @@ $(document).ready(function () { // Call the function now to handle forward/back button presses in the browser. on_wearing_mask_change(); + // When the vaccinated_option_option changes we want to make its respective + // children show/hide. + $("input[type=radio][name=vaccine_option]").change(() => on_vaccination_change(url)); + // Call the function now to handle forward/back button presses in the browser. + on_vaccination_change(url); + + // When the vaccine_type dropdown selected option changes we want to update + // the booster vaccine dropdown. + $("#vaccine_type").change(() => update_booster_dropdown(url)); + $("#vaccine_booster_type").change(update_booster_warning); + + // When the vaccinated_booster_option changes we want to make its respective + // children show/hide. + $("input[type=radio][name=vaccine_booster_option]").change(on_vaccination_booster_change); + // Call the function now to handle forward/back button presses in the browser. + on_vaccination_booster_change(); + // When the short_range_option changes we want to make its respective // children show/hide. $("input[type=radio][name=short_range_option]").change(on_short_range_option_change); @@ -1216,3 +1284,47 @@ function objectifyForm(formArray) { returnArray[formArray[i]['name']] = formArray[i]['value']; return returnArray; } + +// ------- VACCINATION DATA ------- + +// From data available in Results of COVID-19 Vaccine Effectiveness +// Studies: An Ongoing Systematic Review - Updated September 8, 2022. +// https://view-hub.org/resources +vaccine_primary_host_immunity = [ + 'AZD1222_(AstraZeneca)', + 'AZD1222_(AstraZeneca)_and_BNT162b2_(Pfizer)', + 'AZD1222_(AstraZeneca)_and_any_mRNA_-_heterologous', + 'Ad26.COV2.S_(Janssen)', + 'Any_mRNA_-_heterologous', + 'BBIBP-CorV_(Beijing_CNBG)', + 'BNT162b2_(Pfizer)', + 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', + 'CoronaVac_(Sinovac)', + 'CoronaVac_(Sinovac)_and_AZD1222_(AstraZeneca)', + 'CoronaVac_(Sinovac)_and_AZD1222_(AstraZeneca)_-_heterologous', + 'CoronaVac_(Sinovac)_and_BNT162b2_(Pfizer)', + 'Covishield', + 'Sputnik_V_(Gamaleya)', + 'mRNA-1273_(Moderna)', +] + +vaccine_booster_host_immunity = [ + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'AZD1222_(AstraZeneca)',}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'BNT162b2_(Pfizer)',}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'Ad26.COV2.S_(Janssen)', 'booster vaccine': 'Ad26.COV2.S_(Janssen)',}, + {'primary series vaccine': 'Ad26.COV2.S_(Janssen)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'AZD1222_(AstraZeneca)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'BNT162b2_(Pfizer)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'BNT162b2_(Pfizer)_(3_doses)', 'booster vaccine': 'BNT162b2_(Pfizer)_(4th_dose)',}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'AZD1222_(AstraZeneca)',}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'BNT162b2_(Pfizer)',}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'CoronaVac_(Sinovac)',}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)',}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)',}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'mRNA-1273_(Moderna)',} +] diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2 index 665527f6..5959d555 100644 --- a/caimira/apps/templates/base/calculator.form.html.j2 +++ b/caimira/apps/templates/base/calculator.form.html.j2 @@ -58,7 +58,7 @@ Virus data: -
With booster?
The use of masks mitigates exposure at short-range. The analytical model with short-range interactions does not take mask wearing into account.
-0 short-range interactions.
@@ -604,8 +647,6 @@No vaccination selected.
+ {% endif %} +The vaccination input corresponds to the vaccine type(s) administrated to the exposed population, assuming every exposed (or the occupant in question) has received the vaccine cocktail selected by the user. +The respective vaccine effectiveness values were extracted from data available in Results of COVID-19 Vaccine Effectiveness Studies: An Ongoing Systematic Review - Updated September 8, 2022, using this script.
+Please enter either the room volume (in m³) or both the floor area (m²) and the room height (m). diff --git a/caimira/data/__init__.py b/caimira/data/__init__.py index 23dab962..8c0a1390 100644 --- a/caimira/data/__init__.py +++ b/caimira/data/__init__.py @@ -40,3 +40,48 @@ GenevaTemperatures = { month: GenevaTemperatures_hourly[month].refine(refine_factor=10) for month, temperatures in local_hourly_temperatures_celsius_per_hour.items() } + + +# ------- VACCINATION DATA ------- + +# From data available in Results of COVID-19 Vaccine Effectiveness +# Studies: An Ongoing Systematic Review - Updated September 8, 2022. +# https://view-hub.org/resources +vaccine_primary_host_immunity = { + 'AZD1222_(AstraZeneca)': 0.589293, + 'AZD1222_(AstraZeneca)_and_BNT162b2_(Pfizer)': 0.7865, + 'AZD1222_(AstraZeneca)_and_any_mRNA_-_heterologous': 0.81, + 'Ad26.COV2.S_(Janssen)': 0.551278, + 'Any_mRNA_-_heterologous': 0.93875, + 'BBIBP-CorV_(Beijing_CNBG)': 0.4325, + 'BNT162b2_(Pfizer)': 0.660272, + 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)': 0.586319, + 'CoronaVac_(Sinovac)': 0.317333, + 'CoronaVac_(Sinovac)_and_AZD1222_(AstraZeneca)': 0.472, + 'CoronaVac_(Sinovac)_and_AZD1222_(AstraZeneca)_-_heterologous': 0.74, + 'CoronaVac_(Sinovac)_and_BNT162b2_(Pfizer)': 0.7965, + 'Covishield': 0.98, + 'Sputnik_V_(Gamaleya)': 0.696, + 'mRNA-1273_(Moderna)': 0.730148, +} + +vaccine_booster_host_immunity = [ + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'AZD1222_(AstraZeneca)', 'VE': 0.500429}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'BNT162b2_(Pfizer)', 'VE': 0.537818}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)', 'VE': 0.284}, + {'primary series vaccine': 'AZD1222_(AstraZeneca)', 'booster vaccine': 'mRNA-1273_(Moderna)', 'VE': 0.709143}, + {'primary series vaccine': 'Ad26.COV2.S_(Janssen)', 'booster vaccine': 'Ad26.COV2.S_(Janssen)', 'VE': 0.492667}, + {'primary series vaccine': 'Ad26.COV2.S_(Janssen)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)', 'VE': 0.79}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'AZD1222_(AstraZeneca)', 'VE': 0.801}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'BNT162b2_(Pfizer)', 'VE': 0.60712}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)', 'VE': 0.632633}, + {'primary series vaccine': 'BNT162b2_(Pfizer)', 'booster vaccine': 'mRNA-1273_(Moderna)', 'VE': 0.716786}, + {'primary series vaccine': 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)_and_mRNA-1273_(Moderna)', 'VE': 0.645}, + {'primary series vaccine': 'BNT162b2_(Pfizer)_(3_doses)', 'booster vaccine': 'BNT162b2_(Pfizer)_(4th_dose)', 'VE': 0.962}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'AZD1222_(AstraZeneca)', 'VE': 0.9405}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'BNT162b2_(Pfizer)', 'VE': 0.690563}, + {'primary series vaccine': 'CoronaVac_(Sinovac)', 'booster vaccine': 'CoronaVac_(Sinovac)', 'VE': 0.52225}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)', 'VE': 0.842143}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'BNT162b2_(Pfizer)_or_mRNA-1273_(Moderna)', 'VE': 0.632633}, + {'primary series vaccine': 'mRNA-1273_(Moderna)', 'booster vaccine': 'mRNA-1273_(Moderna)', 'VE': 0.633238} +] diff --git a/caimira/scripts/data/WeeklySummary_COVID19_VE_Studies_08Sep2022_adapted.xlsx b/caimira/scripts/data/WeeklySummary_COVID19_VE_Studies_08Sep2022_adapted.xlsx new file mode 100644 index 00000000..c3ff459d --- /dev/null +++ b/caimira/scripts/data/WeeklySummary_COVID19_VE_Studies_08Sep2022_adapted.xlsx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2aef0605c3668b4884b815f71c0eae93eaaa7b88d6b6c17ff97f6a86a38674a5 +size 76179581 diff --git a/caimira/scripts/data/vaccine_effectiveness.py b/caimira/scripts/data/vaccine_effectiveness.py new file mode 100644 index 00000000..8186da64 --- /dev/null +++ b/caimira/scripts/data/vaccine_effectiveness.py @@ -0,0 +1,56 @@ +import pandas as pd +from tabulate import tabulate + +''' +Script file to generate the vaccine effectiveness values. +To generate the primary vaccine effectiveness values, uncoment lines 16-21. +To generate the booster effectiveness values, uncoment lines 26-56. +''' + +# Data from 08 Sep. 2022 +file_loc = "./WeeklySummary_COVID19_VE_Studies_08Sep2022_adapted.xlsx" + + +# ------- PRIMARY VACCINATION ------ # + +# df = pd.read_excel(file_loc, sheet_name="Primary_filtered", usecols="A, B, E") + +# calculate the VE value +# df = df.drop(df[df['VE'] < 0].index) +# ve_data = df.groupby(['vaccine'])['VE'].mean().divide(100).reset_index() +# print(tabulate(ve_data, headers='keys', tablefmt='psql')) + + +# ------- BOOSTER VACCINATION ------ # + +# df = pd.read_excel(file_loc, sheet_name="Booster_filtered", usecols="A, B, C, F") + +# # create df without the ' or ' substring in primary vaccines +# rows_with_or = df[df['primary series vaccine'].str.contains(' or ')] +# rows_indexes = list(rows_with_or.index) +# df_without_or = df.drop(labels=rows_indexes, axis=0) + +# # copy of all the rows that contain ' or ' +# new_rows_with_or = rows_with_or.reset_index().copy() + +# # create new dataframe empty +# rows_to_add = pd.DataFrame(columns=rows_with_or.columns) + +# # duplicate each row and add it into the new dataframe +# for index, row in new_rows_with_or.iterrows(): +# new_rows_with_or.at[index, 'primary series vaccine'] = row['primary series vaccine'].split(' or ')[0] +# rows_to_add.loc[index] = new_rows_with_or.loc[index] +# new_rows_with_or.at[index, 'primary series vaccine'] = row['primary series vaccine'].split(' or ')[1] +# rows_to_add.loc[len(rows_indexes)+index] = new_rows_with_or.loc[index] + +# # merge the dataframe without the ' or ' with the new dataframe that has the rows divided in two +# final_df = pd.concat([df_without_or, rows_to_add]).reset_index().drop(columns=['index']) + +# # calculate the VE value +# final_df = final_df.drop(final_df[final_df['VE'] < 0].index) + +# ve_data = final_df.groupby(['primary series vaccine', 'booster vaccine'])['VE'].mean().divide(100).reset_index() + +# result = ve_data.to_dict('records') + +# print(tabulate(ve_data, headers='keys', tablefmt='psql')) diff --git a/caimira/tests/models/test_exposure_model.py b/caimira/tests/models/test_exposure_model.py index f66943a5..1a986815 100644 --- a/caimira/tests/models/test_exposure_model.py +++ b/caimira/tests/models/test_exposure_model.py @@ -297,13 +297,13 @@ def test_prob_meet_infected_person(pop, cases, AB, exposed, infected, prob_meet_ [30, known_concentrations(lambda t: 1.2), 100000, 68, 5, 55.93154502], ]) -def test_probabilistic_exposure_probability(exposed_population, cm, +def test_probabilistic_exposure_probability(sr_model, 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, + model = ExposureModel(cm, sr_model, 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 @@ -390,4 +390,25 @@ def test_diameter_vectorisation_room(diameter_dependent_model, sr_model, cases_m ventilation = models.HVACMechanical(active=models.SpecificInterval(((0., 24.), )), q_air_mech=100.)) with pytest.raises(ValueError, match=error_message): models.ExposureModel(concentration, sr_model, populations[0], cases_model) - \ No newline at end of file + + +@pytest.mark.parametrize( + ["cm", "host_immunity", "expected_probability"], + [ + [known_concentrations(lambda t: 36.), np.array([0.25, 0.5]), np.array([57.40415859, 41.03956914])], + [known_concentrations(lambda t: 36.), np.array([0., 1.]), np.array([67.95037626, 0.])], + ] +) +def test_host_immunity_vectorisation(sr_model, cases_model, cm, host_immunity, expected_probability): + population = models.Population( + 10, halftime, models.Mask(np.array([0.3, 0.35])), + models.Activity.types['Standing'], host_immunity=host_immunity + ) + model = ExposureModel(cm, sr_model, population, cases_model) + inf_probability = model.infection_probability() + + np.testing.assert_almost_equal( + inf_probability, expected_probability, decimal=1 + ) + assert isinstance(inf_probability, np.ndarray) + assert inf_probability.shape == (2, ) diff --git a/setup.cfg b/setup.cfg index c2f968b6..46f08728 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,3 +29,5 @@ ignore_missing_imports = True [mypy-timezonefinder.*] ignore_missing_imports = True +[mypy-pandas.*] +ignore_missing_imports = True