From 868215705c41ff91cfdf6023d91d59360edf5852 Mon Sep 17 00:00:00 2001 From: JeaDylan <101608960+JeaDylan@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:43:16 +0200 Subject: [PATCH] WeatherParameters: New data in model+db to setup default parameters --- .../WeatherParameters/WeatherParameters.py | 112 ++++++++++++++++++ .../WeatherParametersList.py | 72 ++++++++++- src/Model/test_Model.py | 21 ++++ src/View/WeatherParameters/TableDefault.py | 12 +- src/View/WeatherParameters/UndoCommand.py | 6 + src/View/WeatherParameters/Window.py | 15 ++- 6 files changed, 230 insertions(+), 8 deletions(-) diff --git a/src/Model/WeatherParameters/WeatherParameters.py b/src/Model/WeatherParameters/WeatherParameters.py index ce6f34c1..72dd30e5 100644 --- a/src/Model/WeatherParameters/WeatherParameters.py +++ b/src/Model/WeatherParameters/WeatherParameters.py @@ -154,6 +154,93 @@ class Data(SQLSubModel): self._data[key] = self._types[key](value) +class WeatherParametersDefault(SQLSubModel): + def __init__(self, type: str, value: float = 0.0, + id: int = -1, status=None, owner_scenario=-1): + super(WeatherParametersDefault, self).__init__( + id=id, status=status, + owner_scenario=owner_scenario + ) + + self._type = type + self._value = float(value) + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + status = data['status'] + scenario = data["scenario"] + loaded = data['loaded_pid'] + + if scenario is None: + return new + + table = execute( + "SELECT pamhyr_id, deleted, type, value, scenario " + + "FROM weather_parameters_default " + + f"WHERE scenario = {scenario.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + ) + + for row in table: + it = iter(row) + + pid = next(it) + deleted = next(it) + type = next(it) + value = next(it) + owner_scenario = next(it) + + wp_default = cls( + type=type, + value=value, + id=pid, + status=status, + owner_scenario=owner_scenario + ) + if deleted: + wp_default.set_as_deleted() + + loaded.add(pid) + new.append(wp_default) + + data["scenario"] = scenario.parent + new += cls._db_load(execute, data) + data["scenario"] = scenario + + return new + + def _db_save(self, execute, data=None): + if not self.must_be_saved(): + return True + + execute( + "INSERT INTO " + + "weather_parameters_default(" + + "pamhyr_id, deleted, type, value, scenario) " + + "VALUES (" + + f"{self.id}, {self._db_format(self.is_deleted())}, " + + f"'{self._db_format(self._type)}', {self._value}, " + + f"{self._status.scenario_id}" + + ")" + ) + + return True + + @property + def type(self): + return self._type + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + self._value = float(value) + self.modified() + + class WeatherParameters(SQLSubModel): _sub_classes = [Data] @@ -196,6 +283,18 @@ class WeatherParameters(SQLSubModel): ) """) + execute(f""" + CREATE TABLE weather_parameters_default{ext}( + {cls.create_db_add_pamhyr_id()}, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + type TEXT NOT NULL, + value FLOAT NOT NULL, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + primary KEY(pamhyr_id, scenario) + ) + """) + if ext == "_tmp": return True @@ -245,6 +344,19 @@ class WeatherParameters(SQLSubModel): cls._db_create(execute) created = True + if not cls.is_table_exists(execute, "weather_parameters_default"): + execute(f""" + CREATE TABLE weather_parameters_default( + {cls.create_db_add_pamhyr_id()}, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + type TEXT NOT NULL, + value FLOAT NOT NULL, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + primary KEY(pamhyr_id, scenario) + ) + """) + if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/WeatherParameters/WeatherParametersList.py b/src/Model/WeatherParameters/WeatherParametersList.py index a7e2f215..4e5c932d 100644 --- a/src/Model/WeatherParameters/WeatherParametersList.py +++ b/src/Model/WeatherParameters/WeatherParametersList.py @@ -22,7 +22,10 @@ from tools import trace, timer from Model.Tools.PamhyrListExt import PamhyrModelList from Model.Except import NotImplementedMethodeError -from Model.WeatherParameters.WeatherParameters import WeatherParameters +from Model.WeatherParameters.WeatherParameters import ( + WeatherParameters, + WeatherParametersDefault, +) from Model.WeatherParameters.WeatherParametersTypes import ( NotDefined, WeatherParam, AirTemperature, SpecificHumidity, GlobalRadiation, ReferenceWindSpeed, GroundwaterTemperature, @@ -34,6 +37,14 @@ class WeatherParametersList(PamhyrModelList): _sub_classes = [ WeatherParameters, ] + _default_types = [ + "AT", "SH", "GR", "RWS", "GT", "GFR", "ALB", "SC", "CCF" + ] + + def __init__(self, status=None): + super(WeatherParametersList, self).__init__(status=status) + self._default = [] + self._init_missing_default_values() @classmethod def _db_load(cls, execute, data=None): @@ -46,6 +57,13 @@ class WeatherParametersList(PamhyrModelList): execute, data ) + data['loaded_pid'] = set() + new._default = WeatherParametersDefault._db_load( + execute, data + ) + new._init_missing_default_values() + new._sort_default_values() + return new def _db_save(self, execute, data=None): @@ -57,6 +75,10 @@ class WeatherParametersList(PamhyrModelList): "DELETE FROM weather_parameters_data " + f"WHERE scenario = {self._status.scenario_id}" ) + execute( + "DELETE FROM weather_parameters_default " + + f"WHERE scenario = {self._status.scenario_id}" + ) if data is None: data = {} @@ -64,8 +86,41 @@ class WeatherParametersList(PamhyrModelList): for wp in self._lst: wp._db_save(execute, data=data) + for wp_default in self._default: + wp_default._db_save(execute, data=data) + return True + def _data_traversal(self, + predicate=lambda obj, data: True, + modifier=lambda obj, data: None, + data={}): + if predicate(self, data): + modifier(self, data) + + for el in self._lst: + el._data_traversal(predicate, modifier, data) + + for el in self._default: + el._data_traversal(predicate, modifier, data) + + def _init_missing_default_values(self): + existing = [wp_default.type for wp_default in self.default] + for type in self._default_types: + if type not in existing: + self._default.append( + WeatherParametersDefault(type=type, status=self._status) + ) + + self._sort_default_values() + + def _sort_default_values(self): + self._default.sort( + key=lambda wp_default: self._default_types.index(wp_default.type) + if wp_default.type in self._default_types + else len(self._default_types) + ) + def new(self, index, type="ND"): if type == "AT": n = AirTemperature(status=self._status) @@ -94,3 +149,18 @@ class WeatherParametersList(PamhyrModelList): @property def Weather_Parameters_List(self): return self.lst + + @property + def default(self): + return list( + filter( + lambda el: not el.is_deleted(), + self._default + ) + ) + + def default_for_type(self, type): + for wp_default in self.default: + if wp_default.type == type: + return wp_default + return None diff --git a/src/Model/test_Model.py b/src/Model/test_Model.py index c9d1835b..a88378ce 100644 --- a/src/Model/test_Model.py +++ b/src/Model/test_Model.py @@ -62,6 +62,27 @@ class StudyTestCase(unittest.TestCase): study = Study.new("foo", "bar") self.assertNotEqual(study.river, None) + def test_save_open_study_weather_parameters_default(self): + study = Study.new("foo", "bar") + defaults = study.river.weather_parameters.default + + self.assertEqual(len(defaults), 9) + defaults[0].value = 12.5 + + dir = tempfile.mkdtemp() + f = os.path.join(dir, "foo.pamhyr") + + study.filename = f + study.save() + study.close() + + study = Study.open(f) + defaults = study.river.weather_parameters.default + + self.assertEqual(len(defaults), 9) + self.assertEqual(defaults[0].type, "AT") + self.assertEqual(defaults[0].value, 12.5) + class StudyScenarioTestCase(unittest.TestCase): def test_create_study(self): diff --git a/src/View/WeatherParameters/TableDefault.py b/src/View/WeatherParameters/TableDefault.py index c95560d6..8c472483 100644 --- a/src/View/WeatherParameters/TableDefault.py +++ b/src/View/WeatherParameters/TableDefault.py @@ -56,12 +56,14 @@ class WeatherParametersTableDefaultModel(PamhyrTableModel): column = index.column() if self._headers[column] == "name": - return self._data[row] + if self._opt_data is not None: + return self._opt_data[row] + return self._data[row].type elif self._headers[column] == "value": - n = 0.0 - if n is None: + value = self._data[row].value + if value is None: return self._trad['not_associated'] - return n + return value return QVariant() @@ -73,7 +75,7 @@ class WeatherParametersTableDefaultModel(PamhyrTableModel): column = index.column() try: - if self._headers[column] is not None: + if self._headers[column] == "value": self._undo.push( SetCommand( self._data, row, self._headers[column], value diff --git a/src/View/WeatherParameters/UndoCommand.py b/src/View/WeatherParameters/UndoCommand.py index 2be40ae8..18727d99 100644 --- a/src/View/WeatherParameters/UndoCommand.py +++ b/src/View/WeatherParameters/UndoCommand.py @@ -41,6 +41,8 @@ class SetCommand(QUndoCommand): if self._column == "name": self._old = self._data[self._row].name + elif self._column == "value": + self._old = self._data[self._row].value elif self._column == "concentration": self._old = self._data[self._row].concentration elif self._column == "eg": @@ -59,6 +61,8 @@ class SetCommand(QUndoCommand): def undo(self): if self._column == "name": self._data[self._row].name = self._old + elif self._column == "value": + self._data[self._row].value = self._old elif self._column == "concentration": self._data[self._row].concentration = self._old elif self._column == "eg": @@ -71,6 +75,8 @@ class SetCommand(QUndoCommand): def redo(self): if self._column == "name": self._data[self._row].name = self._new + elif self._column == "value": + self._data[self._row].value = self._new elif self._column == "concentration": self._data[self._row].concentration = self._new elif self._column == "eg": diff --git a/src/View/WeatherParameters/Window.py b/src/View/WeatherParameters/Window.py index e9f99b58..9fac4eba 100644 --- a/src/View/WeatherParameters/Window.py +++ b/src/View/WeatherParameters/Window.py @@ -131,7 +131,7 @@ class WeatherParametersWindow(PamhyrWindow): self.table_default = self.find(QTableView, f"tableView") if self._study.is_editable(): - editable_headers = self._trad.get_dict("table_headers") + editable_headers = ["value"] else: editable_headers = [] @@ -140,7 +140,8 @@ class WeatherParametersWindow(PamhyrWindow): table_headers=self._trad.get_dict("table_headers"), editable_headers=editable_headers, delegates={}, - data=self.weather_params, + data=self._study.river.weather_parameters.default, + opt_data=self.weather_params, undo=self._undo_stack, trad=self._trad ) @@ -210,6 +211,16 @@ class WeatherParametersWindow(PamhyrWindow): ) self.table_spec.scrollTo(index) + default_selection = self.table_default.selectionModel() + default_index = self.table_default.model().index(0, 0) + default_selection.select( + default_index, + QItemSelectionModel.Rows | + QItemSelectionModel.ClearAndSelect | + QItemSelectionModel.Select + ) + self.table_default.scrollTo(default_index) + def on_table_default_selection_changed(self, selected, deselected): rows = self.table_default.selectionModel().selectedRows() if len(rows) == 0: