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