diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py
index 5445e2e2..ea869fec 100644
--- a/caimira/apps/calculator/__init__.py
+++ b/caimira/apps/calculator/__init__.py
@@ -359,14 +359,13 @@ class CO2Data(BaseRequestHandler):
Returns a list of tuples containing (index, X-axis value) for the detected significant changes.
"""
- times = CO2_data['times']
- CO2_values = CO2_data['CO2']
+ times: list = CO2_data['times']
+ CO2_values: list = CO2_data['CO2']
if len(times) != len(CO2_values):
raise ValueError("times and CO2 values must have the same length.")
- # Convert the input lists to numpy arrays for use with the ruptures library
- times_np = np.array(times)
+ # Convert the input list to a numpy array for use with the ruptures library
CO2_np = np.array(CO2_values)
# Define the model for change point detection (Radial Basis Function kernel)
@@ -378,7 +377,14 @@ class CO2Data(BaseRequestHandler):
# Predict change points using the Pelt algorithm with a penalty value of 15
result = algo.predict(pen=15)
- return [times_np[idx] for idx in result[:-1]]
+ # Find local minima and maxima
+ segments = np.split(np.arange(len(CO2_values)), result)
+ merged_segments = [np.hstack((segments[i], segments[i + 1])) for i in range(len(segments) - 1)]
+ result_set = set()
+ for segment in merged_segments[:-2]:
+ result_set.add(times[CO2_values.index(min(CO2_np[segment]))])
+ result_set.add(times[CO2_values.index(max(CO2_np[segment]))])
+ return list(result_set)
def generate_ventilation_plot(self, CO2_data: dict, transition_times: typing.Optional[list] = None, ventilation_values: typing.Optional[list] = None):
times = CO2_data['times']
@@ -411,7 +417,9 @@ class CO2Data(BaseRequestHandler):
return
if endpoint.rstrip('/') == 'plot':
- self.finish({'CO2_plot': self.generate_ventilation_plot(form.CO2_data, self.find_change_points_with_pelt(form.CO2_data))})
+ transition_times = self.find_change_points_with_pelt(form.CO2_data)
+ self.finish({'CO2_plot': self.generate_ventilation_plot(form.CO2_data, transition_times),
+ 'transition_times': [round(el, 2) for el in transition_times]})
else:
executor = loky.get_reusable_executor(
max_workers=self.settings['handler_worker_pool_size'],
diff --git a/caimira/apps/calculator/co2_model_generator.py b/caimira/apps/calculator/co2_model_generator.py
index 1a935577..0187535b 100644
--- a/caimira/apps/calculator/co2_model_generator.py
+++ b/caimira/apps/calculator/co2_model_generator.py
@@ -190,7 +190,9 @@ class CO2FormData(model_generator.FormData):
def ventilation_transition_times(self) -> typing.Tuple[float, ...]:
# Check what type of ventilation is considered for the fitting
if self.fitting_ventilation_type == 'fitting_natural_ventilation':
- return tuple(self.fitting_ventilation_states)
+ vent_states = self.fitting_ventilation_states
+ vent_states.append(self.CO2_data['times'][-1])
+ return tuple(vent_states)
else:
return tuple((self.CO2_data['times'][0], self.CO2_data['times'][-1]))
diff --git a/caimira/apps/calculator/static/js/co2_form.js b/caimira/apps/calculator/static/js/co2_form.js
index 01e073f0..ddbbfd36 100644
--- a/caimira/apps/calculator/static/js/co2_form.js
+++ b/caimira/apps/calculator/static/js/co2_form.js
@@ -215,10 +215,10 @@ function validateCO2Form() {
const max_presence_time = Math.max(elapsed_time_infected, elapsed_time_exposed);
const max_transition_time = parsedValue[parsedValue.length - 1] * 60;
- if (max_transition_time < max_presence_time) {
+ if (max_transition_time > max_presence_time) {
insertErrorFor(
$("#DIVCO2_fitting_result"),
- `The last transition time (${parsedValue[parsedValue.length - 1]}) should be after the last presence time (${max_presence_time / 60}).
`
+ `The last transition time (${parsedValue[parsedValue.length - 1]}) should be before the last presence time (${max_presence_time / 60}).
`
);
submit = false;
}
@@ -297,6 +297,7 @@ function formatCO2DataForm(CO2_data_form) {
let CO2_mapping = {};
CO2_data_form.map((el) => {
let element = $(`[name=${el}]`).first();
+
// Validate checkboxes
if (element.prop('type') == "checkbox") {
CO2_mapping[element.attr('name')] = String(+element.prop('checked'));
@@ -306,7 +307,9 @@ function formatCO2DataForm(CO2_data_form) {
CO2_mapping[element.attr('name')] = $(
`[name=${element.attr('name')}]:checked`
).first().val();
- else CO2_mapping[element.attr('name')] = element.val();
+ else {
+ CO2_mapping[element.attr('name')] = element.val();
+ }
});
return CO2_mapping;
}
@@ -320,9 +323,10 @@ function plotCO2Data(url) {
}).then((response) =>
response
.json()
- .then((json_response) =>
+ .then((json_response) => {
$("#CO2_data_plot").attr("src", json_response["CO2_plot"])
- )
+ $("#fitting_ventilation_states").val(`[${json_response["transition_times"]}]`)
+ })
.then($("#DIVCO2_fitting_to_submit").show())
.catch((error) => console.log(error))
);