From 9f53ad685e50b01897ceb9a4f9d0fb4fc9a7f783 Mon Sep 17 00:00:00 2001
From: Youcef AOUAD <youcef.aouad@inrae.fr>
Date: Fri, 21 Jun 2024 11:39:04 +0200
Subject: [PATCH] d90 Model

---
 src/Model/D90AdisTS/D90AdisTS.py              | 201 ++++++++++++++++++
 src/Model/D90AdisTS/D90AdisTSList.py          |  62 ++++++
 src/Model/D90AdisTS/D90AdisTSSpec.py          | 200 +++++++++++++++++
 .../InitialConditionsAdisTSList.py            |   2 +-
 .../InitialConditionsAdisTSSpec.py            |   2 +-
 src/Model/River.py                            |  10 +
 src/View/MainWindow.py                        |  15 ++
 src/View/ui/MainWindow.ui                     |   6 +
 tests_cases/Enlargement/Enlargement.pamhyr    | Bin 208896 -> 217088 bytes
 9 files changed, 496 insertions(+), 2 deletions(-)
 create mode 100644 src/Model/D90AdisTS/D90AdisTS.py
 create mode 100644 src/Model/D90AdisTS/D90AdisTSList.py
 create mode 100644 src/Model/D90AdisTS/D90AdisTSSpec.py

diff --git a/src/Model/D90AdisTS/D90AdisTS.py b/src/Model/D90AdisTS/D90AdisTS.py
new file mode 100644
index 00000000..e4953cd8
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTS.py
@@ -0,0 +1,201 @@
+# D90AdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import reduce
+
+from tools import trace, timer, old_pamhyr_date_to_timestamp
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+from Model.D90AdisTS.D90AdisTSSpec import D90AdisTSSpec
+
+logger = logging.getLogger()
+
+class D90AdisTS(SQLSubModel):
+    _sub_classes = [
+        D90AdisTSSpec,
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "default",
+                 status=None):
+        super(D90AdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = D90AdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._name = name
+        self._d90 = None
+        self._enabled = True
+        self._data = []
+
+        D90AdisTS._id_cnt = max(
+            D90AdisTS._id_cnt + 1,
+            self.id
+        )
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+                  CREATE TABLE d90_adists(
+                    id INTEGER NOT NULL PRIMARY KEY,
+                    name TEXT NOT NULL,
+                    d90 REAL NOT NULL,
+                    enabled BOOLEAN NOT NULL,
+                  )
+                """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, name, d90, enabled " +
+            "FROM d90_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                d90_id = row[0]
+                name = row[1]
+                d90 = row[2]
+                enabled = (row[3] == 1)
+
+                D90 = cls(
+                    id=d90_id,
+                    name=name,
+                    status=data['status']
+                )
+
+                D90.d90 = d90
+                D90.enabled = enabled
+
+                data['d90_default_id'] = d90_id
+                D90._data = D90AdisTSSpec._db_load(execute, data)
+
+                new.append(D90)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute(f"DELETE FROM d90_adists WHERE id = {self.id}")
+
+        d90 = -1.
+        if self.d90 is not None:
+            d90 = self.d90
+
+        sql = (
+            "INSERT INTO " +
+            "d90_adists(" +
+            "id, name, d90, enabled" +
+            ") " +
+            "VALUES (" +
+            f"{self.id}, '{self._db_format(self._name)}', " +
+            f"{d90}, {self._enabled}" +
+            ")"
+        )
+
+        execute(sql)
+
+        data['d90_default_id'] = self.id
+        execute(
+            "DELETE FROM d90_spec " +
+            f"WHERE d90_default = {self.id}"
+        )
+
+        for d90_spec in self._data:
+            d90_spec._db_save(execute, data)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+        self._status.modified()
+
+    @property
+    def d90(self):
+        return self._d90
+
+    @d90.setter
+    def d90(self, d90):
+        self._d90 = d90
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+    def new(self, index):
+        n = D90AdisTSSpec(status=self._status)
+        self._data.insert(index, n)
+        self._status.modified()
+        return n
+
+    def delete(self, data):
+        self._data = list(
+            filter(
+                lambda x: x not in data,
+                self._data
+            )
+        )
+        self._status.modified()
+
+    def delete_i(self, indexes):
+        for ind in indexes:
+            del self._data[ind]
+        self._status.modified()
+
+    def insert(self, index, data):
+        self._data.insert(index, data)
+        self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/D90AdisTS/D90AdisTSList.py b/src/Model/D90AdisTS/D90AdisTSList.py
new file mode 100644
index 00000000..a9c8e7f5
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTSList.py
@@ -0,0 +1,62 @@
+# D90AdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.D90AdisTS.D90AdisTS import D90AdisTS
+
+class D90AdisTSList(PamhyrModelList):
+    _sub_classes = [
+        D90AdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = D90AdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM d90_adists")
+
+        if data is None:
+            data = {}
+
+        for d90 in self._lst:
+            d90._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index):
+        n = D90AdisTS(status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+
+
+
diff --git a/src/Model/D90AdisTS/D90AdisTSSpec.py b/src/Model/D90AdisTS/D90AdisTSSpec.py
new file mode 100644
index 00000000..d1ab2c8d
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTSSpec.py
@@ -0,0 +1,200 @@
+# D90AdisTSSpec.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class D90AdisTSSpec(SQLSubModel):
+    _sub_classes = [
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "",
+                 status=None):
+        super(D90AdisTSSpec, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = D90AdisTSSpec._id_cnt
+        else:
+            self.id = id
+
+        self._name_section = name
+        self._reach = None
+        self._start_kp = None
+        self._end_kp = None
+        self._d90 = None
+        self._enabled = True
+
+        D90AdisTSSpec._id_cnt = max(D90AdisTSSpec._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+              CREATE TABLE d90_spec(
+                id INTEGER NOT NULL PRIMARY KEY,
+                d90_default INTEGER NOT NULL,
+                name TEXT NOT NULL,
+                reach INTEGER NOT NULL,
+                start_kp REAL NOT NULL,
+                end_kp REAL NOT NULL,
+                d90 REAL NOT NULL,
+                enabled BOOLEAN NOT NULL,
+                FOREIGN KEY(d90_default) REFERENCES d90_adists(id),
+                FOREIGN KEY(reach) REFERENCES river_reach(id)
+              )
+            """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, d90_default, name, reach, start_kp, end_kp, " +
+            "d90, enabled " +
+            "FROM d90_spec " +
+            f"WHERE d90_default = {data['d90_default_id']} "
+        )
+
+        for row in table:
+            id            = row[0]
+            name          = row[2]
+            reach         = row[3]
+            start_kp      = row[4]
+            end_kp        = row[5]
+            d90 = row[6]
+            enabled       = (row[7] == 1)
+
+            new_spec = cls(
+                id=id,
+                name=name,
+                status=data['status']
+            )
+
+            new_spec.reach = reach
+            new_spec.start_kp = start_kp
+            new_spec.end_kp = end_kp
+            new_spec.d90 = d90
+            new_spec.enabled = enabled
+
+            new.append(new_spec)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        d90_default = data['d90_default_id']
+
+        sql = (
+            "INSERT INTO " +
+            "d90_spec(id, d90_default, name, reach, " +
+            "start_kp, end_kp, d90, enabled) " +
+            "VALUES (" +
+            f"{self.id}, " +
+            f"{d90_default}, " +
+            f"'{self._db_format(self._name_section)}', " +
+            f"{self._reach}, " +
+            f"{self._start_kp}, " +
+            f"{self._end_kp}, " +
+            f"{self._d90}, " +
+            f"{self._enabled}" +
+            ")"
+        )
+        execute(sql)
+
+        return True
+
+    @property
+    def name(self):
+        return self._name_section
+
+    @name.setter
+    def name(self, name):
+        self._name_section = name
+        self._status.modified()
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @reach.setter
+    def reach(self, reach):
+        self._reach = reach
+        self._status.modified()
+
+    @property
+    def start_kp(self):
+        return self._start_kp
+
+    @start_kp.setter
+    def start_kp(self, start_kp):
+        self._start_kp = start_kp
+        self._status.modified()
+
+    @property
+    def end_kp(self):
+        return self._end_kp
+
+    @end_kp.setter
+    def end_kp(self, end_kp):
+        self._end_kp = end_kp
+        self._status.modified()
+
+    @property
+    def d90(self):
+        return self._d90
+
+    @d90.setter
+    def d90(self, concentration):
+        self._d90 = d90
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py
index e45aba04..8ff24cf2 100644
--- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py
+++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py
@@ -41,7 +41,7 @@ class InitialConditionsAdisTSList(PamhyrModelList):
         return new
 
     def _db_save(self, execute, data=None):
-        execute("DELETE FROM initial_conditions")
+        execute("DELETE FROM initial_conditions_adists")
 
         if data is None:
             data = {}
diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py
index 45286f63..81fad351 100644
--- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py
+++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py
@@ -70,7 +70,7 @@ class ICAdisTSSpec(SQLSubModel):
                 ed REAL NOT NULL,
                 rate REAL NOT NULL,
                 enabled BOOLEAN NOT NULL,
-                FOREIGN KEY(ic_default) REFERENCES initial_conditions(id),
+                FOREIGN KEY(ic_default) REFERENCES initial_conditions_adists(id),
                 FOREIGN KEY(reach) REFERENCES river_reach(id)
               )
             """)
diff --git a/src/Model/River.py b/src/Model/River.py
index 5eabb932..4cc2c5e4 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -50,6 +50,7 @@ from Model.Pollutants.PollutantsList import PollutantsList
 from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList import InitialConditionsAdisTSList
 from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList import BoundaryConditionsAdisTSList
 from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import LateralContributionsAdisTSList
+from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
 
 
 class RiverNode(Node, SQLSubModel):
@@ -239,6 +240,7 @@ class River(Graph, SQLSubModel):
         InitialConditionsAdisTSList,
         BoundaryConditionsAdisTSList,
         LateralContributionsAdisTSList,
+        D90AdisTSList,
     ]
 
     def __init__(self, status=None):
@@ -267,6 +269,7 @@ class River(Graph, SQLSubModel):
         self._InitialConditionsAdisTS = InitialConditionsAdisTSList(status=self._status)
         self._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList(status=self._status)
         self._LateralContributionsAdisTS = LateralContributionsAdisTSList(status=self._status)
+        self._D90AdisTS = D90AdisTSList(status=self._status)
 
     @classmethod
     def _db_create(cls, execute):
@@ -353,6 +356,8 @@ class River(Graph, SQLSubModel):
 
         new._LateralContributionsAdisTS = LateralContributionsAdisTSList._db_load(execute, data)
 
+        new._D90AdisTS = D90AdisTSList._db_load(execute, data)
+
         return new
 
     def _db_save(self, execute, data=None):
@@ -377,6 +382,7 @@ class River(Graph, SQLSubModel):
         objs.append(self._InitialConditionsAdisTS)
         objs.append(self._BoundaryConditionsAdisTS)
         objs.append(self._LateralContributionsAdisTS)
+        objs.append(self._D90AdisTS)
 
         self._save_submodel(execute, objs, data)
         return True
@@ -520,6 +526,10 @@ Last export at: @date."""
     def lateral_contributions_adists(self):
         return self._LateralContributionsAdisTS
 
+    @property
+    def d90_adists(self):
+        return self._D90AdisTS
+
     def get_params(self, solver):
         if solver in self._parameters:
             return self._parameters[solver]
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index f94c4d07..12d9af67 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -122,6 +122,7 @@ define_model_action = [
     "action_menu_boundary_conditions_sediment",
     "action_menu_rep_additional_lines", "action_menu_output_kp",
     "action_menu_run_adists", "action_menu_pollutants",
+    "action_menu_d90",
 ]
 
 action = (
@@ -235,6 +236,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         """
         actions = {
             # Menu action
+            "action_menu_d90": self.open_d90,
             "action_menu_pollutants": self.open_pollutants,
             "action_menu_run_adists":self.run_solver_adists,
             "action_menu_output_kp": self.open_output_kp_adists,
@@ -866,6 +868,19 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
     # SUB WINDOWS #
     ###############
 
+    def open_d90(self):
+        if self.sub_window_exists(
+            PollutantsWindow,
+            data=[self._study, None]
+        ):
+            return
+
+        Pollutants = PollutantsWindow(
+            study=self._study,
+            parent=self
+        )
+        Pollutants.show()
+
     def open_pollutants(self):
         if self.sub_window_exists(
             PollutantsWindow,
diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui
index 20ec6283..842e220a 100644
--- a/src/View/ui/MainWindow.ui
+++ b/src/View/ui/MainWindow.ui
@@ -219,6 +219,7 @@
     </property>
     <addaction name="action_menu_output_kp"/>
     <addaction name="action_menu_pollutants"/>
+    <addaction name="action_menu_d90"/>
    </widget>
    <addaction name="menu_File"/>
    <addaction name="menu_network"/>
@@ -761,6 +762,11 @@
     <string>Pollutants</string>
    </property>
   </action>
+  <action name="action_menu_d90">
+   <property name="text">
+    <string>D90</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections>
diff --git a/tests_cases/Enlargement/Enlargement.pamhyr b/tests_cases/Enlargement/Enlargement.pamhyr
index 7cbb7e4ffefc175307429c2d8af87987164093b8..9aa6c32eaddadc52309022f8de7ef26e5bb9a78a 100644
GIT binary patch
delta 3021
zcmaKuO>EOv9Kh{3b`og%nx(8Ouj$8HCn?b2G)>!37<7yc#`nfH*jMJ-PHP$`ne9ep
z7t;%uX%eUhE)(iur=12BaGE$x)5JFIItig2VC*tY8sfxde}6p3G|?n0lJon$|9kKM
z{(kRW{-f*iM*Qj!UA#HGIMg*eo6qNS)06p`QfYI2$T-urOrgKfeRLaLL(4bUE+BS{
zWlqxTpIgH;w|>nU9bkGHW`Z{A)uy7C8=@|1imB)t4l#4|^|fE<zRf3A|J81mq9__Y
zhhM6Qo^K|;`v)=9lyYU5zB3#O_iSU(0}4GuPtb$Sgzzisi!m&Pl2M_M6S_hywIdlV
zObdc=_vyh<qNj&Lw<z=!{fT}@Kcjo-7PPs6=C^Y!m4r4q0iNxw^HRta?3ZwmK<JhR
z!Tp!JPs^eBZ;?0*QlXw*kMz)It=MYpbSxJOM;}J-MK_{rQ7L*jIud0fk0U=tHX^H$
zQ;}RG%01+6bJw|5?mRcg4RSR5i2afMmi>~gvd7sR+Z}!s{yw}Oz8F3f?q~jG?la#o
zUohvGJxn<CAapzQRY(aP2=&r`(?8H((>i^W9-^MvxuULJ8MLwrN5#hZlvu5nD{4)w
zmQ7VtN~O|1cmx3+QC1Dps7e``qoNttRBNi~h>$kd3W!;Wqv*LL-lwlDi28z3HWV3R
z*37ajEs44TIrB(%!E)77XH{;g84=6_2+y{eO;MNXH6!nV^k;TRglF1BS*g@(GV%B_
zcqI$r=?<c%u97ikJ_T=P2QId03!1tj<Yoy-f8usL)h0?}18SIboc`GL3btpxke_<P
zRO_`#FF^VuS0wMPJW5M-byXt&^B;m&LYQ>W@?@n^H;I<M;D#d;b2Q6Y7xPjBlH&{r
z#*;qpihJ88vQkrn6ZydPa&2DSS!mMH*!$qkAUx6TH7wI0`Om&*Ykhc6o7Pkf2`XWV
zEGd7^?l_Kjx0%{X)hv?*;oo(&U7a;&o$UPc@3`K0XOE<XKkI7Q&KgN`_KdA1ai+5d
zTOi3x<}^oHbSa6`ZR(O(t%xl%d&-T;w~0=o7h9hRe-g}M5|6c+jk+%6NNr|M*jhgx
zwc0$rZcI)WTM-A3gPKX=S6k+y78o0T8~8{Pj}R_vLXMQ;&@q6c{dkzbt<kO2Q8304
z9wG*O1C@qEa+G?@#e)Q!x(M6JCGQo#37AJX<>N|ux<=p;7Y+nq(Qo;N11*dLbjqiP
z-KIMOu;9bjUDzLhlRlhx;VS`{Cvfl(K*(huffY+u77BU6pYB0hNZ^-!0r+H|;DOg{
zjPQ;i&XZ(k4%n2(y@WOlu}QXaXE*mic0Y*YJWlE>mb*jCrXj90y<I=3j?IBO!sC}n
zOw;IK4eqn8ew-kdY!={T@GCuAay#rH9JaZJY=UC>r-z5qv!L=kM!u?vs~x(`%zy`z
z?e;zUObQ-H9@sez;wZv#BI-(mRLDTlK7SygT?DFi*lc_}<<jjf3Zo{AIa~mmkK=7E
zYM6CtSzJ`Se+DPPn26(8%Yfyq>$3MgkmoF?`)E*TlI~LiG>1<kLa44R!Z}KcA+^`V
z9Kmo3%3_mjSE-zf*%z=zie+p9@aU+8!#=N6g<KIbC>+`C7i4x1X!CK**!Kgeu4!UT
zE?3o>QWo{a75nfoy!J!8!Q}gJ$TsaegkdTTr~<zTGP|6c27ckDv7gkZbC2*srN^C{
zhGTZTX?Wh<jec6@dZXQUDD)6*qBV3LO`@*u$K7{suHERiCNvn*kwhxyUSZ01EER4z
zkSGa8u8a0*JMCX^_$uhdL@Mg(WD;c!mb9gfErV7_q#|22$!G?wi}u=r#%<LW)IgsZ
z6@rx#DR!%6NV=i~L}3ZUW64z56IHEIRVCGQ=Fqwu!wR}WRT@9B2u<eWssC9!;tp3p
M<@-|Yi${$A0MPiNaR2}S

delta 176
zcmV;h08jsbpbdcF43HlIGqE6N9}G7)FfukSH#9UjFgLT4A9GL)Yyc1F56cg=50MXS
zv4L6-1tSFmO$n25e;|{Qe<K_M6axb-0dsF`c4cy3aA9&`ZDn+2awr=D13Lkifz1II
zv*&*qkOvI_001ZnpqB?Q15vSoNC~%~IRj({hXf!2rvxAZ5C{Sf>HrV*59+fKK;91t
e2?7NG4-*J5Gq>|U0ud0m1Rw$M54ZGR0u0d98#4s}

-- 
GitLab