diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py index 668a91bf..97b20fca 100644 --- a/caimira/apps/calculator/__init__.py +++ b/caimira/apps/calculator/__init__.py @@ -354,7 +354,6 @@ class CO2Data(BaseRequestHandler): async def post(self) -> None: requested_model_config = tornado.escape.json_decode(self.request.body) - try: form = co2_model_generator.CO2FormData.from_dict(requested_model_config) except Exception as err: @@ -375,7 +374,7 @@ class CO2Data(BaseRequestHandler): ) report = await asyncio.wrap_future(report_task) - def generate_image(transition_times: tuple, ventilation_values: tuple): + def generate_ventilation_plot(transition_times: tuple, ventilation_values: tuple): fig = plt.figure(figsize=(7, 4), dpi=110) plt.plot(form.CO2_data['times'], form.CO2_data['CO2']) for index, time in enumerate(transition_times[:-1]): @@ -387,7 +386,8 @@ class CO2Data(BaseRequestHandler): return fig result = dict(report.CO2_fit_params()) - result['CO2_plot'] = img2base64(_figure2bytes(generate_image(report.ventilation_transition_times, result['ventilation_values']))) + result['transition_times'] = report.ventilation_transition_times + result['CO2_plot'] = img2base64(_figure2bytes(generate_ventilation_plot(report.ventilation_transition_times, result['ventilation_values']))) self.finish(result) diff --git a/caimira/apps/calculator/co2_model_generator.py b/caimira/apps/calculator/co2_model_generator.py index f0341338..39e61d29 100644 --- a/caimira/apps/calculator/co2_model_generator.py +++ b/caimira/apps/calculator/co2_model_generator.py @@ -46,7 +46,7 @@ class CO2FormData: #: and the defaults in the html form must not be contradictory. _DEFAULTS: typing.ClassVar[typing.Dict[str, typing.Any]] = { 'CO2_data': '{}', - 'specific_breaks': '{}', # CHECK INTEGRATION WITH WHO + 'specific_breaks': '{}', 'exposed_coffee_break_option': 'coffee_break_0', 'exposed_coffee_duration': 5, 'exposed_finish': '17:30', @@ -89,8 +89,8 @@ class CO2FormData: form_data[key] = default_value for key, value in form_data.items(): - if key in model_generator._CAST_RULES_FORM_ARG_TO_NATIVE: - form_data[key] = model_generator._CAST_RULES_FORM_ARG_TO_NATIVE[key](value) + if key in _CAST_RULES_FORM_ARG_TO_NATIVE: + form_data[key] = _CAST_RULES_FORM_ARG_TO_NATIVE[key](value) if key not in cls._DEFAULTS: raise ValueError(f'Invalid argument "{html.escape(key)}" given') @@ -318,6 +318,33 @@ class CO2FormData: if self.ventilation_type == 'from_fitting' and self.window_opening_regime == 'windows_open_periodically': transition_times = sorted(models.PeriodicInterval(self.windows_frequency, self.windows_duration, min(self.infected_start, self.exposed_start)/60).transition_times()) - return tuple(filter(lambda x: x < last_present_time, transition_times)) + return tuple(filter(lambda x: x <= last_present_time, transition_times)) else: return tuple((min(self.infected_start, self.exposed_start)/60, max(self.infected_finish, self.exposed_finish)/60), ) # all day long + +#: Mapping of field name to a callable which can convert values from form +#: input (URL encoded arguments / string) into the correct type. +_CAST_RULES_FORM_ARG_TO_NATIVE: typing.Dict[str, typing.Callable] = {} + +#: Mapping of field name to callable which can convert native type to values +#: that can be encoded to URL arguments. +_CAST_RULES_NATIVE_TO_FORM_ARG: typing.Dict[str, typing.Callable] = {} + + +for _field in dataclasses.fields(CO2FormData): + if _field.type is minutes_since_midnight: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = model_generator.time_string_to_minutes + _CAST_RULES_NATIVE_TO_FORM_ARG[_field.name] = model_generator.time_minutes_to_string + elif _field.type is int: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = model_generator._safe_int_cast + elif _field.type is float: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = float + elif _field.type is bool: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = lambda v: v == '1' + _CAST_RULES_NATIVE_TO_FORM_ARG[_field.name] = int + elif _field.type is list: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = model_generator.string_to_list + _CAST_RULES_NATIVE_TO_FORM_ARG[_field.name] = model_generator.list_to_string + elif _field.type is dict: + _CAST_RULES_FORM_ARG_TO_NATIVE[_field.name] = model_generator.string_to_dict + _CAST_RULES_NATIVE_TO_FORM_ARG[_field.name] = model_generator.dict_to_string diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index c230e4a9..35ced634 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -22,7 +22,6 @@ DEFAULTS = { 'ceiling_height': 0., 'conditional_probability_plot': False, 'conditional_probability_viral_loads': False, - 'CO2_data': '{}', 'CO2_data_option': False, 'CO2_fitting_result': '{}', 'exposed_coffee_break_option': 'coffee_break_0', diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 58ddc8e8..97749fd2 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -37,7 +37,6 @@ class FormData: ceiling_height: float conditional_probability_plot: bool conditional_probability_viral_loads: bool - CO2_data: dict CO2_data_option: bool CO2_fitting_result: dict exposed_coffee_break_option: str diff --git a/caimira/apps/calculator/static/js/co2_form.js b/caimira/apps/calculator/static/js/co2_form.js index b9e7c50b..e5277f3f 100644 --- a/caimira/apps/calculator/static/js/co2_form.js +++ b/caimira/apps/calculator/static/js/co2_form.js @@ -1,5 +1,5 @@ // JS file to handle manipulation on CO2 Fitting Algorithm Dialog. -const CO2_data = [ +const CO2_data_form = [ 'CO2_data', 'specific_breaks', 'exposed_coffee_break_option', @@ -74,11 +74,11 @@ function displayJsonToHtmlTable(jsonData) { var row = jsonData[i]; if (i < 5) { htmlData += - "" + - row["Times"].toFixed(2) + - "" + - row["CO2"].toFixed(2) + - ""; + ` + ${row["Times"].toFixed(2)} + + ${row["CO2"].toFixed(2)} + `; } structure["times"].push(row["Times"]); structure["CO2"].push(row["CO2"]); @@ -115,8 +115,8 @@ function insertErrorFor(referenceNode, text) { function validate() { $('span.' + "error_text").remove(); let submit = true; - for (var i = 0; i < CO2_data.length; i++) { - let element = $(`[name=${CO2_data[i]}]`)[0]; + for (var i = 0; i < CO2_data_form.length; i++) { + let element = $(`[name=${CO2_data_form[i]}]`)[0]; if (element.value === '') { insertErrorFor($('#CO2_input_data_div'), `'${element.name}' must be defined.`); // raise error for total number and room volume. submit = false; @@ -128,10 +128,17 @@ function validate() { function display_fitting_data(json_response) { $("#DIV_CO2_fitting_result").show(); $("#CO2_data_plot").attr("src", json_response['CO2_plot']); - delete json_response['CO2_plot']; + // Not needed for the form submit + delete json_response['CO2_plot']; $("#CO2_fitting_result").val(JSON.stringify(json_response)); $("#exhalation_rate_fit").html('Exhalation rate: ' + String(json_response['exhalation_rate'].toFixed(2)) + ' m³/h'); - // $("#ventilation_rate_fit").html(json_response['ventilation_values']); + let ventilation_table = "TimeVentilation value (ACH)"; + json_response['ventilation_values'].map((val, index) => { + console.log(json_response['transition_times']) + let transition_times = `${(json_response['transition_times'][index]).toFixed(2)} - ${(json_response['transition_times'][index + 1]).toFixed(2)}` + ventilation_table += `${transition_times}${val}`; + }); + $("#ventilation_rate_fit").html(ventilation_table); $("#generate_fitting_data").html('Fit data'); $("#save_and_dismiss_dialog").show(); } @@ -139,7 +146,7 @@ function display_fitting_data(json_response) { function submit_fitting_algorithm(url) { if (validate()) { let CO2_mapping = {}; - CO2_data.map(el => { + CO2_data_form.map(el => { let element = $(`[name=${el}]`); // Validate radio buttons if (element.length != 1) CO2_mapping[element[0].name] = $(`[name=${element[0].name}]:checked`)[0].value @@ -147,7 +154,7 @@ function submit_fitting_algorithm(url) { }) $('#CO2_input_data_div').show(); $("#generate_fitting_data").html( - `Loading...` + `Loading...` ); $('#CO2_input_data').html(JSON.stringify(CO2_mapping, null, "\t")) fetch(url, { @@ -172,7 +179,3 @@ function clear_fitting_algorithm() { $('#CO2_data_no').click(); ventilation_from_fitting(false); } - -function dismiss_co2_dialog() { - $('#CO2_data_no').click(); -} \ No newline at end of file diff --git a/caimira/apps/calculator/static/js/form.js b/caimira/apps/calculator/static/js/form.js index d2b0dbf1..715808f0 100644 --- a/caimira/apps/calculator/static/js/form.js +++ b/caimira/apps/calculator/static/js/form.js @@ -505,7 +505,12 @@ function ventilation_from_fitting(condition_from_fitting) { else { // Select the URL ventilation option, if any (from back-navigation) var url = new URL(decodeURIComponent(window.location.href)); - let ventilation_from_url = url.searchParams.has('ventilation_type') ? url.searchParams.get('ventilation_type') : "no_ventilation"; + let ventilation_from_url; + if (url.searchParams.has('ventilation_type')) { + ventilation_from_url = url.searchParams.get('ventilation_type'); + if (ventilation_from_url == 'from_fitting') ventilation_from_url = 'no_ventilation'; + } + else ventilation_from_url = 'no_ventilation'; $(`input[type=radio][id=${ventilation_from_url}]`).prop('checked',true); $('#DIVopening_distance').after($('#window_opening_regime')); } @@ -968,16 +973,8 @@ $(document).ready(function () { // Handle default URL values if they are not explicitly defined. // Populate CO2 Fitting Algorithm Dialog - let CO2_data = url.searchParams.has('CO2_data') ? url.searchParams.get('CO2_data') : null; - if (CO2_data) { - let CO2_inputs = JSON.parse(CO2_data); - let input_for_table = []; - for (let i = 0; i < CO2_inputs['times'].length; i++) { - input_for_table.push({'Times': CO2_inputs['times'][i], 'CO2': CO2_inputs['CO2'][i]}); - }; - displayJsonToHtmlTable(input_for_table); - submit_fitting_algorithm(`${$('#url_prefix').data().calculator_prefix}/co2-fit`); - } + let CO2_data = url.searchParams.has('CO2_fitting_result') ? url.searchParams.get('CO2_fitting_result') : null; + if (CO2_data) display_fitting_data(JSON.parse(CO2_data)); // Populate primary vaccine dropdown $("#vaccine_type option").remove(); diff --git a/caimira/apps/templates/base/calculator.form.html.j2 b/caimira/apps/templates/base/calculator.form.html.j2 index bb04819b..7c6cb522 100644 --- a/caimira/apps/templates/base/calculator.form.html.j2 +++ b/caimira/apps/templates/base/calculator.form.html.j2 @@ -702,7 +702,7 @@