Skip to content

Commit

Permalink
Merge branch 'feature/global_store' into 'master'
Browse files Browse the repository at this point in the history
Global Store and Configuration Implementation

See merge request caimira/caimira!466
  • Loading branch information
lrdossan committed Nov 20, 2023
2 parents 4c4b3f0 + 19dcc4e commit 59466df
Show file tree
Hide file tree
Showing 12 changed files with 935 additions and 323 deletions.
26 changes: 1 addition & 25 deletions caimira/apps/calculator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,7 +37,7 @@
# 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.12.1"
__version__ = "4.13.0"

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -105,17 +104,6 @@ async def post(self) -> None:
from pprint import pprint
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)
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
74 changes: 0 additions & 74 deletions caimira/apps/calculator/data_service.py

This file was deleted.

53 changes: 7 additions & 46 deletions caimira/apps/calculator/defaults.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import typing

from caimira.store.configuration import config

# ------------------ Default form values ----------------------

# Used to declare when an attribute of a class must have a value provided, and
# there should be no default value used.
NO_DEFAULT = object()
DEFAULT_MC_SAMPLE_SIZE = 250_000
DEFAULT_MC_SAMPLE_SIZE = config.monte_carlo_sample_size

#: The default values for undefined fields. Note that the defaults here
#: and the defaults in the html form must not be contradictory.
Expand Down Expand Up @@ -79,57 +81,17 @@

# ------------------ Activities ----------------------

ACTIVITIES: typing.List[typing.Dict[str, typing.Any]] = [
# Mostly silent in the office, but 1/3rd of time speaking.
{'name': 'office', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 2}},
{'name': 'smallmeeting', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': None}},
# Each infected person spends 1/3 of time speaking.
{'name': 'largemeeting', 'activity': 'Standing',
'expiration': {'Speaking': 1, 'Breathing': 2}},
{'name': 'callcentre', 'activity': 'Seated', 'expiration': 'Speaking'},
# Daytime control room shift, 50% speaking.
{'name': 'controlroom-day', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 1}},
# Nightshift control room, 10% speaking.
{'name': 'controlroom-night', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 9}},
{'name': 'library', 'activity': 'Seated', 'expiration': 'Breathing'},
# Model 1/2 of time spent speaking in a lab.
{'name': 'lab', 'activity': 'Light activity',
'expiration': {'Speaking': 1, 'Breathing': 1}},
# Model 1/2 of time spent speaking in a workshop.
{'name': 'workshop', 'activity': 'Moderate activity',
'expiration': {'Speaking': 1, 'Breathing': 1}},
{'name': 'training', 'activity': 'Standing', 'expiration': 'Speaking'},
{'name': 'training_attendee', 'activity': 'Seated', 'expiration': 'Breathing'},
{'name': 'gym', 'activity': 'Heavy exercise', 'expiration': 'Breathing'},
{'name': 'household-day', 'activity': 'Light activity',
'expiration': {'Breathing': 5, 'Speaking': 5}},
{'name': 'household-night', 'activity': 'Seated',
'expiration': {'Breathing': 7, 'Speaking': 3}},
{'name': 'primary-school', 'activity': 'Light activity',
'expiration': {'Breathing': 5, 'Speaking': 5}},
{'name': 'secondary-school', 'activity': 'Light activity',
'expiration': {'Breathing': 7, 'Speaking': 3}},
{'name': 'university', 'activity': 'Seated',
'expiration': {'Breathing': 9, 'Speaking': 1}},
{'name': 'restaurant', 'activity': 'Seated',
'expiration': {'Breathing': 1, 'Speaking': 9}},
{'name': 'precise', 'activity': None, 'expiration': None},
]
ACTIVITIES: typing.Dict[str, typing.Dict] = config.population_scenario_activity

# ------------------ Validation ----------------------

ACTIVITY_TYPES = [activity['name'] for activity in ACTIVITIES]
ACTIVITY_TYPES: typing.List[str] = list(ACTIVITIES.keys())
COFFEE_OPTIONS_INT = {'coffee_break_0': 0, 'coffee_break_1': 1,
'coffee_break_2': 2, 'coffee_break_4': 4}
CONFIDENCE_LEVEL_OPTIONS = {'confidence_low': 10,
'confidence_medium': 5, 'confidence_high': 2}
MECHANICAL_VENTILATION_TYPES = {
'mech_type_air_changes', 'mech_type_air_supply', 'not-applicable'}
MASK_TYPES = {'Type I', 'FFP2', 'Cloth'}
MASK_TYPES: typing.List[str] = list(config.mask_distributions.keys())
MASK_WEARING_OPTIONS = {'mask_on', 'mask_off'}
MONTH_NAMES = [
'January', 'February', 'March', 'April', 'May', 'June', 'July',
Expand All @@ -143,8 +105,7 @@
'mRNA-1273_(Moderna)', 'Sputnik_V_(Gamaleya)', 'CoronaVac_(Sinovac)_and_BNT162b2_(Pfizer)']
VENTILATION_TYPES = {'natural_ventilation',
'mechanical_ventilation', 'no_ventilation'}
VIRUS_TYPES = {'SARS_CoV_2', 'SARS_CoV_2_ALPHA', 'SARS_CoV_2_BETA',
'SARS_CoV_2_GAMMA', 'SARS_CoV_2_DELTA', 'SARS_CoV_2_OMICRON'}
VIRUS_TYPES: typing.List[str] = list(config.virus_distributions)
VOLUME_TYPES = {'room_volume_explicit', 'room_volume_from_dimensions'}
WINDOWS_OPENING_REGIMES = {'windows_open_permanently',
'windows_open_periodically', 'not-applicable'}
Expand Down
43 changes: 12 additions & 31 deletions caimira/apps/calculator/model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .defaults import (NO_DEFAULT, DEFAULT_MC_SAMPLE_SIZE, DEFAULTS, ACTIVITIES, ACTIVITY_TYPES, COFFEE_OPTIONS_INT, CONFIDENCE_LEVEL_OPTIONS,
MECHANICAL_VENTILATION_TYPES, MASK_TYPES, MASK_WEARING_OPTIONS, MONTH_NAMES, VACCINE_BOOSTER_TYPE, VACCINE_TYPE,
VENTILATION_TYPES, VIRUS_TYPES, VOLUME_TYPES, WINDOWS_OPENING_REGIMES, WINDOWS_TYPES)
from caimira.store.configuration import config

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -314,10 +315,10 @@ def initialize_room(self) -> models.Room:

if self.arve_sensors_option == False:
if self.room_heating_option:
humidity = 0.3
humidity = config.room['defaults']['humidity_with_heating']
else:
humidity = 0.5
inside_temp = 293.
humidity = config.room['defaults']['humidity_without_heating']
inside_temp = config.room['defaults']['inside_temp']
else:
humidity = float(self.humidity)
inside_temp = self.inside_temp
Expand Down Expand Up @@ -373,7 +374,7 @@ def build_CO2_model(self, sample_size=DEFAULT_MC_SAMPLE_SIZE) -> models.CO2Conce
if (self.activity_type == 'precise'):
activity_defn, _ = self.generate_precise_activity_expiration()
else:
activity_defn = ACTIVITIES[ACTIVITY_TYPES.index(self.activity_type)]['activity']
activity_defn = activity_defn = ACTIVITIES[self.activity_type]['activity']

population = mc.SimplePopulation(
number=models.IntPiecewiseConstant(transition_times=tuple(transition_times), values=tuple(total_people)),
Expand Down Expand Up @@ -476,7 +477,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 = config.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))
Expand Down Expand Up @@ -511,9 +513,8 @@ def infected_population(self) -> mc.InfectedPopulation:
# Initializes the virus
virus = virus_distributions[self.virus_type]

activity_index = ACTIVITY_TYPES.index(self.activity_type)
activity_defn = ACTIVITIES[activity_index]['activity']
expiration_defn = ACTIVITIES[activity_index]['expiration']
activity_defn = ACTIVITIES[self.activity_type]['activity']
expiration_defn = ACTIVITIES[self.activity_type]['expiration']

if (self.activity_type == 'smallmeeting'):
# Conversation of N people is approximately 1/N% of the time speaking.
Expand All @@ -538,29 +539,9 @@ def infected_population(self) -> mc.InfectedPopulation:
return infected

def exposed_population(self) -> mc.Population:
scenario_activity = {
'office': 'Seated',
'controlroom-day': 'Seated',
'controlroom-night': 'Seated',
'smallmeeting': 'Seated',
'largemeeting': 'Standing',
'callcentre': 'Seated',
'library': 'Seated',
'training': 'Standing',
'training_attendee': 'Seated',
'lab':'Light activity',
'workshop': 'Moderate activity',
'gym':'Heavy exercise',
'household-day': 'Light activity',
'household-night': 'Seated',
'primary-school': 'Light activity',
'secondary-school': 'Light activity',
'university': 'Seated',
'restaurant': 'Seated',
'precise': self.precise_activity['physical_activity'] if self.activity_type == 'precise' else None,
}

activity_defn = scenario_activity[self.activity_type]
activity_defn = (self.precise_activity['physical_activity']
if self.activity_type == 'precise'
else str(config.population_scenario_activity[self.activity_type]['activity']))
activity = activity_distributions[activity_defn]

infected_occupants = self.infected_people
Expand Down
9 changes: 6 additions & 3 deletions caimira/apps/calculator/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ... import monte_carlo as mc
from .model_generator import FormData, DEFAULT_MC_SAMPLE_SIZE
from ... import dataclass_utils
from caimira.store.configuration import config


def model_start_end(model: models.ExposureModel):
Expand Down Expand Up @@ -201,16 +202,18 @@ def conditional_prob_inf_given_vl_dist(infection_probability: models._Vectorised
for vl_log in viral_loads:
specific_prob = infection_probability[np.where((vl_log-step/2-specific_vl)*(vl_log+step/2-specific_vl)<0)[0]] #type: ignore
pi_means.append(specific_prob.mean())
lower_percentiles.append(np.quantile(specific_prob, 0.05))
upper_percentiles.append(np.quantile(specific_prob, 0.95))
lower_percentiles.append(np.quantile(specific_prob, config.conditional_prob_inf_given_viral_load['lower_percentile']))
upper_percentiles.append(np.quantile(specific_prob, config.conditional_prob_inf_given_viral_load['upper_percentile']))

return pi_means, lower_percentiles, upper_percentiles


def manufacture_conditional_probability_data(exposure_model: models.ExposureModel,
infection_probability: models._VectorisedFloat):

min_vl, max_vl, step = 2, 10, 8/100
min_vl = config.conditional_prob_inf_given_viral_load['min_vl']
max_vl = config.conditional_prob_inf_given_viral_load['max_vl']
step = (max_vl - min_vl)/100
viral_loads = np.arange(min_vl, max_vl, step)
specific_vl = np.log10(exposure_model.concentration_model.virus.viral_load_in_sputum)
pi_means, lower_percentiles, upper_percentiles = conditional_prob_inf_given_vl_dist(infection_probability, viral_loads,
Expand Down
Loading

0 comments on commit 59466df

Please sign in to comment.