diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e7cea6f..2f0143f0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,6 +87,7 @@ test-cern-caimira-py39: - ./app-config/openshift/${CAIMIRA_INSTANCE}/expected only: - master + - live/caimira-test check_openshift_config_test: extends: .test_openshift_config diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..24f5aa32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# 4.17.2 (September 19, 2024) + +## Features Added +- Initial commit with changelog file. +- New project layout architecture. +- Added CAiMIRA REST API features. + +## Bug Fixes +- Virus and CO2 routes and controllers. diff --git a/caimira/pyproject.toml b/caimira/pyproject.toml index 93d1f1e4..3a8e7aac 100644 --- a/caimira/pyproject.toml +++ b/caimira/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "caimira" -version = "4.17.0a2" +version = "4.17.2" description = "CAiMIRA - CERN Airborne Model for Indoor Risk Assessment" license = { text = "Apache-2.0" } authors = [ diff --git a/caimira/src/caimira/api/controller/co2_report_controller.py b/caimira/src/caimira/api/controller/co2_report_controller.py index 02433318..22db3b48 100644 --- a/caimira/src/caimira/api/controller/co2_report_controller.py +++ b/caimira/src/caimira/api/controller/co2_report_controller.py @@ -1,26 +1,27 @@ +import typing + from caimira.calculator.validators.co2.co2_validator import CO2FormData from caimira.calculator.store.data_registry import DataRegistry +from caimira.calculator.models.models import CO2DataModel -def generate_form_obj(form_data, data_registry): +def generate_form_obj(form_data: typing.Dict, data_registry: DataRegistry) -> CO2FormData: return CO2FormData.from_dict(form_data=form_data, data_registry=data_registry) -def generate_model(form_obj, data_registry): - sample_size = data_registry.monte_carlo['sample_size'] - return form_obj.build_model(sample_size=sample_size) +def generate_model(form_obj: CO2FormData) -> CO2DataModel: + return form_obj.build_model() -def generate_report(model): +def generate_report(model: CO2DataModel) -> typing.Dict: return dict(model.CO2_fit_params()) -def submit_CO2_form(form_data): - data_registry = DataRegistry() +def submit_CO2_form(form_data: typing.Dict) -> typing.Dict: + data_registry: DataRegistry = DataRegistry() - form_obj = generate_form_obj( - form_data=form_data, data_registry=data_registry) - model = generate_model(form_obj=form_obj, data_registry=data_registry) - report_data = generate_report(model=model) + form_obj: CO2FormData = generate_form_obj(form_data=form_data, data_registry=data_registry) + model: CO2DataModel = generate_model(form_obj=form_obj) + report_data: typing.Dict = generate_report(model=model) return report_data diff --git a/caimira/src/caimira/api/controller/virus_report_controller.py b/caimira/src/caimira/api/controller/virus_report_controller.py index 9f070757..0b79a276 100644 --- a/caimira/src/caimira/api/controller/virus_report_controller.py +++ b/caimira/src/caimira/api/controller/virus_report_controller.py @@ -1,37 +1,37 @@ import concurrent.futures import functools +import typing from caimira.calculator.validators.virus.virus_validator import VirusFormData from caimira.calculator.store.data_registry import DataRegistry +from caimira.calculator.models.models import ExposureModel import caimira.calculator.report.virus_report_data as rg -def generate_form_obj(form_data, data_registry): +def generate_form_obj(form_data: typing.Dict, data_registry: DataRegistry) -> VirusFormData: return VirusFormData.from_dict( form_data=form_data, data_registry=data_registry, ) -def generate_model(form_obj, data_registry): - sample_size = data_registry.monte_carlo['sample_size'] - return form_obj.build_model(sample_size=sample_size) - - -def generate_report_results(form_obj): +def generate_report(form_obj: VirusFormData, report_generation_parallelism: typing.Optional[int]) -> typing.Dict: return rg.calculate_report_data( form=form_obj, executor_factory=functools.partial( - concurrent.futures.ThreadPoolExecutor, None, # TODO define report_parallelism + concurrent.futures.ThreadPoolExecutor, + report_generation_parallelism, ), ) -def submit_virus_form(form_data): - data_registry = DataRegistry +def submit_virus_form(form_data: typing.Dict, report_generation_parallelism: typing.Optional[int]) -> typing.Dict: + data_registry: DataRegistry = DataRegistry() + + form_obj: VirusFormData = generate_form_obj(form_data=form_data, data_registry=data_registry) + report_data: typing.Dict = generate_report(form_obj=form_obj, report_generation_parallelism=report_generation_parallelism) - form_obj = generate_form_obj(form_data=form_data, data_registry=data_registry) - model = generate_model(form_obj=form_obj, data_registry=data_registry) - report_data = generate_report_results(form_obj=form_obj, model=model) + # Handle model representation + if report_data['model']: report_data['model'] = repr(report_data['model']) return report_data diff --git a/caimira/src/caimira/api/routes/report_routes.py b/caimira/src/caimira/api/routes/report_routes.py index f448df86..aa38b297 100644 --- a/caimira/src/caimira/api/routes/report_routes.py +++ b/caimira/src/caimira/api/routes/report_routes.py @@ -7,7 +7,7 @@ from caimira.api.controller.co2_report_controller import submit_CO2_form -class BaseReportHandler(tornado.web.RedirectHandler): +class BaseReportHandler(tornado.web.RequestHandler): def set_default_headers(self): self.set_header("Access-Control-Allow-Origin", "*") self.set_header("Access-Control-Allow-Headers", "x-requested-with") @@ -22,7 +22,14 @@ class VirusReportHandler(BaseReportHandler): def post(self): try: form_data = json.loads(self.request.body) - report_data = submit_virus_form(form_data) + arguments = self.request.arguments + # Report generation parallelism argument + try: + report_generation_parallelism = int(arguments['report_generation_parallelism'][0]) + except (ValueError, IndexError, KeyError): + report_generation_parallelism = None + + report_data = submit_virus_form(form_data, report_generation_parallelism) response_data = { "status": "success", @@ -36,12 +43,7 @@ def post(self): self.write_error(status_code=400, exc_info=sys.exc_info()) -class CO2ReportHandler(tornado.web.RequestHandler): - def set_default_headers(self): - self.set_header("Access-Control-Allow-Origin", "*") - self.set_header("Access-Control-Allow-Headers", "x-requested-with") - self.set_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") - +class CO2ReportHandler(BaseReportHandler): def post(self): try: form_data = json.loads(self.request.body) diff --git a/caimira/src/caimira/calculator/models/models.py b/caimira/src/caimira/calculator/models/models.py index 9af2a8cf..0cd151dd 100644 --- a/caimira/src/caimira/calculator/models/models.py +++ b/caimira/src/caimira/calculator/models/models.py @@ -807,7 +807,7 @@ class SimplePopulation: number: typing.Union[int, IntPiecewiseConstant] #: The times in which the people are in the room. - presence: typing.Union[None, Interval] + presence: typing.Optional[Interval] #: The physical activity being carried out by the people. activity: Activity diff --git a/caimira/src/caimira/calculator/validators/co2/co2_validator.py b/caimira/src/caimira/calculator/validators/co2/co2_validator.py index 36ed80fa..e0d67f13 100644 --- a/caimira/src/caimira/calculator/validators/co2/co2_validator.py +++ b/caimira/src/caimira/calculator/validators/co2/co2_validator.py @@ -193,8 +193,7 @@ def ventilation_transition_times(self) -> typing.Tuple[float]: vent_states.append(last_time_from_input) return tuple(vent_states) - def build_model(self, size=None) -> models.CO2DataModel: # type: ignore - size = size or self.data_registry.monte_carlo['sample_size'] + def build_model(self, sample_size = None) -> models.CO2DataModel: # Build a simple infected and exposed population for the case when presence # intervals and number of people are dynamic. Activity type is not needed. if self.occupancy_format == 'dynamic': diff --git a/caimira/src/caimira/calculator/validators/form_validator.py b/caimira/src/caimira/calculator/validators/form_validator.py index 074ebf20..fe2e58dc 100644 --- a/caimira/src/caimira/calculator/validators/form_validator.py +++ b/caimira/src/caimira/calculator/validators/form_validator.py @@ -206,7 +206,7 @@ def get_activity_mins(population): def validate(self): raise NotImplementedError("Subclass must implement") - def build_model(self, sample_size=None): + def build_model(self, sample_size: typing.Optional[int] = None): raise NotImplementedError("Subclass must implement") def _compute_breaks_in_interval(self, start, finish, n_breaks, duration) -> models.BoundarySequence_t: diff --git a/cern_caimira/pyproject.toml b/cern_caimira/pyproject.toml index 8eedd26d..0ca89383 100644 --- a/cern_caimira/pyproject.toml +++ b/cern_caimira/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cern-caimira" -version = "4.17.1a1" +version = "4.17.2" description = "CAiMIRA - CERN Airborne Model for Indoor Risk Assessment" license = { text = "Apache-2.0" } authors = [ diff --git a/cern_caimira/src/cern_caimira/apps/calculator/static/js/co2_form.js b/cern_caimira/src/cern_caimira/apps/calculator/static/js/co2_form.js index 9f491418..60371d9b 100644 --- a/cern_caimira/src/cern_caimira/apps/calculator/static/js/co2_form.js +++ b/cern_caimira/src/cern_caimira/apps/calculator/static/js/co2_form.js @@ -392,11 +392,6 @@ function plotCO2Data(url) { function submitFittingAlgorithm(url) { if (validateCO2Form()) { - // Disable all the ventilation inputs - $("#fitting_ventilation_states, [name=fitting_ventilation_type]").prop( - "disabled", - true - ); // Disable room capacity input $("#room_capacity").prop( "disabled",