diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd13feb..1f5d45b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 4.17.4 (November 05, 2024) + +## Features Added +- CO2 fitting algorithm post-review changes + +## Bug Fixes +- N/A + # 4.17.3 (October 16, 2024) ## Features Added diff --git a/caimira/pyproject.toml b/caimira/pyproject.toml index efc2e958..9f82c5d5 100644 --- a/caimira/pyproject.toml +++ b/caimira/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "caimira" -version = "4.17.3" +version = "4.17.4" description = "CAiMIRA - CERN Airborne Model for Indoor Risk Assessment" license = { text = "Apache-2.0" } authors = [ diff --git a/caimira/src/caimira/calculator/models/monte_carlo/models.py b/caimira/src/caimira/calculator/models/monte_carlo/models.py index 9ee1d481..1c7c4a6e 100644 --- a/caimira/src/caimira/calculator/models/monte_carlo/models.py +++ b/caimira/src/caimira/calculator/models/monte_carlo/models.py @@ -129,8 +129,8 @@ def _build_mc_model(model: dataclass_instance) -> typing.Type[MCModelBase[_Model # Inject the runtime generated MC types into this module. for _model in _MODEL_CLASSES: - setattr(sys.modules[__name__], _model.__name__, _build_mc_model(_model)) + setattr(sys.modules[__name__], _model.__name__, _build_mc_model(_model)) # type: ignore # Make sure that each of the models is imported if you do a ``import *``. -__all__ = [_model.__name__ for _model in _MODEL_CLASSES] + ["MCModelBase"] +__all__ = [_model.__name__ for _model in _MODEL_CLASSES] + ["MCModelBase"] # type: ignore diff --git a/caimira/src/caimira/calculator/report/co2_report_data.py b/caimira/src/caimira/calculator/report/co2_report_data.py index 793c064f..4c7a0436 100644 --- a/caimira/src/caimira/calculator/report/co2_report_data.py +++ b/caimira/src/caimira/calculator/report/co2_report_data.py @@ -21,14 +21,15 @@ def build_initial_plot( [occupancy_transition_times[-1]] + ventilation_transition_times) - ventilation_plot: str = form.generate_ventilation_plot( - ventilation_transition_times=all_vent_transition_times, + vent_plot_img, vent_plot_data = form.generate_ventilation_plot( + ventilation_transition_times=ventilation_transition_times, occupancy_transition_times=occupancy_transition_times ) context = { - 'CO2_plot': ventilation_plot, 'transition_times': [round(el, 2) for el in all_vent_transition_times], + 'CO2_plot_img': vent_plot_img, + 'CO2_plot_data': vent_plot_data } return context @@ -52,9 +53,12 @@ def build_fitting_results( # predictive CO2 result based on the fitting results. context = dict(CO2model.CO2_fit_params()) + vent_plot_img, vent_plot_data = form.generate_ventilation_plot(ventilation_transition_times=ventilation_transition_times[:-1], + predictive_CO2=context['predictive_CO2']) + # Add the transition times and CO2 plot to the results. context['transition_times'] = ventilation_transition_times - context['CO2_plot'] = form.generate_ventilation_plot(ventilation_transition_times=ventilation_transition_times[:-1], - predictive_CO2=context['predictive_CO2']) + context['CO2_plot_img'] = vent_plot_img + context['CO2_plot_data'] = vent_plot_data return context diff --git a/caimira/src/caimira/calculator/validators/co2/co2_validator.py b/caimira/src/caimira/calculator/validators/co2/co2_validator.py index e0d67f13..43ea6b7a 100644 --- a/caimira/src/caimira/calculator/validators/co2/co2_validator.py +++ b/caimira/src/caimira/calculator/validators/co2/co2_validator.py @@ -105,7 +105,7 @@ def find_change_points(self) -> list: """ Perform change point detection using scipy library (find_peaks method) with rolling average of data. Incorporate existing state change candidates and adjust the result accordingly. - Returns a list of the detected ventilation state changes, discarding any occupancy state change. + Returns a list of the detected ventilation transition times, discarding any occupancy state change. """ times: list = self.CO2_data['times'] CO2_values: list = self.CO2_data['CO2'] @@ -147,33 +147,46 @@ def find_change_points(self) -> list: def generate_ventilation_plot(self, ventilation_transition_times: typing.Optional[list] = None, occupancy_transition_times: typing.Optional[list] = None, - predictive_CO2: typing.Optional[list] = None) -> str: + predictive_CO2: typing.Optional[list] = None): # Plot data (x-axis: times; y-axis: CO2 concentrations) times_values: list = self.CO2_data['times'] CO2_values: list = self.CO2_data['CO2'] fig = plt.figure(figsize=(7, 4), dpi=110) - plt.plot(times_values, CO2_values, label='Input CO₂') + plt.plot(times_values, CO2_values, label='CO₂ Data') + + # Add predictive CO2 + if (predictive_CO2): + plt.plot(times_values, predictive_CO2, label='Predictive CO₂') - # Add occupancy state changes: - if (occupancy_transition_times): - for i, time in enumerate(occupancy_transition_times): - plt.axvline(x = time, color = 'grey', linewidth=0.5, linestyle='--', label='Occupancy change (from input)' if i == 0 else None) - # Add ventilation state changes: + # Add ventilation transition times: if (ventilation_transition_times): for i, time in enumerate(ventilation_transition_times): if i == 0: - label = 'Ventilation change (detected)' if occupancy_transition_times else 'Ventilation state changes' + label = 'Ventilation transition times (suggestion)' if occupancy_transition_times else 'Ventilation transition times' else: label = None - plt.axvline(x = time, color = 'red', linewidth=0.5, linestyle='--', label=label) + plt.axvline(x = time, color = 'red', linewidth=1, linestyle='--', label=label) + + # Add occupancy changes (UI): + if (occupancy_transition_times): + for i, time in enumerate(occupancy_transition_times): + plt.axvline(x = time, color = 'grey', linewidth=1, linestyle='--', label='Occupancy change (from UI)' if i == 0 else None) - if (predictive_CO2): - plt.plot(times_values, predictive_CO2, label='Predictive CO₂') plt.xlabel('Time of day') plt.ylabel('Concentration (ppm)') plt.legend() - return img2base64(_figure2bytes(fig)) + + vent_plot_data = { + 'plot': img2base64(_figure2bytes(fig)), + 'times': times_values, + 'CO2': CO2_values, + 'occ_trans_time': occupancy_transition_times, + 'vent_trans_time': ventilation_transition_times, + 'predictive_CO2': predictive_CO2, + } + + return img2base64(_figure2bytes(fig)), vent_plot_data def population_present_changes(self, infected_presence: models.Interval, exposed_presence: models.Interval) -> typing.List[float]: state_change_times = set(infected_presence.transition_times()) diff --git a/caimira/tests/models/test_co2_concentration_model.py b/caimira/tests/models/test_co2_concentration_model.py index 44ba4db9..0bcf54c0 100644 --- a/caimira/tests/models/test_co2_concentration_model.py +++ b/caimira/tests/models/test_co2_concentration_model.py @@ -57,7 +57,7 @@ def test_integrated_concentration(simple_co2_conc_model): def test_find_change_points(scenario_data, room_volume, max_total_people, start, finish, state_changes, request): ''' Specific test of the find_change_points method. - Testing the ventilation state changes only. + Testing the ventilation transition times only. ''' CO2_form_model: CO2FormData = CO2FormData( CO2_data=request.getfixturevalue(scenario_data), diff --git a/cern_caimira/pyproject.toml b/cern_caimira/pyproject.toml index 80be5326..7c6ddc4e 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.3" +version = "4.17.4" 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 60371d9b..f28bd5cb 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 @@ -222,7 +222,7 @@ function validateCO2Form() { if (parsedValue.length <= 1) { insertErrorFor( $referenceNode, - `'${$ventilationStates.attr('name')}' must have more than one ventilation state change (at least the beggining and end of simulation time).
` + `'${$ventilationStates.attr('name')}' must have more than one ventilation transition time (at least the beginning and end of simulation time).
` ); submit = false; } @@ -297,9 +297,9 @@ function displayTransitionTimesHourFormat(start, stop) { function displayFittingData(json_response) { $("#DIVCO2_fitting_result").show(); - $("#CO2_data_plot").attr("src", json_response["CO2_plot"]); + $("#CO2_data_plot").attr("src", json_response["CO2_plot_img"]); // Not needed for the form submission - delete json_response["CO2_plot"]; + delete json_response["CO2_plot_img"]; delete json_response["predictive_CO2"]; // Convert nulls to empty strings in the JSON response if (json_response["room_capacity"] === null) json_response["room_capacity"] = ''; @@ -381,7 +381,7 @@ function plotCO2Data(url) { response .json() .then((json_response) => { - $("#CO2_data_plot").attr("src", json_response["CO2_plot"]) + $("#CO2_data_plot").attr("src", json_response["CO2_plot_img"]) $("#fitting_ventilation_states").val(`[${json_response["transition_times"]}]`) }) .then($("#DIVCO2_fitting_to_submit").show()) diff --git a/cern_caimira/src/cern_caimira/apps/templates/base/calculator.form.html.j2 b/cern_caimira/src/cern_caimira/apps/templates/base/calculator.form.html.j2 index dc5fb4d5..8410844c 100644 --- a/cern_caimira/src/cern_caimira/apps/templates/base/calculator.form.html.j2 +++ b/cern_caimira/src/cern_caimira/apps/templates/base/calculator.form.html.j2 @@ -349,7 +349,7 @@ The dashed lines are suggestions for the ventilation transition times

- +
?