From 741046a5af1ea8f9aea2a59a98b6e33affe152e8 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Tue, 24 Oct 2023 17:06:13 +0200 Subject: [PATCH] changed files directory and added remaining store items --- caimira/apps/calculator/__init__.py | 24 -------- caimira/apps/calculator/model_generator.py | 9 +-- caimira/global_store/constants.py | 59 ++++++++++++++++++- .../data_service.py | 0 .../{data_store.py => global_store.py} | 2 +- caimira/models.py | 29 ++++----- caimira/tests/test_data_service.py | 2 +- 7 files changed, 78 insertions(+), 47 deletions(-) rename caimira/{apps/calculator => global_store}/data_service.py (100%) rename caimira/global_store/{data_store.py => global_store.py} (92%) diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py index 7dc383a7..f4076874 100644 --- a/caimira/apps/calculator/__init__.py +++ b/caimira/apps/calculator/__init__.py @@ -28,7 +28,6 @@ from . import markdown_tools from . import model_generator from .report_generator import ReportGenerator, calculate_report_data -from .data_service import DataService from .user import AuthenticatedUser, AnonymousUser # The calculator version is based on a combination of the model version and the @@ -106,17 +105,6 @@ async def post(self) -> None: pprint(requested_model_config) start = datetime.datetime.now() - # Data Service API Integration - fetched_service_data = None - data_service: DataService = self.settings["data_service"] - if self.settings["data_service"]: - try: - fetched_service_data = await data_service.fetch() - except Exception as err: - error_message = f"Something went wrong with the data service: {str(err)}" - LOG.error(error_message, exc_info=True) - self.send_error(500, reason=error_message) - try: form = model_generator.FormData.from_dict(requested_model_config) except Exception as err: @@ -429,15 +417,6 @@ def make_app( ) template_environment.globals['get_url']=get_root_url template_environment.globals['get_calculator_url']=get_root_calculator_url - - data_service_credentials = { - 'data_service_client_email': os.environ.get('DATA_SERVICE_CLIENT_EMAIL', None), - 'data_service_client_password': os.environ.get('DATA_SERVICE_CLIENT_PASSWORD', None), - } - data_service = None - data_service_enabled = os.environ.get('DATA_SERVICE_ENABLED', 'False').lower() == 'true' - if data_service_enabled: - data_service = DataService(data_service_credentials) if debug: tornado.log.enable_pretty_logging() @@ -456,9 +435,6 @@ def make_app( arve_client_secret=os.environ.get('ARVE_CLIENT_SECRET', None), arve_api_key=os.environ.get('ARVE_API_KEY', None), - # Data Service Integration - data_service=data_service, - # Process parallelism controls. There is a balance between serving a single report # requests quickly or serving multiple requests concurrently. # The defaults are: handle one report at a time, and allow parallelism diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index edf29e17..50548911 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -315,10 +315,10 @@ def initialize_room(self) -> models.Room: if self.arve_sensors_option == False: if self.room_heating_option: - humidity = 0.3 + humidity = constants.room['defaults']['humidity_with_heating'] else: - humidity = 0.5 - inside_temp = 293. + humidity = constants.room['defaults']['humidity_without_heating'] + inside_temp = constants.room['defaults']['inside_temp'] else: humidity = float(self.humidity) inside_temp = self.inside_temp @@ -472,7 +472,8 @@ def ventilation(self) -> models._VentilationBase: # This is a minimal, always present source of ventilation, due # to the air infiltration from the outside. # See CERN-OPEN-2021-004, p. 12. - infiltration_ventilation = models.AirChange(active=always_on, air_exch=0.25) + residual_vent: float = constants.ventilation['infiltration_ventilation'] # type: ignore + infiltration_ventilation = models.AirChange(active=always_on, air_exch=residual_vent) if self.hepa_option: hepa = models.HEPAFilter(active=always_on, q_air_mech=self.hepa_amount) return models.MultipleVentilation((ventilation, hepa, infiltration_ventilation)) diff --git a/caimira/global_store/constants.py b/caimira/global_store/constants.py index 5a9c3b41..d32992d5 100644 --- a/caimira/global_store/constants.py +++ b/caimira/global_store/constants.py @@ -219,6 +219,46 @@ 'sigma': {'B': 0.262364, 'L': 0.506818, 'O': 0.585005}, } +short_range_model = { + 'exhalation_coefficient': 2, + 'mouth_diameter': 0.02, + 'penetration_coefficients': {'𝛽r1': 0.18, '𝛽r2': 0.2, '𝛽x1': 2.4}, + 'tstar': 2 +} + +concentration_model = { + 'CO2_concentration_model': { + 'CO2_atmosphere_concentration': 440.44, + 'CO2_fraction_exhaled': 0.042 + }, + 'min_background_concentration': 0.0 +} + +population_with_virus = { + 'fraction_of_infectious_virus': 1, +} + +particle = { + 'evaporation_factor': 0.3, +} + +ventilation = { + 'natural': { + 'discharge_factor': { + 'sliding': 0.6, + }, + }, + 'infiltration_ventilation': 0.25, +} + +room = { + 'defaults': { + 'inside_temp': 293, + 'humidity_with_heating': 0.3, + 'humidity_without_heating': 0.5, + }, +} + def update_local_reference(local_data, api_data: typing.Union[dict, typing.Any]): ''' Recursive function that iterates through the keys and values of local and remote dictionaries. @@ -240,11 +280,12 @@ def update_local_reference(local_data, api_data: typing.Union[dict, typing.Any]) async def populate_data(): global data_fetched, BLOmodel, covid_overal_vl_data, infectious_dose_distribution, viable_to_RNA_ratio_distribution, virus_distributions, activity_distributions global mask_distributions, expiration_BLO_factors, long_range_expiration_distributions, short_range_expiration_distributions, short_range_distances - global infected_population_scenario_activity, monte_carlo_sample_size, conditional_prob_inf_given_viral_load, exposure_model + global infected_population_scenario_activity, monte_carlo_sample_size, conditional_prob_inf_given_viral_load, exposure_model, short_range_model + global concentration_model, population_with_virus, particle, ventilation, room if not data_fetched and os.environ.get('DATA_SERVICE_ENABLED', 'False').lower() == 'true': # Fetch data if it hasn't been fetched yet - from caimira.global_store.data_store import GlobalStore + from caimira.global_store.global_store import GlobalStore await GlobalStore.populate_from_api() data = GlobalStore.get_data()['data'] @@ -279,7 +320,19 @@ async def populate_data(): conditional_prob_inf_given_viral_load, data['conditional_prob_inf_given_viral_load']) exposure_model = update_local_reference( exposure_model, data['exposure_model']) - + short_range_model = update_local_reference( + short_range_model, data['short_range_model']) + concentration_model = update_local_reference( + concentration_model, data['concentration_model']) + population_with_virus = update_local_reference( + population_with_virus, data['population_with_virus']) + particle = update_local_reference( + particle, data['particle']) + ventilation = update_local_reference( + ventilation, data['ventilation']) + room = update_local_reference( + room, data['room']) + data_fetched = True # Executed when this module is imported diff --git a/caimira/apps/calculator/data_service.py b/caimira/global_store/data_service.py similarity index 100% rename from caimira/apps/calculator/data_service.py rename to caimira/global_store/data_service.py diff --git a/caimira/global_store/data_store.py b/caimira/global_store/global_store.py similarity index 92% rename from caimira/global_store/data_store.py rename to caimira/global_store/global_store.py index 4eb6d482..3ae4cd79 100644 --- a/caimira/global_store/data_store.py +++ b/caimira/global_store/global_store.py @@ -1,6 +1,6 @@ import os -from caimira.apps.calculator.data_service import DataService +from caimira.global_store.data_service import DataService class GlobalStore: diff --git a/caimira/models.py b/caimira/models.py index 24ef62e2..d49dfcb8 100644 --- a/caimira/models.py +++ b/caimira/models.py @@ -46,6 +46,8 @@ # by providing a no-op cache decorator when type-checking. cached = lambda *cached_args, **cached_kwargs: lambda function: function # noqa +import caimira.global_store.constants as constants + from .utils import method_cache from .dataclass_utils import nested_replace @@ -347,7 +349,7 @@ def discharge_coefficient(self) -> _VectorisedFloat: Average measured value of discharge coefficient for sliding or side-hung windows. """ - return 0.6 + return float(constants.ventilation['natural']['discharge_factor']['sliding']) # type: ignore @dataclass(frozen=True) @@ -860,7 +862,7 @@ def fraction_of_infectious_virus(self) -> _VectorisedFloat: The fraction of infectious virus. """ - return 1. + return float(constants.population_with_virus['fraction_of_infectious_virus']) # type: ignore def aerosols(self): """ @@ -1032,7 +1034,7 @@ def min_background_concentration(self) -> _VectorisedFloat: (in the same unit as the concentration). Its the value towards which the concentration will decay to. """ - return 0. + return float(constants.concentration_model['min_background_concentration']) # type: ignore def normalization_factor(self) -> _VectorisedFloat: """ @@ -1220,7 +1222,7 @@ class ConcentrationModel(_ConcentrationModelBase): #: evaporation factor: the particles' diameter is multiplied by this # factor as soon as they are in the air (but AFTER going out of the, # mask, if any). - evaporation_factor: float = 0.3 + evaporation_factor: float = constants.particle['evaporation_factor'] # type: ignore @property def population(self) -> InfectedPopulation: @@ -1260,10 +1262,10 @@ class CO2ConcentrationModel(_ConcentrationModelBase): CO2_emitters: SimplePopulation #: CO2 concentration in the atmosphere (in ppm) - CO2_atmosphere_concentration: float = 440.44 + CO2_atmosphere_concentration: float = constants.concentration_model['CO2_concentration_model']['CO2_atmosphere_concentration'] # type: ignore #: CO2 fraction in the exhaled air - CO2_fraction_exhaled: float = 0.042 + CO2_fraction_exhaled: float = constants.concentration_model['CO2_concentration_model']['CO2_fraction_exhaled'] # type: ignore @property def population(self) -> SimplePopulation: @@ -1284,7 +1286,6 @@ def normalization_factor(self) -> _VectorisedFloat: return (1e6*self.population.activity.exhalation_rate *self.CO2_fraction_exhaled) - @dataclass(frozen=True) class ShortRangeModel: ''' @@ -1309,14 +1310,14 @@ def dilution_factor(self) -> _VectorisedFloat: The dilution factor for the respective expiratory activity type. ''' # Average mouth opening diameter (m) - mouth_diameter = 0.02 + mouth_diameter: float = constants.short_range_model['dilution_factor']['mouth_diameter'] # type: ignore # Breathing rate, from m3/h to m3/s BR = np.array(self.activity.exhalation_rate/3600.) # Exhalation coefficient. Ratio between the duration of a breathing cycle and the duration of # the exhalation. - φ = 2 + φ: float = constants.short_range_model['dilution_factor']['exhalation_coefficient'] # type: ignore # Exhalation airflow, as per Jia et al. (2022) Q_exh: _VectorisedFloat = φ * BR @@ -1328,12 +1329,12 @@ def dilution_factor(self) -> _VectorisedFloat: u0 = np.array(Q_exh/Am) # Duration of the expiration period(s), assuming a 4s breath-cycle - tstar = 2.0 + tstar: float = constants.short_range_model['dilution_factor']['tstar'] # type: ignore # Streamwise and radial penetration coefficients - 𝛽r1 = 0.18 - 𝛽r2 = 0.2 - 𝛽x1 = 2.4 + 𝛽r1: float = constants.short_range_model['dilution_factor']['penetration_coefficients']['𝛽r1'] # type: ignore + 𝛽r2: float = constants.short_range_model['dilution_factor']['penetration_coefficients']['𝛽r2'] # type: ignore + 𝛽x1: float = constants.short_range_model['dilution_factor']['penetration_coefficients']['𝛽x1'] # type: ignore # Parameters in the jet-like stage # Position of virtual origin @@ -1489,7 +1490,7 @@ class ExposureModel: geographical_data: Cases #: The number of times the exposure event is repeated (default 1). - repeats: int = 1 + repeats: int = constants.exposure_model['repeats'] def __post_init__(self): """ diff --git a/caimira/tests/test_data_service.py b/caimira/tests/test_data_service.py index 01cf176c..4991b259 100644 --- a/caimira/tests/test_data_service.py +++ b/caimira/tests/test_data_service.py @@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock from tornado.httpclient import HTTPError -from caimira.apps.calculator.data_service import DataService +from caimira.global_store.data_service import DataService @dataclass class MockResponse: