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)) );