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 += - "