From 29350858994719fabada517ddfaebbad7cbf1a8e Mon Sep 17 00:00:00 2001
From: signedav
Date: Wed, 22 Nov 2023 08:44:40 +0100
Subject: [PATCH 01/11] implementation of basket manager callable over the
dataset manager
---
QgisModelBaker/gui/basket_manager.py | 67 ++++++
QgisModelBaker/gui/dataset_manager.py | 37 +---
QgisModelBaker/gui/panel/basket_panel.py | 256 +++++++++++++++++++++++
QgisModelBaker/ui/basket_manager.ui | 31 +++
QgisModelBaker/ui/basket_panel.ui | 36 ++++
QgisModelBaker/ui/dataset_manager.ui | 33 +--
6 files changed, 409 insertions(+), 51 deletions(-)
create mode 100644 QgisModelBaker/gui/basket_manager.py
create mode 100644 QgisModelBaker/gui/panel/basket_panel.py
create mode 100644 QgisModelBaker/ui/basket_manager.ui
create mode 100644 QgisModelBaker/ui/basket_panel.ui
diff --git a/QgisModelBaker/gui/basket_manager.py b/QgisModelBaker/gui/basket_manager.py
new file mode 100644
index 000000000..114687a9c
--- /dev/null
+++ b/QgisModelBaker/gui/basket_manager.py
@@ -0,0 +1,67 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 10.08.2021
+ git sha : :%H$
+ copyright : (C) 2021 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+from qgis.PyQt.QtWidgets import QDialog, QMessageBox
+
+from QgisModelBaker.gui.panel.basket_panel import BasketPanel
+from QgisModelBaker.utils import gui_utils
+
+DIALOG_UI = gui_utils.get_ui_class("basket_manager.ui")
+
+
+class BasketManagerDialog(QDialog, DIALOG_UI):
+ def __init__(self, parent=None, db_connector=None, datasetname=None):
+ QDialog.__init__(self, parent)
+ self.setupUi(self)
+
+ self.datasetname = datasetname
+ self.db_connector = db_connector
+
+ self.buttonBox.accepted.connect(self._accepted)
+ self.buttonBox.rejected.connect(self._rejected)
+
+ # baskets part
+ self.baskets_panel = BasketPanel(self)
+ self.baskets_layout.addWidget(self.baskets_panel)
+
+ self.baskets_panel.load_basket_config(self.db_connector, self.datasetname)
+
+ def _accepted(self):
+ feedbacks = self.baskets_panel.save_basket_config(
+ self.db_connector, self.datasetname
+ )
+ negative_feedbacks = [
+ feedback for feedback in feedbacks if feedback[0] is False
+ ]
+ if negative_feedbacks:
+ warning_box = QMessageBox(self)
+ warning_box.setIcon(QMessageBox.Critical)
+ warning_title = self.tr("Creating baskets failed")
+ warning_box.setWindowTitle(warning_title)
+ warning_box.setText(
+ "{}{}".format(
+ "\n".join([feedback[1] for feedback in negative_feedbacks]),
+ "\n(The problem is often an incorrectly formatted BID)",
+ )
+ )
+ warning_box.exec_()
+ self.close()
+
+ def _rejected(self):
+ self.close()
diff --git a/QgisModelBaker/gui/dataset_manager.py b/QgisModelBaker/gui/dataset_manager.py
index 31c5cc4d5..3214716ad 100644
--- a/QgisModelBaker/gui/dataset_manager.py
+++ b/QgisModelBaker/gui/dataset_manager.py
@@ -21,6 +21,7 @@
from qgis.PyQt.QtWidgets import QDialog, QHeaderView, QMessageBox, QTableView
import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
+from QgisModelBaker.gui.basket_manager import BasketManagerDialog
from QgisModelBaker.gui.edit_dataset_name import EditDatasetDialog
from QgisModelBaker.gui.panel import db_panel_utils
from QgisModelBaker.libs.modelbaker.db_factory.db_simple_factory import DbSimpleFactory
@@ -89,7 +90,7 @@ def __init__(self, iface, parent=None, wizard_embedded=False):
self.add_button.clicked.connect(self._add_dataset)
self.edit_button.clicked.connect(self._edit_dataset)
- self.create_baskets_button.clicked.connect(self._create_baskets)
+ self.basket_manager_button.clicked.connect(self._open_basket_manager)
self.dataset_tableview.selectionModel().selectionChanged.connect(
lambda: self._enable_dataset_handling(True)
)
@@ -126,7 +127,7 @@ def _enable_dataset_handling(self, enable):
self.dataset_tableview.setEnabled(enable)
self.add_button.setEnabled(enable)
self.edit_button.setEnabled(self._valid_selection())
- self.create_baskets_button.setEnabled(self._valid_selection())
+ self.basket_manager_button.setEnabled(self._valid_selection())
def _type_changed(self):
ili_mode = self.type_combo_box.currentData()
@@ -180,37 +181,17 @@ def _edit_dataset(self):
self._refresh_datasets(self._updated_configuration())
self._jump_to_entry(edit_dataset_dialog.dataset_line_edit.text())
- def _create_baskets(self):
+ def _open_basket_manager(self):
if self._valid_selection():
db_connector = db_utils.get_db_connector(self._updated_configuration())
if db_connector and db_connector.get_basket_handling:
- feedbacks = []
- for record in db_connector.get_topics_info():
- dataset_tid = self.dataset_tableview.selectedIndexes()[0].data(
- int(DatasetModel.Roles.TID)
- )
- status, message = db_connector.create_basket(
- dataset_tid, ".".join([record["model"], record["topic"]])
- )
- feedbacks.append((status, message))
-
- info_box = QMessageBox(self)
- info_box.setIcon(
- QMessageBox.Warning
- if len([feedback for feedback in feedbacks if not feedback[0]])
- else QMessageBox.Information
+ datasetname = self.dataset_tableview.selectedIndexes()[0].data(
+ int(DatasetModel.Roles.DATASETNAME)
)
- info_title = self.tr("Created baskets")
- info_box.setWindowTitle(info_title)
- info_box.setText(
- "{}{}".format(
- "\n".join([feedback[1] for feedback in feedbacks]),
- "\n\nBe aware that the IDs of the baskets are created as UUIDs. To change that, edit the t_ili2db_basket table manually."
- if len([feedback for feedback in feedbacks if feedback[0]])
- else "",
- )
+ basket_manager_dialog = BasketManagerDialog(
+ self, db_connector, datasetname
)
- info_box.exec_()
+ basket_manager_dialog.exec_()
def _jump_to_entry(self, datasetname):
matches = self.dataset_model.match(
diff --git a/QgisModelBaker/gui/panel/basket_panel.py b/QgisModelBaker/gui/panel/basket_panel.py
new file mode 100644
index 000000000..ff33abefd
--- /dev/null
+++ b/QgisModelBaker/gui/panel/basket_panel.py
@@ -0,0 +1,256 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 20.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+import uuid
+from enum import Enum, IntEnum
+
+from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt
+from qgis.PyQt.QtWidgets import QAbstractItemView, QHeaderView, QWidget
+
+import QgisModelBaker.utils.gui_utils as gui_utils
+from QgisModelBaker.utils.gui_utils import CheckDelegate
+
+WIDGET_UI = gui_utils.get_ui_class("basket_panel.ui")
+
+
+class BasketModel(QAbstractTableModel):
+ """
+ ItemModel providing all the baskets, the existing ones and the possible ones in the given dataset.
+ It provides the topic, the suggested BIDs (t_ili_tid) and the information if it's created already.
+ As well the option if it should be created (could be extended if it should be deleted when unchecked)
+ """
+
+ class Roles(Enum):
+ EXISTING = Qt.UserRole + 1
+
+ def __int__(self):
+ return self.value
+
+ class Columns(IntEnum):
+ DO_CREATE = 0
+ TOPIC = 1
+ BID_DOMAIN = 2
+ BID_VALUE = 3
+
+ def __init__(self):
+ super().__init__()
+ self.basket_settings = {}
+
+ def columnCount(self, parent):
+ return len(BasketModel.Columns)
+
+ def rowCount(self, parent):
+ return len(self.basket_settings.keys())
+
+ def flags(self, index):
+ if index.data(int(BasketModel.Roles.EXISTING)):
+ return Qt.ItemIsSelectable
+ if index.column() == BasketModel.Columns.DO_CREATE:
+ return Qt.ItemIsEnabled
+ if index.column() == BasketModel.Columns.BID_VALUE:
+ return Qt.ItemIsEditable | Qt.ItemIsEnabled
+ return Qt.ItemIsSelectable | Qt.ItemIsEnabled
+
+ def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
+ """
+ default override
+ """
+ return super().createIndex(row, column, parent)
+
+ def parent(self, index):
+ """
+ default override
+ """
+ return index
+
+ def headerData(self, section, orientation, role):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if section == BasketModel.Columns.DO_CREATE:
+ return self.tr("Create")
+ if section == BasketModel.Columns.TOPIC:
+ return self.tr("Topic")
+ if section == BasketModel.Columns.BID_DOMAIN:
+ return self.tr("BID (OID Type)")
+ if section == BasketModel.Columns.BID_VALUE:
+ return self.tr("BID Value")
+
+ def data(self, index, role):
+ if role == int(Qt.DisplayRole) or role == int(Qt.EditRole):
+ key = list(self.basket_settings.keys())[index.row()]
+ if index.column() == BasketModel.Columns.DO_CREATE:
+ return self.basket_settings[key]["create"]
+ if index.column() == BasketModel.Columns.TOPIC:
+ return key
+ if index.column() == BasketModel.Columns.BID_DOMAIN:
+ return self.basket_settings[key]["bid_domain"]
+ if index.column() == BasketModel.Columns.BID_VALUE:
+ return self.basket_settings[key]["bid_value"]
+ elif role == int(Qt.ToolTipRole):
+ key = list(self.basket_settings.keys())[index.row()]
+ if index.column() == BasketModel.Columns.DO_CREATE:
+ return self.tr("If this basket should be created")
+ if index.column() == BasketModel.Columns.TOPIC:
+ return key
+ if index.column() == BasketModel.Columns.BID_DOMAIN:
+ message = self.tr(
+ "
The OID format is not defined, you can use whatever you want, but it should always start with an underscore _
or an alphanumeric value.
"
+ )
+ oid_domain = self.basket_settings[key].get("oid_domain", "")
+ if oid_domain[-7:] == "UUIDOID":
+ message = self.tr(
+ "The OID should be an Universally Unique Identifier (OID TEXT*36).
"
+ )
+ elif oid_domain[-11:] == "STANDARDOID":
+ message = self.tr(
+ """
+
+
+ The OID format requireds an 8 char prefix and 8 char postfix.
+
+ Prefix (2 + 6 chars): Country identifier + a 'global' identification part assigned once by the official authority.
+ Postfix (8 chars): Sequence (numeric or alphanumeric) of your system as 'local' identification part.
+
+
+ """
+ )
+ elif oid_domain[-6:] == "I32OID":
+ message = self.tr(
+ "The OID must be an integer value (OID 0 .. 2147483647).
"
+ )
+ elif oid_domain[-6:] == "ANYOID":
+ message = self.tr(
+ "The OID format could vary depending in what basket the object (entry) is located.
These objects could be in the following topics: {topics}".format(
+ topics=self.basket_settings[key]["interlis_topic"]
+ )
+ )
+ return message
+ if index.column() == BasketModel.Columns.BID_VALUE:
+ return self.basket_settings[key]["bid_value"]
+ elif role == int(BasketModel.Roles.EXISTING):
+ key = list(self.basket_settings.keys())[index.row()]
+ return self.basket_settings[key]["existing"]
+ return None
+
+ def setData(self, index, data, role):
+ if role == int(Qt.EditRole):
+ if index.column() == BasketModel.Columns.BID_VALUE:
+ key = list(self.basket_settings.keys())[index.row()]
+ self.basket_settings[key]["bid_value"] = data
+ self.dataChanged.emit(index, index)
+ if index.column() == BasketModel.Columns.DO_CREATE:
+ key = list(self.basket_settings.keys())[index.row()]
+ self.basket_settings[key]["create"] = data
+ self.dataChanged.emit(index, index)
+ return True
+
+ def load_basket_config(self, db_connector, dataset):
+ self.beginResetModel()
+ self.basket_settings.clear()
+ for topic_record in db_connector.get_topics_info():
+ basket_setting = {}
+ topic_key = f"{topic_record['model']}.{topic_record['topic']}"
+ # check if existing
+ if topic_key in [
+ basket_record["topic"]
+ for basket_record in db_connector.get_baskets_info()
+ if basket_record["datasetname"] == dataset
+ ]:
+ basket_setting["existing"] = True
+ basket_setting["create"] = True
+ else:
+ # if not existing "suggest" create if "relevant"
+ basket_setting["existing"] = False
+ basket_setting["create"] = topic_record["relevance"]
+ # set bid_domain and create suggestion of value
+ basket_setting["bid_domain"] = topic_record["bid_domain"]
+ if basket_setting["bid_domain"] == "INTERLIS.UUIDOID":
+ basket_setting["bid_value"] = uuid.uuid4()
+ elif basket_setting["bid_domain"] == "INTERLIS.STANDARDOID":
+ basket_setting["bid_value"] = f"chB00000{self._next_tid_value()}"
+ elif basket_setting["bid_domain"] == "INTERLIS.I32OID":
+ basket_setting["bid_value"] = self._next_tid_value()
+ else:
+ basket_setting["bid_value"] = f"_{uuid.uuid4()}"
+
+ self.basket_settings[topic_key] = basket_setting
+ self.endResetModel()
+
+ def _next_tid_value(self):
+ return 42
+
+ def save_basket_config(self, db_connector, dataset):
+ feedbacks = []
+ datasets_info = db_connector.get_datasets_info()
+ dataset_tid = -1
+ for dataset_record in datasets_info:
+ if dataset_record["datasetname"] == dataset:
+ dataset_tid = dataset_record["t_id"]
+ break
+ if dataset_tid < 0:
+ feedbacks.append((False, self.tr("Dataset needs to be created first.")))
+ else:
+ for topic_key in self.basket_settings.keys():
+ basket_setting = self.basket_settings[topic_key]
+ if not basket_setting["existing"] and basket_setting["create"]:
+ # basket should be created
+ print(f"create {topic_key}")
+ status, message = db_connector.create_basket(
+ dataset_tid, topic_key, basket_setting["bid_value"]
+ )
+ feedbacks.append((status, message))
+ return feedbacks
+
+
+class BasketPanel(QWidget, WIDGET_UI):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.setupUi(self)
+ self.parent = parent
+ self.bid_model = BasketModel()
+ self.basket_view.setModel(self.bid_model)
+
+ self.basket_view.horizontalHeader().setSectionResizeMode(
+ BasketModel.Columns.DO_CREATE, QHeaderView.ResizeToContents
+ )
+ self.basket_view.horizontalHeader().setSectionResizeMode(
+ BasketModel.Columns.TOPIC, QHeaderView.Stretch
+ )
+ self.basket_view.horizontalHeader().setSectionResizeMode(
+ BasketModel.Columns.BID_DOMAIN, QHeaderView.ResizeToContents
+ )
+ self.basket_view.horizontalHeader().setSectionResizeMode(
+ BasketModel.Columns.BID_VALUE, QHeaderView.ResizeToContents
+ )
+
+ self.basket_view.setItemDelegateForColumn(
+ BasketModel.Columns.DO_CREATE,
+ CheckDelegate(self, Qt.EditRole),
+ )
+ self.basket_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
+
+ def load_basket_config(self, db_connector, dataset):
+ self.bid_model.load_basket_config(db_connector, dataset)
+
+ def save_basket_config(self, db_connector, dataset):
+ # if a cell is still edited, we need to store it in model by force
+ index = self.basket_view.currentIndex()
+ self.basket_view.currentChanged(index, index)
+
+ return self.bid_model.save_basket_config(db_connector, dataset)
diff --git a/QgisModelBaker/ui/basket_manager.ui b/QgisModelBaker/ui/basket_manager.ui
new file mode 100644
index 000000000..2976fe213
--- /dev/null
+++ b/QgisModelBaker/ui/basket_manager.ui
@@ -0,0 +1,31 @@
+
+
+ EditDatasetDialog
+
+
+
+ 0
+ 0
+ 1082
+ 470
+
+
+
+ Edit dataset
+
+
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/ui/basket_panel.ui b/QgisModelBaker/ui/basket_panel.ui
new file mode 100644
index 000000000..0de501439
--- /dev/null
+++ b/QgisModelBaker/ui/basket_panel.ui
@@ -0,0 +1,36 @@
+
+
+ basket_panel
+
+
+
+ 0
+ 0
+ 797
+ 443
+
+
+
+ Basket Configuration
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/ui/dataset_manager.ui b/QgisModelBaker/ui/dataset_manager.ui
index 1d84a2553..fff9cdbf6 100644
--- a/QgisModelBaker/ui/dataset_manager.ui
+++ b/QgisModelBaker/ui/dataset_manager.ui
@@ -14,26 +14,6 @@
Dataset Manager
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
@@ -56,12 +36,12 @@
-
-
+
- <html><head/><body><p>Creates a <span style=" font-weight:600;">basket</span> per <span style=" font-weight:600;">topic</span> and <span style=" font-weight:600;">dataset </span>in the <span style=" font-style:italic;">t_ili2db_basket</span> table.</p><p>While a dataset is just an entry in the <span style=" font-style:italic;">t_ili2db_dataset</span> table, there must be baskets for each entry that can be selected. Usually there exists only one basket topic and per dataset.</p></body></html>
+ <html><head/><body><p>To create a <span style=" font-weight:600;">basket</span> per <span style=" font-weight:600;">topic</span> and <span style=" font-weight:600;">dataset </span>in the <span style=" font-style:italic;">t_ili2db_basket</span> table or not.</p><p>While a dataset is just an entry in the <span style=" font-style:italic;">t_ili2db_dataset</span> table, there must be baskets for each entry that can be selected. Usually there exists only one basket topic and per dataset.</p></body></html>
- Create baskets for selected dataset
+ Manage baskets of selected dataset
@@ -145,6 +125,13 @@
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
From 322738193da04714bff501c13adbf3735b4e8eea Mon Sep 17 00:00:00 2001
From: signedav
Date: Wed, 22 Nov 2023 14:24:13 +0100
Subject: [PATCH 02/11] Basket create page in Workflow wizard
---
QgisModelBaker/gui/panel/session_panel.py | 13 +--
.../workflow_wizard/default_baskets_page.py | 106 ++++++++++++++++++
.../workflow_wizard/project_creation_page.py | 2 +-
.../gui/workflow_wizard/workflow_wizard.py | 20 +++-
.../ui/workflow_wizard/default_baskets.ui | 87 ++++++++++++++
QgisModelBaker/utils/gui_utils.py | 74 ++++++++++--
6 files changed, 282 insertions(+), 20 deletions(-)
create mode 100644 QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
create mode 100644 QgisModelBaker/ui/workflow_wizard/default_baskets.ui
diff --git a/QgisModelBaker/gui/panel/session_panel.py b/QgisModelBaker/gui/panel/session_panel.py
index 918d864f2..d1794fb8f 100644
--- a/QgisModelBaker/gui/panel/session_panel.py
+++ b/QgisModelBaker/gui/panel/session_panel.py
@@ -354,7 +354,7 @@ def run(self, edited_command=None):
self.progress_bar.setValue(90)
- # an user interaction (cancel) here cannot interupt the process, why it's disabled (and enabled again below). This part might come to a seperate page anyway in future.
+ # an user interaction (cancel) here cannot interupt the process, why it's disabled (and enabled again below).
self.setDisabled(True)
if (
self.db_action_type == DbActionType.GENERATE
@@ -396,16 +396,7 @@ def _create_default_dataset(self):
if default_datasets_info_tids:
default_dataset_tid = default_datasets_info_tids[0]
- if default_dataset_tid is not None:
- for topic_record in db_connector.get_topics_info():
- status, message = db_connector.create_basket(
- default_dataset_tid,
- ".".join([topic_record["model"], topic_record["topic"]]),
- )
- self.print_info.emit(
- self.tr("- {}").format(message), LogColor.COLOR_INFO
- )
- else:
+ if default_dataset_tid is None:
self.print_info.emit(
self.tr(
"No default dataset created ({}) - do it manually in the dataset manager."
diff --git a/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py b/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
new file mode 100644
index 000000000..51d270a21
--- /dev/null
+++ b/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
@@ -0,0 +1,106 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 17.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+/***************************************************************************
+ * *
+ * 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 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+from qgis.PyQt.QtWidgets import QWizardPage
+
+import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
+from QgisModelBaker.gui.panel.basket_panel import BasketPanel
+from QgisModelBaker.utils import gui_utils
+from QgisModelBaker.utils.globals import DEFAULT_DATASETNAME
+from QgisModelBaker.utils.gui_utils import LogColor
+
+PAGE_UI = gui_utils.get_ui_class("workflow_wizard/default_baskets.ui")
+
+
+class DefaultBasketsPage(QWizardPage, PAGE_UI):
+ def __init__(self, parent, title):
+ QWizardPage.__init__(self, parent)
+
+ self.workflow_wizard = parent
+
+ self.setupUi(self)
+ self.setTitle(title)
+ self.setStyleSheet(gui_utils.DEFAULT_STYLE)
+
+ self.baskets_panel = BasketPanel(self)
+ self.baskets_layout.addWidget(self.baskets_panel)
+
+ self.create_default_baskets_button.clicked.connect(self._create_default_baskets)
+ self.skip_button.clicked.connect(self._skip)
+
+ self.db_connector = None
+ self.is_complete = False
+
+ def isComplete(self):
+ return self.is_complete
+
+ def setComplete(self, complete):
+ self.is_complete = complete
+ self.create_default_baskets_button.setDisabled(complete)
+ self.skip_button.setDisabled(complete)
+ self.basket_panel.setDisabled(complete)
+ self.completeChanged.emit()
+
+ def nextId(self):
+ return self.workflow_wizard.next_id()
+
+ def restore_configuration(self, configuration):
+ self.db_connector = db_utils.get_db_connector(configuration)
+ self.baskets_panel.load_basket_config(self.db_connector, DEFAULT_DATASETNAME)
+
+ def _create_default_baskets(self):
+ self.progress_bar.setValue(0)
+ # we store the settings to the db
+ feedbacks = self.baskets_panel.save_basket_config(
+ self.db_connector, DEFAULT_DATASETNAME
+ )
+ success = True
+ for feedback in feedbacks:
+ if feedback[0]:
+ # positive
+ self.workflow_wizard.log_panel.print_info(
+ feedback[1], LogColor.COLOR_INFO
+ )
+ else:
+ # negative
+ self.workflow_wizard.log_panel.print_info(
+ feedback[1], LogColor.COLOR_FAIL
+ )
+ success = False
+
+ if success:
+ self.progress_bar.setFormat(self.tr("Default baskets created!"))
+ self.progress_bar.setValue(100)
+ self.setStyleSheet(gui_utils.SUCCESS_STYLE)
+ self.setComplete(True)
+ else:
+ self.workflow_wizard.log_panel.print_info(message)
+ self.progress_bar.setFormat(
+ self.tr(
+ "Issues occured. Skip to proceed and fix it in Dataset Manager..."
+ )
+ )
+ self.progress_bar.setValue(0)
+
+ def _skip(self):
+ self.progress_bar.setValue(100)
+ self.progress_bar.setFormat(self.tr("SKIPPED"))
+ self.progress_bar.setTextVisible(True)
+ self.setStyleSheet(gui_utils.INACTIVE_STYLE)
+ self.setComplete(True)
diff --git a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
index 2e94ae094..d4e464e77 100644
--- a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
@@ -63,7 +63,7 @@ def __init__(self, parent, title):
self.create_project_button.clicked.connect(self._create_project)
- def set_configuration(self, configuration):
+ def restore_configuration(self, configuration):
self.configuration = configuration
def _create_project(self):
diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
index 3db32c7d8..7265ab190 100644
--- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
+++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
@@ -29,6 +29,7 @@
from QgisModelBaker.gui.workflow_wizard.database_selection_page import (
DatabaseSelectionPage,
)
+from QgisModelBaker.gui.workflow_wizard.default_baskets_page import DefaultBasketsPage
from QgisModelBaker.gui.workflow_wizard.execution_page import ExecutionPage
from QgisModelBaker.gui.workflow_wizard.export_data_configuration_page import (
ExportDataConfigurationPage,
@@ -150,6 +151,9 @@ def __init__(self, iface, base_config, parent):
self._current_page_title(PageIds.ImportSchemaExecution),
DbActionType.GENERATE,
)
+ self.default_baskets_page = DefaultBasketsPage(
+ self, self._current_page_title(PageIds.DefaultBaskets)
+ )
self.data_configuration_page = ImportDataConfigurationPage(
self, self._current_page_title(PageIds.ImportDataConfiguration)
)
@@ -186,6 +190,7 @@ def __init__(self, iface, base_config, parent):
)
self.setPage(PageIds.ImportSchemaConfiguration, self.schema_configuration_page)
self.setPage(PageIds.ImportSchemaExecution, self.import_schema_execution_page)
+ self.setPage(PageIds.DefaultBaskets, self.default_baskets_page)
self.setPage(PageIds.ImportDataConfiguration, self.data_configuration_page)
self.setPage(PageIds.ImportDataExecution, self.import_data_execution_page)
self.setPage(PageIds.ProjectCreation, self.project_creation_page)
@@ -301,6 +306,12 @@ def next_id(self):
self.import_schema_execution_page.setComplete(True)
return PageIds.ImportDataConfiguration
self.import_schema_execution_page.setComplete(True)
+
+ if self._basket_handling(self.import_schema_configuration):
+ return PageIds.DefaultBaskets
+ return PageIds.ProjectCreation
+
+ if self.current_id == PageIds.DefaultBaskets:
return PageIds.ProjectCreation
if self.current_id == PageIds.ImportDataConfiguration:
@@ -364,8 +375,13 @@ def id_changed(self, new_id):
self.import_models_model.import_sessions(),
)
+ if self.current_id == PageIds.DefaultBaskets:
+ self.default_baskets_page.restore_configuration(
+ self.import_schema_configuration
+ )
+
if self.current_id == PageIds.ProjectCreation:
- self.project_creation_page.set_configuration(
+ self.project_creation_page.restore_configuration(
self.import_schema_configuration
)
@@ -443,6 +459,8 @@ def _current_page_title(self, id):
return self.tr("Schema Import Configuration")
elif id == PageIds.ImportSchemaExecution:
return self.tr("Schema Import Sessions")
+ elif id == PageIds.DefaultBaskets:
+ return self.tr("Create Baskets for Default Dataset")
elif id == PageIds.ImportDataConfiguration:
return self.tr("Data Import Configuration")
elif id == PageIds.ImportDataExecution:
diff --git a/QgisModelBaker/ui/workflow_wizard/default_baskets.ui b/QgisModelBaker/ui/workflow_wizard/default_baskets.ui
new file mode 100644
index 000000000..4354b6fc4
--- /dev/null
+++ b/QgisModelBaker/ui/workflow_wizard/default_baskets.ui
@@ -0,0 +1,87 @@
+
+
+ default_baskets
+
+
+
+ 0
+ 0
+ 800
+ 700
+
+
+
+ Create default Baskets
+
+
+ -
+
+
+ QLayout::SetMaximumSize
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ <html><head/><body><p>You need baskets of the default dataset (<code>Baseset</code>) to collect new data in the created schema. </p><p>If you plan to start with the import of existing data, you can skip this step. To create baskets later on, find the <b><i>Dataset Manager</i></b> in the Model Baker Menu.</p></body></html>
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+ -
+
+
+ Create baskets
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 0
+
+
+
+ -
+
+
+ Skip this step
+
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py
index 6ac5b1d04..1de62f71d 100644
--- a/QgisModelBaker/utils/gui_utils.py
+++ b/QgisModelBaker/utils/gui_utils.py
@@ -6,15 +6,25 @@
import xml.etree.ElementTree as CET
from enum import Enum, IntEnum
+from PyQt5.QtWidgets import QApplication
from qgis.PyQt.QtCore import (
+ QEvent,
QModelIndex,
+ QRect,
QSortFilterProxyModel,
QStringListModel,
Qt,
pyqtSignal,
)
from qgis.PyQt.QtGui import QIcon, QStandardItem, QStandardItemModel
-from qgis.PyQt.QtWidgets import QCheckBox, QLineEdit, QListView
+from qgis.PyQt.QtWidgets import (
+ QCheckBox,
+ QLineEdit,
+ QListView,
+ QStyle,
+ QStyledItemDelegate,
+ QStyleOptionButton,
+)
from qgis.PyQt.uic import loadUiType
from QgisModelBaker.libs.modelbaker.iliwrapper.ilicache import IliCache
@@ -168,12 +178,13 @@ class PageIds:
GenerateDatabaseSelection = 4
ImportSchemaConfiguration = 5
ImportSchemaExecution = 6
- ImportDataConfiguration = 7
- ImportDataExecution = 8
- ExportDatabaseSelection = 9
- ExportDataConfiguration = 10
- ExportDataExecution = 11
- ProjectCreation = 12
+ DefaultBaskets = 7
+ ImportDataConfiguration = 8
+ ImportDataExecution = 9
+ ExportDatabaseSelection = 10
+ ExportDataConfiguration = 11
+ ExportDataExecution = 12
+ ProjectCreation = 13
class ToppingWizardPageIds:
@@ -1107,3 +1118,52 @@ def model_topics(self, schema_identificator):
for basket in self.schema_baskets[schema_identificator]:
model_topics.add(basket.get("topic", ""))
return list(model_topics)
+
+
+class CheckDelegate(QStyledItemDelegate):
+ def __init__(self, parent, role):
+
+ super().__init__(parent)
+
+ self.role = role
+
+ def editorEvent(self, event, model, option, index):
+
+ if event.type() == QEvent.MouseButtonPress:
+
+ value = index.data(int(self.role)) or False
+
+ model.setData(index, not value, int(self.role))
+
+ return True
+
+ return super().editorEvent(event, model, option, index)
+
+ def paint(self, painter, option, index):
+
+ opt = QStyleOptionButton()
+
+ opt.rect = option.rect
+
+ center_x = opt.rect.x() + opt.rect.width() / 2
+
+ center_y = opt.rect.y() + opt.rect.height() / 2
+
+ checkbox_width = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth)
+
+ checkbox_height = QApplication.style().pixelMetric(QStyle.PM_IndicatorHeight)
+
+ checkbox_rect = QRect(
+ int(center_x - checkbox_width / 2),
+ int(center_y - checkbox_height / 2),
+ checkbox_width,
+ checkbox_height,
+ )
+
+ opt.rect = checkbox_rect
+
+ value = index.data(int(self.role)) or False
+
+ opt.state |= QStyle.State_On if value else QStyle.State_Off
+
+ QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter)
From 17ddc9d37789e1efe3f520e1a1cc0554f86a21fb Mon Sep 17 00:00:00 2001
From: signedav
Date: Wed, 22 Nov 2023 16:16:23 +0100
Subject: [PATCH 03/11] use {t_id} as placeholder
---
QgisModelBaker/gui/panel/basket_panel.py | 57 ++++++++++++++----------
QgisModelBaker/ui/basket_panel.ui | 7 +++
QgisModelBaker/utils/gui_utils.py | 16 +++----
3 files changed, 46 insertions(+), 34 deletions(-)
diff --git a/QgisModelBaker/gui/panel/basket_panel.py b/QgisModelBaker/gui/panel/basket_panel.py
index ff33abefd..71dd20766 100644
--- a/QgisModelBaker/gui/panel/basket_panel.py
+++ b/QgisModelBaker/gui/panel/basket_panel.py
@@ -165,35 +165,40 @@ def load_basket_config(self, db_connector, dataset):
self.basket_settings.clear()
for topic_record in db_connector.get_topics_info():
basket_setting = {}
+
topic_key = f"{topic_record['model']}.{topic_record['topic']}"
# check if existing
- if topic_key in [
- basket_record["topic"]
- for basket_record in db_connector.get_baskets_info()
- if basket_record["datasetname"] == dataset
- ]:
- basket_setting["existing"] = True
- basket_setting["create"] = True
- else:
- # if not existing "suggest" create if "relevant"
- basket_setting["existing"] = False
- basket_setting["create"] = topic_record["relevance"]
- # set bid_domain and create suggestion of value
+ existing = False
+ for basket_record in db_connector.get_baskets_info():
+ if (
+ basket_record["datasetname"] == dataset
+ and topic_key == basket_record["topic"]
+ ):
+ existing = True
+ basket_setting["bid_value"] = basket_record["basket_t_ili_tid"]
+
+ # if not existing "suggest" create if "relevant"
+ basket_setting["existing"] = existing
+ basket_setting["create"] = existing or topic_record["relevance"]
+
basket_setting["bid_domain"] = topic_record["bid_domain"]
- if basket_setting["bid_domain"] == "INTERLIS.UUIDOID":
- basket_setting["bid_value"] = uuid.uuid4()
- elif basket_setting["bid_domain"] == "INTERLIS.STANDARDOID":
- basket_setting["bid_value"] = f"chB00000{self._next_tid_value()}"
- elif basket_setting["bid_domain"] == "INTERLIS.I32OID":
- basket_setting["bid_value"] = self._next_tid_value()
- else:
- basket_setting["bid_value"] = f"_{uuid.uuid4()}"
+
+ if not existing:
+ # set suggestion of value
+ if basket_setting["bid_domain"] == "INTERLIS.UUIDOID":
+ basket_setting["bid_value"] = uuid.uuid4()
+ elif basket_setting["bid_domain"] == "INTERLIS.STANDARDOID":
+ basket_setting["bid_value"] = "chB00000{t_id}"
+ elif basket_setting["bid_domain"] == "INTERLIS.I32OID":
+ basket_setting["bid_value"] = "{t_id}"
+ else:
+ basket_setting["bid_value"] = f"_{uuid.uuid4()}"
self.basket_settings[topic_key] = basket_setting
self.endResetModel()
- def _next_tid_value(self):
- return 42
+ def _next_tid_value(self, db_connector):
+ return db_connector.get_next_ili2db_sequence_value()
def save_basket_config(self, db_connector, dataset):
feedbacks = []
@@ -212,7 +217,11 @@ def save_basket_config(self, db_connector, dataset):
# basket should be created
print(f"create {topic_key}")
status, message = db_connector.create_basket(
- dataset_tid, topic_key, basket_setting["bid_value"]
+ dataset_tid,
+ topic_key,
+ basket_setting["bid_value"].format(
+ t_id=self._next_tid_value(db_connector)
+ ),
)
feedbacks.append((status, message))
return feedbacks
@@ -241,7 +250,7 @@ def __init__(self, parent=None):
self.basket_view.setItemDelegateForColumn(
BasketModel.Columns.DO_CREATE,
- CheckDelegate(self, Qt.EditRole),
+ CheckDelegate(self, Qt.EditRole, BasketModel.Roles.EXISTING),
)
self.basket_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
diff --git a/QgisModelBaker/ui/basket_panel.ui b/QgisModelBaker/ui/basket_panel.ui
index 0de501439..e3f21ecb4 100644
--- a/QgisModelBaker/ui/basket_panel.ui
+++ b/QgisModelBaker/ui/basket_panel.ui
@@ -29,6 +29,13 @@
-
+ -
+
+
+ <html><head/><body><p>Use `{t_id}` as placeholder when you want to use the next T_Id sequence value.</body></html>
+
+
+
diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py
index 1de62f71d..81aa56938 100644
--- a/QgisModelBaker/utils/gui_utils.py
+++ b/QgisModelBaker/utils/gui_utils.py
@@ -1121,11 +1121,13 @@ def model_topics(self, schema_identificator):
class CheckDelegate(QStyledItemDelegate):
- def __init__(self, parent, role):
+ def __init__(self, parent, role, disable_role=None):
super().__init__(parent)
self.role = role
+ # according to this role it can be disabled or enabled
+ self.disable_role = disable_role
def editorEvent(self, event, model, option, index):
@@ -1140,30 +1142,24 @@ def editorEvent(self, event, model, option, index):
return super().editorEvent(event, model, option, index)
def paint(self, painter, option, index):
+ # don't show option when disabled
+ if index.data(int(self.disable_role)) if self.disable_role else False:
+ return
opt = QStyleOptionButton()
-
opt.rect = option.rect
-
center_x = opt.rect.x() + opt.rect.width() / 2
-
center_y = opt.rect.y() + opt.rect.height() / 2
-
checkbox_width = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth)
-
checkbox_height = QApplication.style().pixelMetric(QStyle.PM_IndicatorHeight)
-
checkbox_rect = QRect(
int(center_x - checkbox_width / 2),
int(center_y - checkbox_height / 2),
checkbox_width,
checkbox_height,
)
-
opt.rect = checkbox_rect
value = index.data(int(self.role)) or False
-
opt.state |= QStyle.State_On if value else QStyle.State_Off
-
QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter)
From 34bcf16ee3cf83530d475298cd681c6fe1d48c7b Mon Sep 17 00:00:00 2001
From: signedav
Date: Wed, 22 Nov 2023 16:22:51 +0100
Subject: [PATCH 04/11] fix typo
---
QgisModelBaker/gui/workflow_wizard/default_baskets_page.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py b/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
index 51d270a21..981cb5beb 100644
--- a/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/default_baskets_page.py
@@ -54,7 +54,7 @@ def setComplete(self, complete):
self.is_complete = complete
self.create_default_baskets_button.setDisabled(complete)
self.skip_button.setDisabled(complete)
- self.basket_panel.setDisabled(complete)
+ self.baskets_panel.setDisabled(complete)
self.completeChanged.emit()
def nextId(self):
From bac4985caa1c5b039b1a589cfb3a70b6bc77e79e Mon Sep 17 00:00:00 2001
From: signedav
Date: Thu, 23 Nov 2023 13:17:14 +0100
Subject: [PATCH 05/11] add tooltip etc
---
QgisModelBaker/gui/panel/basket_panel.py | 2 +-
QgisModelBaker/ui/basket_manager.ui | 6 +++---
QgisModelBaker/ui/basket_panel.ui | 7 -------
3 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/QgisModelBaker/gui/panel/basket_panel.py b/QgisModelBaker/gui/panel/basket_panel.py
index 71dd20766..b2b244ef4 100644
--- a/QgisModelBaker/gui/panel/basket_panel.py
+++ b/QgisModelBaker/gui/panel/basket_panel.py
@@ -142,7 +142,7 @@ def data(self, index, role):
)
return message
if index.column() == BasketModel.Columns.BID_VALUE:
- return self.basket_settings[key]["bid_value"]
+ return "Use `{t_id}` as placeholder when you want to use the next T_Id sequence value."
elif role == int(BasketModel.Roles.EXISTING):
key = list(self.basket_settings.keys())[index.row()]
return self.basket_settings[key]["existing"]
diff --git a/QgisModelBaker/ui/basket_manager.ui b/QgisModelBaker/ui/basket_manager.ui
index 2976fe213..9c9237e88 100644
--- a/QgisModelBaker/ui/basket_manager.ui
+++ b/QgisModelBaker/ui/basket_manager.ui
@@ -1,7 +1,7 @@
- EditDatasetDialog
-
+ BasketManagerDialog
+
0
@@ -11,7 +11,7 @@
- Edit dataset
+ Manage Baskets
-
diff --git a/QgisModelBaker/ui/basket_panel.ui b/QgisModelBaker/ui/basket_panel.ui
index e3f21ecb4..0de501439 100644
--- a/QgisModelBaker/ui/basket_panel.ui
+++ b/QgisModelBaker/ui/basket_panel.ui
@@ -29,13 +29,6 @@
-
- -
-
-
- <html><head/><body><p>Use `{t_id}` as placeholder when you want to use the next T_Id sequence value.</body></html>
-
-
-
From 2d2e2c5e63eef06dccbe6b1376a6a8729b751525 Mon Sep 17 00:00:00 2001
From: signedav
Date: Thu, 23 Nov 2023 14:30:56 +0100
Subject: [PATCH 06/11] fix wizard page order
---
.../gui/workflow_wizard/workflow_wizard.py | 43 ++++++++++++++-----
1 file changed, 33 insertions(+), 10 deletions(-)
diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
index 7265ab190..fd00a591e 100644
--- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
+++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
@@ -286,20 +286,25 @@ def next_id(self):
self.schema_configuration_page.setComplete(True)
if self.current_id == PageIds.ImportSchemaExecution:
- # if transfer file available or possible (by getting via UsabILIty Hub)
+ # if transfer files available, then go to data import
+ if self.import_data_file_model.rowCount():
+ return PageIds.ImportDataConfiguration
+
+ # if basket handling active, go to the create basket
+ if self._basket_handling(self.import_schema_configuration):
+ return PageIds.DefaultBaskets
+
+ # if transfer file are possible (by getting via UsabILIty Hub), go to the data import
self.log_panel.print_info(
self.tr(
"Checking for potential referenced data on the repositories (might take a while)..."
)
)
self.import_schema_execution_page.setComplete(False)
- if (
- self.import_data_file_model.rowCount()
- or self.update_referecedata_cache_model(
- self._db_modelnames(self.import_data_configuration),
- "referenceData",
- ).rowCount()
- ):
+ if self.update_referecedata_cache_model(
+ self._db_modelnames(self.import_data_configuration),
+ "referenceData",
+ ).rowCount():
self.log_panel.print_info(
self.tr("Potential referenced data found.")
)
@@ -307,11 +312,29 @@ def next_id(self):
return PageIds.ImportDataConfiguration
self.import_schema_execution_page.setComplete(True)
- if self._basket_handling(self.import_schema_configuration):
- return PageIds.DefaultBaskets
+ # otherwise, go to project create
return PageIds.ProjectCreation
if self.current_id == PageIds.DefaultBaskets:
+ # if transfer file are possible (by getting via UsabILIty Hub), go to the data import
+ self.log_panel.print_info(
+ self.tr(
+ "Checking for potential referenced data on the repositories (might take a while)..."
+ )
+ )
+ self.default_baskets_page.setComplete(False)
+ if self.update_referecedata_cache_model(
+ self._db_modelnames(self.import_data_configuration),
+ "referenceData",
+ ).rowCount():
+ self.log_panel.print_info(
+ self.tr("Potential referenced data found.")
+ )
+ self.default_baskets_page.setComplete(True)
+ return PageIds.ImportDataConfiguration
+ self.default_baskets_page.setComplete(True)
+
+ # otherwise, go to project create
return PageIds.ProjectCreation
if self.current_id == PageIds.ImportDataConfiguration:
From 66786eca0b14d5131b451cc8a05a791ea930e82e Mon Sep 17 00:00:00 2001
From: signedav
Date: Thu, 23 Nov 2023 14:39:48 +0100
Subject: [PATCH 07/11] basket generation and manager documentations
---
docs/assets/dataset_basket_manager.png | Bin 0 -> 83498 bytes
.../workflow_wizard_default_baskets_page.png | Bin 0 -> 97587 bytes
docs/background_info/basket_handling.md | 34 +++++++++++-------
docs/user_guide/import_workflow.md | 13 ++++++-
4 files changed, 34 insertions(+), 13 deletions(-)
create mode 100644 docs/assets/dataset_basket_manager.png
create mode 100644 docs/assets/workflow_wizard_default_baskets_page.png
diff --git a/docs/assets/dataset_basket_manager.png b/docs/assets/dataset_basket_manager.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d8d1176c5dcc7f95dd91b744ad8f878c89af675
GIT binary patch
literal 83498
zcmdRVbySq$_os@0NJ&VC0@B@$N=Tz1Al)D^bhjX#0@A6JbTf4K5RyY9ImFO0z!3Y2
z-{1bXXV320-E;OmoCCxA%oF#yeLpwsz4ANkr({nbJa~XDCo84;-~kHs!GlM4Ptbt>
zfbMvEfnSfE-pYM^0zBSNOhbXsq|VZxoYm~io!yKa%^q0T+S!<~Ihi<`nb|shwsSr}
zX%hzqz4|*y($UPw+0xGT#YamUvjPM
z139TTAKla8i*DY8W~+Tiqi|bhOS;GSo?A*qvg&W7q+WmQF6(Eq5NSfFsfdm0SF)Uk
zQz}dqOO#0u5x4HIg|6b$M1X<(VKe5l4bR+(a43u5b{x^_L
z>M8U;6TMWeC;B%MBliE%llkEL&DH@u6
z^d<07z@4?c@_cx|o)6xi@vYOZm$N6;7qR#?*a{eL$YKo*_OGg+{XKvX_ha(RU13ue
zU4@)yTV_-{YxgHq<#`me{+_y=ZHI^*JGJdSe2>dup5-4&CfVFR<4nF(obAKFOjVVf&Jr=1ZD2kZ=;<03&JsY-6
z_Wr`{+uCv+zuk^$fLDO0h`8(~$kG6OJN>m(k`Y+#%@h7l$fi=;-{)>w_oTWYV!XJ*
z91kI@yIIuyyPqYI?}mGJV#(k3igo|(C$qflPb)C*Hy^skAiZz}G75u^Ih|0PO>u+1
zl1BD>s&ruHlh=doZAj1(&D1@ccN`XDP!guC<2!37tviN}DH&yv+M?i6Pf3aL9SEv@Br3F&YuUyk41Efy(vphl_oCD#1XBjk!zEm~AVqUVq%rej6N9Ip~9
z#JDRMyZL<18Z=uKl$%Q@FE8H@fzWbr#KgtLb@}Sw@Bh)t%1U>4_wQUeg3hk4fuSKf
zHnyljwSw_NH3qD%cJt~>?D^YXV=tr-;Zvxp9V(AyO56KAXJL<{9^%XWri0H*%|hh-
zpzPe-#|0Bwl~K2%_w?Th7NM92@l)WS8yOkZSVN=XaF9$SNk@(4guNien;;P4NYt*l
zEJW8J$ht7*x>j3L4}mqff3~oo3rs4GLm}8ck|8oMJgm-gFP;5(?4qLSGc%vG>p*g<
zs`%g~PnOEcN_14vsLP>vPQ8v2DhQYe$bW{`b
zejZsdBNDW+u`%cE@u2#8!TnaGZD(gEbC8uC+w8uN*xxO6YS6yJ6NT`}y{YC9cQ
zSl`^NvRxEDK0YQaxtEbdP=G+%ZNQDu
zF)+l($E!i7p5I^nGcyZ|`1JIl-SRdn)m;~*-!1^VVxC5Qhq;u1L9?Cvi%nC{b^j7R
z_hAKVM6ES64j3IxDe`%-(WM9GX8>&|E44N#-q7B8Zby*P07YSwxVz>F3kysAudgvN
zGt+Q!#Rg##qa|Q2c$ZQ$2U%O%H`i*XWS9rM(nVFc3E{`|#`*f+x^os#i
zaHgHTtLMe;h1h?^!cxp$#B_hk+Ws4%WgVQuX|ZPh#$bTMTVQ)>Om+}|6m;I$l)nNf
zi}F1oVZ-e$k6XLzM6$KzWB@6_O6jZl7D6H-)sG)P7Q6go`#%?$^;6hw($dnvi>q`}
z(d6rARtSe$Kxo#Af#HA{J`8kOJ0%}k*~_`BciT
zSy*)T#&nFk@d%o3?faf~Gr=vF{kDdR@{CfaEYn^$FRq82vJ}Emy9}<`&PUb@0=o$9
zLO@YmSdgG4>3cFkk~l1mR(let$OY|R_#n?WHj)f*u1=cDM{hIPptVYD&~d{H>spOp
zM0ka0v;oQWj1(h^btTK~S)}av!Z?3&Bg%|-Dw+J-Qev!Rb`!&R9VS=NQ(l{qaCP^I
zJV3Vz#>p{pzr2a&$D&G=5k$jtaFCk5wP3@;JL;@rP2?~b$(4&I5p}okxj{k4F&)p9
zE77jq`6k|lggj^*sSU+lYPA0~r7e}a`KiJBVzqR1W|cd1pEFV6&Q+9BMKq-qvit{p
z{V_VtQ@jssCtp+g9Zp$%d7dN0I=9if@Z(Lq3H!>eNqemgdyH6{Kb4v{$W9>}IS@}G
zP=OL=l()WTF!GLL$Vl&p^rw^7bWtNZpSf2n{quuW9(Nlj&yg(JKUflu7Tn+hi#>h#
z#A=D@QGu;k+n3MDlQsxuZ5UBf6KdH=xE#7kzS-5Bda%oYIC`@LQZolIx}`+yH{ga-
zVCtiRpL*YCybsXP#`Z$EjNqJ&;An;+by?+J<-}p1v~qsJl|u*pGWrZsm)_4;wJLd*
z-`|WaWx4NebIg=%y5L!USzw!vr&oH^zTwDaIZh{jdtu^xxshZ=&5=B9J*ojuwH?!d
z09yH0JJ0&6(S?R4DoMIy6ZN{Ll8|O%YqIyD23ODJEJz0&mymW6YU{;Iz(6JWv(MuD
zxKG>3{A$m2ohz|){=wcJrHbLWLq^!J{^-vhmf0$iuM5VQog$8dJr-Qel7e2)QnQ#G
zG09Qkm^C$q^IGCpTa1e58txInMU`q}aVH%C
zd&$W*OGD!rT4LSf$hsUNWSNGHSJx-
z&AXmtbAx%<41DH+#VytC#RmHH0aN$m&L#@UHahYYOo`PDk9khvhWh<$@zbs8$OC>)
zj+_9o@U15(EGB#sx1=3fUFh5uZjUoW=4Pw^pijF=L`=(#Ns+#8cX|lTT0R*WnyL}J
z^eiJwd5fH0@dl4VebIU{VtzW+^kmvu91k{UbYm&y+i8nDan`pUgv?({n2+d^+Iu&J
zhJM^UAcNM5oofRg1EC8FF<#*-8A2rj8ZO4X7j2J?g&kH?2uSCIQs{9iSW$9jf`?(x
zwv;1}D_fk_9*G-Fr>Y^Nj0exUW}A+kmM-n_q>qjQO`h?sM;pL{G
z3FB>N9U$e_1V$$catiEZ80NRif8rXmUp3<@!dupGt^
zKX($OAo(Pykn!=>&h$53;0ZpS<>v*H5{cW3wQ$=N*Cw&tX=`v1wr#CgVG7_gh)iJ&
zTQvYm1jBW;DXZHdqqE|vFAPyi#3b3rQ{?q%zSS?u4N88R>dyal4I9~4l>f>bS}aBTT%!?L;zpb(MpU6i?&0?Y!fG~uqIOu
zH!XMg6BI_K{Ljk=tjg3qqAeOan8%`}^)_1gTRcV7I|cW05eT
z^9JGZ<9ertif#U(>B0?@Bi5(a8A&ZZ{cUV`u~(fupS*%C5hQR)N;kd5(L289&CdbX
zOEsAY4l6^Ce}}r
z>dWnoVG!Fd?Oviyze9qi1%lL|clbtMifafVgGL*jd05IIr7I5WelV+i#sfSlDUwgV
zBR$l9$P^Lc!&?TW6y73YJtV_7&G+ISIPZ@`4o>E%$j8eSrE-@zvA(wYAi2HH;qpnV
zj^&8G`6L6s8@~0?vk)F)M!*zhbF$H>el0ORuEKPn=g-qi5|qbungJW(LHw>0vwS7K
zV9uOC3cME`A4v*RcZEj3*Z$TsBHi4t$eDC8PbUY(ihCSw2r7q-@Uk)!eyco2w1Yr*plJWYQlXj_|3+YO&~<$1L-Soj(NBRg{k
z>!}mq3q}Q+X^EGcw74G``y%(i+Lw_AX^UOh3WZWTVMQi|Vol!3`C1Ovsr2kgm`M=z
z%6(^Hp_3t=0SKfRqV^0gwzqRB7DH1dcf)^ief;~m-W>kHT>HD2woVT8(=i39TzZ=PvBV)N$>}UUHva#ZE=INfuNvJm^Qmr0v|87Yxqg^2kpNi+)c
z7`&i;?Z@GW-i5EO;Al&0ut0TyNP3GLYXL53z%pZX>zPRxEBj_lX^MBnXY`Zvh6<|<
zx_`9h|GS2NMK_VCs8G~Pa~x`p^f13A+N5NR
zfY+HbiSqw={;n?s&q#Q-%7=Q?rzPQTgK_*m%>9_us~bUKUYBdG2*Q9Qf?y#5%(%js
z7xpXSt^#taH8?m}?V(nKHt|iILi9;Vn`N-8azuqdE!(KFl_UrHmp9(kf>+Y8levQ3
zdeAiW&T(=}cPmg9(J1TNrrW%#-ZW(c#Xsh7?JgtSh
z5%bM;6w?!({EQUhT<*LH_FKCa%m>A}p=LTxpR7BpA~~b?;$*fHTZZQ0-85;G4q+4$
zteH6!UNd@6H9m+h+t??i_Knm_$>49J?^xU{W%w12wH-k6gy&}-=m!S6olE#z&-U8e!%$7vlZyNxtHJBf*27Xk`l`XN=d$!Tw#vn;C_{_u-JPp3VyM4aGapi!3CFE&5t|(M
zWxmD9LkHu{2JRmbK{P;X)ukSwp?(XOd~*ikzlRh02zV11sXgHdB3k3cVt1*NVHyfN
zv!6f=r6$a3O*5a47Ht#1Gc1<8WsrF*YBG0V0?Vgic3G%<>VZhx!;+}Rj>#e(@lrUi
z7M##>WhxVA6JqNbAa(*Dx)^Dt=xFR2`ev~&aQ+ZGH{h9F`P^yQr5&4C8G+6(T=}Tx
zyO&t}>LdP5QpK&s)JhLh5PMyyg1sSy>c{)N;RRX8T5-9%U#19ebS7EV^s*d_QqI^q@1y6SNq~dYM%@m8{
z9%6&XdQ0VPw}WiOYgQY&%+)-nxviqJ>`BPmw2^eIq(+Os5-gj;f3HvZzoKD3^~|a?
z>Ei~BAKcX|ugAPvT^pt;ONV{-uXpZj}A9n{7=>?bj;h+C*DtPUdnj6dB*8b9|zh~^w
z|Nl~_|9>QS|0lcrXW!$7(*(-PieKE{*4Y;z_lyfL-G8NoWyOBjuH3((q4{4(Q4bTb
zulDsukpn5XSG2S_GSNxjze{<0i&6?ZezFt&iHz
zJBrF}dps|nzy~EIB~?u2#bAwq
zr&Fx1t>HH4LaG9OwS@9Yk>LPm#U}%j_7abRhM829k38?lp{*ZGHa0gl#u{C$JDvyr#5Quz7v=Ue7~(i2c%&xZdxyZ!P!%1-m@@_HMF
zT!7IcYZ_el!a)4igXfaZ0smE#*3p?R(H^&?L}JQtrvTMP&z;Sy
z`U#iH-Fae<>(U@NPX0w|iFL)!PemZFEu#o%sq0^4@*E!LaoOj{c^<#w(-RiW7RUY#
zw5L7juHLVk_zMXeQ!DHc<=+#f#@x0xLJ0G3f)3-#%X8PdRY-0J@t1@OP}N0ZH+|et
zzV^1rLy7ASmrn?a^v>}*e0FM&%>p+Z+#{^0Tb-Ye>WQXsFmM=}9&~Gap14+C9Tn;d
ziDX(+BSY23X&7H5oYzVID%X5I+WtcfUA?}pwM$~eJs{_{$={!8<)ucc{Nkg7Ka~o9
zoC?Pp(F)lIfh?uv5a6E_74DGizK-#7B`&v|7r1$C3^xzYXqml*dFqac{#*-=pK0EG
zNjd6PU)Mk15J3U;6>r#mNEGRnj>koXE24PE~BSeRRTBYaulutwd(nBZY0#$M{)
zF~UqPYMM{hHX(U%A=JRbPBs#n+keg4Z_J(jSyNDiAp?hqZi8
zqfh~PV+vyn#h|w>s`+|jLL94*O$^EWMZ~4}YoXS20X0i~j#oaI{jPRv3tb2m-9fpV
z@y|;&LX>D3Jz@s7C*oy9n3xRK`mJAHW~9b{`yIgE#j(0#=XUGT`fz7`uKG*J2(^3x
z(NXuSx?IQ~mJ7p>VpT&H7LFLRm`M8Cjn7nC8m=RkyW)Qm8V$**$-mLVA65oe(Kh
z^gIk>Se9Hc%+IGj^E$i_v!8=ki1P#s|L4!paI3dzEUiE5#~l=_{Jb(PX`A9xpS_zP
zqTKcyi6~*Ab_1EStt1pPa8@{B;>XX8+jZU@tsGF9%s?{z!`m&idO(+}`Xkg1gf^*s
zukv;sbgwUB;3pLE8BM`%cH$ZSmIHLWHgAx=@gBaV)o>v$#NptDJD|ft!?y>9F%<$l
z*-PUjQoImCSo@!Dy~Ovqmz3O&HH$r`US+u)+))>t+r6v4$ghF(1?z|}2WeIMe1SHV
zH>PTNJ4|pk*2>ec^ZQTQ(l$8v8doX0iGdHdDGPYAeRx(^)5aS692TzBm3z2QTFt#h
zB>+`NL!){EWEWT8Dfw)b>~j^lVj}A9f)ftLTWv62X*kv8a`bL6=S!#YJc$XFSQkc$
z8xSKAX2#AJE=mi
zdIEtU?C;ijm~M8+ZTHCWvXw7~noOP|$|*V|?Zi5=bMakoV&7@=td+y2t13$H$1QF%
zs;Nf)q^B4-N@ji5Z8CT0xqxVKA8PFT-3i8>53T+1)9*9oN$44kdO1>6Q89582lYT6
z+s<%>T8-iC#*pfIoFN1CjtHITU1&tD3Fc|%4Ml#P)dup#sF-)Qc#(LVI4_CNC+?kO
z{*g$E+)#nyHaloQj(xCFGtUXLmiYrYu_%=0{~U@YI&tADx1`vKERQG{q{}J*S!e?u
zZ8D-nCt8k70e_M6sI@W%apTvg^yZ6aD)|`%%JW$V=7v&J93-rcL<^mTu+bmGQ;3sk
zNt^zgOH$j`T~L{#es>cr&zJ{%Jl$i`;3+4--nM5(1t*c4p7x#t*BR|S1j(fz4HYa1
zGh#*B$IXeLE(<;RFD$?#iKO-F!;uNW1L=4Su;}pOJAqeTQw*Xf@3pG0I@q8@DOm=6
z-IiJ7@auVx?G-Ai{hKHRl}?zt1N*JP!KbBNtFb`#wepQvhf+Y^6|edb4im$~@sk!9
zxL7zQ2S@?O0jDNGWSCa-?_!M0`Ef20=>(tHO0T-*6VPh0BB~`|nY2GJtZ3iOY`mZB
z+3nStsbP&s5g00QUPg`4dtm?jrXxD-T(b;pVrpUg%3`@d3M{|&T82K~S|EQmd~;9v
zM}2)gQD<#@qz~!0Z$sG%cB`kvx4kAjcBYdXdjpxs$K@c>&eDUJ+$l*V#@W^o&;-rw
zm;SOG!LCKgn=O4)zq5x)cU6J9#h6Els56$ceport5f7tH?7QV#h5f`&pm%9GF^*qi
zJG0gLlBf+|+!$-}AOr7?Ay=x(q~8YK#k6z>mkAtpu?URwU}Yx{2~alMXN$hr@EK4W
zEzP;JQSdnIFIO1P}XnR)H(4VY_7Y#mZQ`V2_c%*|R0qDBHy*&1R3J+scTRGItpYk?@y3
zt6W>dV;eBlZGn`#=PKeyg5&E&o2WZZ(U@%+Vn-y>Fk%rKV9*)C)O`H)An9n{n
zIgD$1xibh=Q186KMq2!H)FNT$`-@kp*Vi2>*NvY=
z%HG%v%(Wz~_Pb(H@kRQwfqpmnU6#`wTAN3XasxhathW89&f3nJNzJ9sd=c5E%cpbd
zF$7i(nZ7q3>;n$N_QFc8AOi)<%8Z<#$Vz#WG?XzujbLNc>$8JISi0?pIb)yridtOB
zYrqJPkZeA3x0*czf0$ijt4svwGf-l%WA;ZYd{>&Q%&|rE7~&ES3Y2B=*UDSdlQ`<5
zR=&1vq(>Au4g{HO#~x}jZJtBxR}WBg_4#577;4%42-uB>!_U6MKMm*d+uf+wZRd6q
zb&RlLrh1g?f4VDN;55e($PoEfOTb~g^NQGM^Y^sbO2fEQ<@XIC3@pmAgYv#esc6bs
z^!bInlK#4_3EtoF52YwZ%>-~(O1ydhDDPtl(cbv8<
z2aLyYdhR*AnX+hd7Gs$vjBD{D%SgRhT&q#YhT)ti(@R4VD~83t;B~Fxq)Lr8wF4}2
zz&uUFZ+7@?|H6IyNIk^m;X=rG@s_GvKrf17(uNiaMsg3m67p7`-ASTyD
ztb`K>6PRi9hDG?pQB0c@Ps+T5O%(l}lmNAq}msd7}DVD*|Cgg#Vk
zq7b|wtg_A
zn|oDoBbKAysU8LzKr_N9t<;2T9z_WMQZ`(zT38HnN``@zi7@(>hoKOLlb#_>_uGa-
zEFU<@?a5e(?!>&mrGG4M-~_S+)GK4
zk$I2z=ls6QN72fPDGi~q62qId#F=Y}YMHB*mO@+l
z$5&}fgTLs)Z=pV&;||M*OT`@AYeli5x05c-h=IKtC#`4vf%C2I0Y?s*FhklpGm`vm
z*1Mbcw`BH4+OFDJ$Vb$=ULW)PWMxcw=1A$i(l-ohKGCZiOlw?HY9uUcw!<6+NRsQ*
zsgMa-kvhXjN;0usN)J7
zh`BtZWus8^WbHM3<_QtUaDCRmsEP%gAnWMrau_AE;H&`5-NJ*F39
zl-NS6CqmkM2jPI*pE2PQ@0UT%`fA*^Z8zcCr{`Dm84
zZ2$Qignq`#d;-XFJoZCrF@KEqr;#T=f4)Gx=4Xc#jiXUWrx_6+9vE3TbyO3OIzlnM
zJt3diFLihnfdC4Ac`c=n7vdpty`A6g9YhweGkOzca<
zVvaPY5vX|jYqs7nj3VG~oK8$M>=afTC-8BtEZKLT+J4#YG}Hcc&Q3Aa)iIPnUNM*T
z239oOPsDpL`*pKyRO`@$KfbS9-B*w%obh;IguSO~K?L2GefxJ>CfDrs=^4$j3Wbcb
z)&?`Zj>y&m<>J-^PbByb!RNOy7qf4)F`x29{XFPnJG2Sfm{?^rEB|zy-f8c-$u{hP
zyptuR%{N=mNm|jMhw*m>%CM(BE0*p6p4ym;u^}isqrX2Ek%nY`o1RzfU*vJULqs{E$lr^jv!OuhG=dUoK
zw}X5>we}5;0|T|8(b25ds9kTe6z;l|(0y|kmsMy+xVhLO`sqJ>i
zaJ$lRZpp?S?u?kN7{y3zqHue5{<(pF(S?^QD$#kDtk1tZ3pOAW8)-$2qEi=Vk#Hbs
zJ(A2H%}}t|7X+PeaB;-#bi3^riBQOv^dMD|aq4-sm~wb(mQH-B!Y=fK_9%}Ov_RN<
z*cO#%G9|u}9~2a!r?7cqW90Gpd155veh$ZK=hv`>;{K*{tSj&n
zS~sLNjrk$
zh)-J*4kP%o^+DH1-JniilGKZzD%qt|b%
z^FO5#b0T-;YDgDjC(4D>3P!!sBl5Ku>^t7iNfB4U5E*nCjxYNP=IpUjdnJOs+xIlH
z6yU>2T=@!W>Bzf4eG8r<8xGG}*41H){mqto2Re%awaE?L-p8KoIqw|KB(59=QIZ^$
zXNGlMjTu(cbv`vf1L^EwOX(BkS%Z;01x`zZ40i%%f;K;&eyZJntz``+y*N{j12<+W
zWl`xp@w35EY2gippcUY6X!n)9#bmUe9lhSR5ubx5wpwD$I6{whr)l6AVcptjh5jQG
z)+sRVx~7Bd(z`JlRrQJ=4Lit?&N^P1si)8od_}9PSSbKUZ?v3NpIgxDk5{a~|wZjKNFH>cYnMftt4ZUKcnSRhg0Iqh3x!NVG;f8H{vAf|Lt>
z#Jf9=u%$0{Jke#aKuaSY*<6a*H-8r$f7s27mVWhYRCf$JZM*0*HlGp!&rb$TaipCu
zi3baG!HH&BR4@!C&hLJ)U*NG4d{RidH^NWkT#Xz?o4pP^*W9CRy>S>
zMrRxrS0E_l%9k~o+(EgGWUA`eX0muMpU1@YAyxqh1Q
zCT^Q92j3lS(i%9-?|Ya0+H@x4PAE8#NcyNKi(#MHItZWZ+`Vvd!6e2L6eL9VG&p?z
zQw*W@JF&O3BH&tuOf_lz0dhC1;c=H{y=x4CE4n0N)v-UDw$*@2q
zm6)*M`!e>l;a*JxpPpYIasrqISXd&?cA*4B=bF}PK%)LHz;;jP<3}m5?V_x_yl}3g
zitu(WnZ$C};9S>kJes}14oiur>(HBc+K8{E`TS1#yiBbr3vEz|{7uTMwiI!MPH0^>ay@OY)
z8}TG?dKI2YQMu#8=KzYmYPYBt|NfhNXLO25hb(KSPPgd*p<6^iL4c2MFPs73PE7nK
z%&S+Nh2>;r4?Tt&nW0szy6l=_PoearX?`p51>zQ6BZ|kmV{~rfsTeWMU3XF8J>MN4$R(Per~(=+64kPTx1LfqZkdsU*Kmyq`D+ZV`2(b#)~`P8U}w3yxn
zpF@;HuVYR;jBa3?jt#Q1vcK=NV`T>Cms`P{y0y>e44{WaE3*du8tsX}tJ(H?8I@!H
zZ(XvH0hdBEeYqK^|)bM7x
z6c#%!{%BQ5V!vWw7!MW9Q1-pMaWghC*&QafrcreQ@Y(2AHf743=fqXdEAoE)A}srg
zUDpw8WdQCdLl%GK6%PTlrmY&KrKKgG$J6JJrgGQ^ZI)Zbwzs!g4O*Lf-?jjCAbfoM
z(1?f}p!@?MaiZQ=5PU;@y0Y}Bs*P)ANxKte>fO4mCSI8&m0MC)!-^a^=^;BwqH8
z$cA&GDzQGi-LE-*wa<5?*ZT_)@9>U@KrN^sgKB1^mH#=`a_0Pn6{Ti-;?>~8DctFf
z;2pzhpM;fN3(Mw5LAymAb3PdCh(@ynrJ4JJYKM}lhRfo~^t|we-Ef(wH!8?d_F6xj
z<$c9Dcz;*iZXU@DfsT#~USdMOj1pP9^=sU>b}*?e?*CCgC?+fMLA9~#D(ycf;roXtS`7jpjknr<#eilaP$%?sb6-Aj
zX9A8o*}=XPy<6<<=kmSxWwQMqX;n+Du1Lm+a8V0TzUhzcSbkM=U8;#a;ve8L)h8AM
zo7LVcCT|D;PYU>Q^lY%euXIHF+OC>7lE3M>0P$3w!xO?Xv9GmY2k1Yv(-bDOwb7eA
z*nimkg#4Eczs~Qo4*;{Y!;-BH^g2*bcZy0wZ>Oz)qUw;vye^lsTkHfd@Z@EhC7(X9
zz+M8SG}R;=mu8oW(#a{ym2-9FinD}ZyZ<1FE?Y)ivJLl2MQl02OKOOhtlzZ;(iI1+iC$5#^s6+FP
zF3z9^yNyq$@F$aZUw6zcH(I8`XDYu+M-WvyZpd@<@C@|#XaD-uZ;^7lL%TYld4OhT
zFawksoqC>YKK6lMUwfzWTB=I&{RPYyQhO_jZvxFYZ;Xv;0KfATJ{$iK1?^j0oNA#p
zxJki%>b-5Mx%97d3ODSXN9<(tx6{{ge4{Zr&XVlroRc=k2N;)?^eXa7-udZ`iQq!n2B>C>k(wbsOG{5Go-DiV2;AvozS(^|QC
zdFr5p+o1!TsrN#Ln(BO}Ucok~c7?c)>U6!9U^d>x;Jlie?@$B33{*^__uAU>zP{qh
z1@YtK(#jpo(YCvQ&_R2y}n
zo$t-R)7Q@gV0eFPx+t2es&Do6sepPcXs#v*6xqu*>R7^nv}!|`5GGfgaYSXkEzMN93(s9U^U)0b`x_ZUL6n1gpF6_MZ9;#mW
z{IBemMg)U3b^-nd3=p5kzm^Cw0xBwHMMW&vrbXBq(4_(>VxWWN>+O&MKxTk8VbRf>
zx$(-KEB1!qc^0CyVGRdzM$lJ8Hrv_gc%Fisib{b=U$l!}0-5F*+k%dpJ)*SSmSW$&
z#cGP#(aC9d6YC!|ZVs#K1wVfxu%oPwx%jS?zjgp%fov38;4X_b%W`6|
z8;v4MW*76~61L7fL!hs
zlwWL3p*L7LlYMZ}Yjc!YC=@-LKzLtvh(#)>Yf9l+)-Uk5YMNEgy06E+BN4D&coPFC
zuIP7E9^KaMu7VVT6#4}YMSpCT=ZNy(a>n5S&Dgw?Qj(IgQkRkngd&wD*3;}-S9;0qOjo&n|p^Nd)r)$__ziZXv$RJbl{U{7IR
z4gjj$!!=GV4$?MTGX@&^sGM~~X=lsRF_uuxL8Tn|(D2!kXb>GaoHK{id)R3~xRQr0
z-q@bdqRQ;jTi@#xLwxrAlXLcTpMzn=oyE+_+{ebWY>wda^)x8;#@MX&K=p%16e#Vn
zoxho&_OqmYZW(I7GIu6vpwDc%UTPR!VZ>!aHalhZ5KQJj6F-_Vo
zfGghM62@Nj7_giyG&nu1-_B2az{tk7k)LL}I>3-+I+89Vj=*##<$zS#0|YfGTwE(%
zTq|0xH64`h5^
zz9G?ugF@Cyr@sFV^cQIEj%3Ps!XnfjEhbG@N8RaxXqGh|ptDVF%4(?rG);u*O^)^tWva5Oq@{LJURT?XW{41Xan=Ji>0z?Ck8qaX
zZNsupK!Z%3$j5n5oxK0tNt{kpVNmtU{{H?yuq?mx%ZuMXB;|lWD3Y(p3&)N_*N$V|
z@GUqr^s%ABi^@W56k_XTFG}`?Sfhf>zR)Dsc>4~LR4tv9%id86zb=m&<h2?b-hOW>ub9so9B0C6PJUyDK6^JwxT6t(WH!t*owFfQHSYecMlUL
zCJXVt^gAqA;6RS$?M4&)=+ehpP;&n|ddjWQua0r?>LsGB=BKDGvV8d}u&ySIw<4VA
zXmXOp-4BeJ*7Z>N#dSHiumNXASdPf*%$*oQ&qKiAfbVM&LLc!`m)v9R*vVtoq}RdA
zNy025RQA0$s>LY93S0a|s@B}HiKBb{R{--pP+fL`&la|cwPKNa#8zoQAx@O?zAZy*on`aoFjo`vv>Hg7*Olwx>x@IaSM@VpUOzt{Yx
zCbH^g>iupc{oH({VcBAdZY{Z!JsHM8*o?nw+MS8u+-bH#y?@FjPu5*>*oGN1-#f7Q
zX(T6SWUH=(1ryYZC}l4ABe`#9t=#@JA85#a<@0#@Ii+ft4Nq9pfz|*j)VJI98akSA
zi?Vzp6jXyF*>H2>p_nX@2G-9QxW=Y}YSw}cw~~nr&AAR%<}VG^lvkSK7FY&5KY6&Vx
z6*DRvgMj|y!t%@oh11?%5S#@BQ1TmytGk!Pa5FYO9x{61)14odH0C!h#aW$lw_k9b
z!jtHY8F_4D61>>%qZaZkt0^5pK@V*6({%L+=aMi;N*(Tkg_mk)+B!*r9F6
zgEJQGP7}5KZ@x#bbltCxPQ}isPbtOXVEMZ3PUM3ToF_9aebOhB?#j)#8p`^fJO*(7
zuf<@q1UE|o!O!|UerDxIJ~ovBC`tc->A$c5d&PNk^Zb7Unkmz6d}`|lL4-u9g3#gB
zmgRi=`E1#tjI5&(FnR?ICQ}U~?y76tT;0oQw2|7Z!%mT`s?pevY;tyH%*;2HsG`A6
z6VxC_?NeLf^oiaYqf2N|E`LOsGoi(mC%Qz5S%>10&vN`*R+g48<=j?!)2xL-3IixGh|4hRcvt=g&A@b5${DV$1aOeoJ(Q!BqtxXy%*u<#8;BL92?V->Vx{VN5$mzd$;4)g0Po)U)&>jw&)D0OZn&okh0>%_X_k<=4|I6S*2J|=W>Jpi$5pjj^XNl!ST`h(T
z&EqWt-vnRH3VuhXX*GgT)^^>;4LfmImmSHxtSgU4={Id`A(7@
zxdN1YtPb~9ay=bfzEd;@>n)=4E0@&^&t+>cJ*K>i*Fz|Y%F+AG1uSYc)??$`-uuk?WG%|+`3m$*T4fHOIV)3G
z;qUE*=&rkfj@;Hs<+nu&AJ&8mHJt~7Yw6@v2MZ}EDm5QU;raY9c;*PA_hF^3E0n^w
zB+Rbv>v*HIK0u0}-bzTvo1
zBD$6*$z(<3Bux@E)>6GHgx-3WzJ$1%sJ1kFYVOoKODNwS#UjeT^}(oOdh(MH#(2``Om!vU(>4_{2j3tX^Pm-b$SZ=7~ARLB15Bc83hC8oFW
z^Ov=!<5|+~#dWf(cC_aTcfCv96!EMQZg6IK2%-=PpvXZVD{;nISZH5f{t`u0vqP77
zv#Iquj*e*xXlY-8!>{KMa*Ht;Gj>3mKa;k3=aMxPj0fP
zPHp-apUS3LP;?WG8EK@9iSE{7lfaudZwg!3KZ;Pqh;9
zl3|lYq19?rA~f?awZ-4ll%4Xbb*m_mdS(uzxtBu
zPiO=(NkxB?W;!9eUrrBac2wz9S+dE+eJ26{$tzo17rkOLOna4eg!8|Ga}5qysLd14
z^EP~}Y8^5rpE4lm8~{3O)8;OitZj|Z-4dw*ADK$=-af;}fBW$xAr1~sx!5}E%lHAQ
zPoGH8y4t+3jpXNXt9)Mil;Hb}y2!UC0h0XpXM4?@lZ2B5>vOI4si&BBDgXW(vla=T{SpJ-jsNyKjKwQ{_i%VJ!>lYFL~=I{d-0mr-#Lr(OzC&D!XAA=-sOv
zfC+hLOoih47pIBnw-}a2NiY
zC7p1SV!W;Wf0OG8*8iG7|GqP=lMrq7--2Y)p0f4-w@3f~MlDo~@+cLv<)Q~={!`%%
zLK3|WM^hq^G+~yih3@CsN-fqVjMtoY-&Y<8cCUD9ER}E*pWqT^91X&ROz`3^ulr(G
z6tcshB+FbaOR~DO)i|OZrhX$J~vF7z?nbV{~gc;I1
zd3^Ab*WRo`I9V4y@<_FV-ofRwxLg?!Lpq@lG3lhOnI_9`5^o4x%+?gyn(rBzW_%ua
z(+q0apXz+#ayCM!EtW;_5I;e^c32<|^mh?G;J;)JI)X2Jlk6a{BTdlxW_q<20GISq
zLCd|a{PC^zI`~GJMR5CNPjE@&%n6M+u1yir370b0YyvOCIA^BomzN*iHh}29CDy+u
zN!fDxtN
zO$=A;(PTzKZq{Yt@|qSnLOW=)dkyl@)6KwwE`EeV-O;29reqyB)5*Hu8ZLUXwog0u
z#RK>I1=ls3Tm`ZweM`LrUS5*ZW+NiqKU@yDyb+$y#L2oqVcT-A2DG+a6w~99n6E&T
zzzx9mfqu=GHrBSkCr8q6j`D|sPK@^jbo+<~YF3z8R_|jz4MJf|&9|eHA7iYSQt4ME&B5y#B>!fcRY(_C@=`~Goo16#%cAW^!{bgB@at>xL1&%zc*3;
zH)ObyhcXEPmJaUCpXdH6Pf^Bm@sV!v@^b{y>CF|FykXK$jp-h)?;MIwx5m4(rmdMG
z9(Q2!W^j66+|d4a-p0^g0d>bp=-tCaXug%Vu~h~SPU@72;;A`%*|%ZHy^gRy(DK#)@t74Dm)NFCg`LwUS(|wwEMG|<(FEY&6=yv
zWQlpL4mF^CpMFuD>(gZ}XGIm;XRr4RqZ2^o
z-xtIlblAkgN;(f$kdL5rII1gl&%j}u(D>N#r)E<13B3NhGy{M1hNC~Puu=HS~|Kt1I4fxMQ=rX{7gL;!obAsP{-l+469>tNp!0e7f}M
z5OP?Z%W*)@429bZU)AVF0$Ao1RLXwbHswaOo-)XHgl6^LF8bsts03Csm4htAmfPMt
zUD|+StYtrL#&S8+^K~fil`3@jWA;L|)I2$DzV!KYHRxULZxoFMrVfmJxA>UB)t5}=o~>GP!)te+*5Osmr7XhMTA8VFoY0R_`4|C7?F
z7P~yT%jMhNIrk}3A)~crz#h>nCMR9cr=;V^zA%!rRf{(CVAbH=uNlut;eo!Ts9YU$JvGMZ^3=iT6~
zYOuCnF&w<rMfwNMBe-Bw)Z`WBRIz0?KV-B8;ayn4HNI@MnNBlRm}
z6+pN9P^OZ_;}RtftS=$b#}+vJ06mUMggm%wQDx3|^?~8ExYqHN&|8N;Y2-Abb^1NOQg?BL0u$zLuaFY0HU>N>`HTY9q4>`I8oOK<#bbbLr%c
zZyT^ElVI4&9EKPvWIT%xRrrjB++LhW45{Nyb67MMj5Xi&q51ptp1Kv+Yei{mtugn0
zG{zzNfMSo7{mbug7Ey^mtEV52Q%=aq(rttS!INs1DiTh?KHolX%@gm;DlycL18`F>XvG}IEKa27PC?A-zJ{%>)Z#t%p$ot|rG
z61f{3z5t((@^9ryV3cPDr!D#Wr~QN6iS>l@rzDc20hW1vPIAqZ2s(LQrT(+8$Madk
zNcTQiq4U-xL{_jos?C(-`x4(ngNHH37UpU9I+Bv$u>~fHq!V1qIOtZb6^=&BB;L9m
zKNskcN9<})3RLCi=Js(YiXW*`hsuK%;)i{IF4uu>Ryc!fgByh`BIH_!vU&XK-%g-q
zlI9E&TX^KJhCoVvp{X4jGy4PBfglfi4q9l{13jlWM!&b#Xf%hDd*kh+rK%{QeljygSKbU9k0B(cPyrzz@;Axo
zu^1tfa*YWEvBwy>$5ZH2N;MHX^?Wb@jn%XvAXAp2VNV>s_$))>8)Bq^cV869(qg=$
zN)$x(DJnGN@AefsG?X}U?x;}HxnG{oS(@ikW$p(m7}Ex9wLWml;Wf3e0WPUo^%~h&
zR=hj=&mEsLKJ|EV9aAODM0@YjO*KPV*~yaF#8ia>)A3Cdc%
zO}NTw*N5AT;3@%p-1cdQSIZbgMg?(xr20%Xo$T$k0(|nusd3q-)sZM9A~9#Z+!$UC
ztyy^Hjql>U)*SYv*@AvC5(UB~GkJ5CQ}~TFv&8(
z+W0=c=+E4DeuvvAUN4SrZo}*A0!@k6lRmkMup0S_fvPmBB5?G+!wXn+8)Opw5#ekLLZxPPzK2mU+oI&F0tlZ8sScfSevQ5
ze>xCbC7N{~j{y*NN3h;0_HBr{V$7
zLPP!91RtwU1FeHzdRp58adCt^6mphQ9M=;-$U8*Dn<1)OLK$RUZP-_3ADxC?}t
zi|wQk1s_D%i6fPanN^tPy$=xhBPXj+lV#plV}Dtk?g_PVQl}&W{U_OIVBUgF&g?Qs
zM!p^73F7oC(kZd{)%+4~A(%uSX~V=T(PZeo>O2o?>3AvggAb$Mep~iT2+8YD?fM60
zw2$RGM)71;s(0xl$EQIhIJrcF^b4F_v@O6cj!-
zkD!AtCNB1=Id@b-fRR_u47`aumooO9aGosN@v@J9L_3ww&Xbt;<&)Wq9GB{-WD2Km
zL@rM3*Q&C+HKchv#6pvz&nF$p!P-q-iCjY3Darx){>?&m>Y#HWj&b#du>d)2Lz!|E
zX)(0Lm8G$I+Cil}H*j=ItfI3%tx{4wI_rgE-F&6CQ%3x5MOUvl7VAA2NU3xxF2H!q
ztpRv);z8FNxiMM6(y8FXO*t2R?a^uk5pn
z)HMXhVG5@Ohk0x{U0rtjqHAfs>dSa-t&QU}^su)@{8&6Br$hjd)k~DMmXH(jJ0p{u
znZXo)Mplq>3ldip-QkV_FvWG(BW9Otbh%8;EdI!q(to%mlFYs-w2|;ok{(~T!6ylu
zD7*Chox~Ii!*xg#MzYt1~dTuyVX6Yvqc^*xi0f8n(MLh_ISEEq{l^A%iQ5OXN!Vwfw*3R~
zXu9v2qEq8PKAm0vPK)u@0-_$aDRO&QOMga*a*cRKLzWBsXnn(hVWKcV*lTdfowB)(
zGc+j>D!-Ito-Jv^Z&!N#r`gJ-*tadtM5m_V?icQa#zLeA#V4#pe$I$9FD204!9Hr~
z>c|0uyYsePU=m7exfD|bnTd&LLZM=!L?Q>^^(KI$S~$6iUoduACeiNc2k!+H|FUX{
ze+hpv=6##@*4OMczud0WTjKEc`n3ZUTlRv`=3(|+P8V2+bncK^F_iCYrw%Qj{b@(V
z#%!)}cgGI!#VwP{R)BdBDE3|6H3H3%=62yQOV
zQ22IhI_Y;`$IOOIOqTsH`rTpnAz6B);>GJX4a!*&=)Uuu{gF+gFqo~&TU2Yk_9*;V
z$E(5BOSUH0$iVr`AA{Cf&U<)Zbi!4&zp74XtnI*p>w~Ye9g9HlF(zdLLt3Wf0*l?0
zfCx>LfiI&S8#6p~g{V@Ht;5lXfh{d^=fgfs=(}!T1Sv5ZZhg{}8h*l}dN|wU$J!E^
z)VrP1d)(KvE}~coW`W>e}I0X|xea1F?
zH4jZ1%SgCes~$~E=AUWiTIV<_VOwa|+lZ|{eB
z5iQrbO1biUDu8AQrGtDZo+i3HkFYUVPjPvQp#VnpZ1%wdw?pQ=uMx_RrFz3zAIoMm
zuH;LTM5Te${Sg@szGc?x>eOkLVoUM0^2%c3dVgf!t~Qf}4x)DI*oFExHof@jn=-!C
zfNx#cOYP=?kwwy}$}W5Ap!Gt7zC|q%c73+LJ^Q2<%(S^GsQ~TiFPSfOelx_NT}kB`
zc>*djq1|6*2yWRmgksz1rn5Y}S*&}%OH_G_)0%w&r02$0niNO6*vtKY=Y(a`?mh6YxX2QFPH3hZte8`xy9VDqU83$U1mzv~CCcI0-W;KIzP?S?rn8sw
zb_jTQf(+Ih45BhQ!|t}D&GGa`xAek`+fJ1sZ`!e;j^%6!Mo0*qJY|j+f%4idEUvs2
zyjaqfE?4Z{YtA+V1%bMA1bh}->;Ms%o>x24GN}x&1RBTvx#|^d5Kzv${Q+GlIC|ty$Z)JEgu!d4zGNe3Wr@`UfOE
z57qPdicrzJ&Am~7P|Vz9W-9fgSs-9-AEiDC>nQ
zd%c7IHx?|QOL)S+U=h&Cs34=BP@@np;6mt!pWjz7=uWYw6{wUh{m|YwYxZ~grMfZu
z9;pKl-JI8k8IU?Vys>VlBO(9k-Vlhjk&nuq*ot{4@BQ*`fOfiAm3M|23$Co`;5`bm
z{W53sg@C@Vvd!|E?j_$ZERwz-P83G-3;=n8y1xj3`1CDjm2G1^Z?&-C24r`2Qm3L+
z@h{MGI~#5lasN|ewyKpbA-^ZvKaevlG_({1I8V4iPW&I-j6q3RGOG|s<`eg$%3-b1
zLWRS6Q3zu8rb7GuJsg!%L8)4|ZB`)!G9jq7LZ#+MyosfMSaLgTefdX`1cZ}*A}U(`
zJh9Vd2pQ6Ue|~5a|G&ZYor>1~=0Pyt|1U1jO{e?6q6zB>CvE>vm_Es}X3H~&f5B`r
z%VS&P3l}f1O1Wy;eGI#yX_H-kCli;v?nmb;24xY
zV}sy&Zj@UzvOPz`h*k>roKnG7b2*DWMo0n{#ZyUe$OC17Z%pQ6rjvru;>S}_8wyqV
z1^c)J)&nXC6FDQ^IT2BZng?se`vY9rosq2@-&tetK&Dq)G_wTB%{UA9ZXm<4p*=R-
zH(T6=;!NChmQ${-WyB(bl$KaVHnt>)quTr_^FRNR*xdh-*IuLHX_9HCkVHaZx&CVl
zFn;BCxaQ>KM5@>ByaP&Sh3K3vnQ)>>XlP`_BqSg^PdPPDXl4|gDCijqn3!-P(ESd~Y&H65FL#wp-H~GA1Smm{R^sgxvSPVcPheu5
zc4$N!^(iNfw80jNOZSC^d^t~zDGMj#=}TJTts}^h5F>WN&2$CEMh*oMmA8oe!MNQm
z8J31_esR9{g1kQ~9GJAC8~62MThBwzqIBi}5!ZE5{d}(*Nr?H|(#h?7-izgJSbH
zo_TXiuq}9lVyCv(gM+0D`!UONqV9`mNTgu0Qe^qmf%`%0U&s48VueT|zpBwDY;44K
z58ZxoumAd5*8|`%NOUXZvaL~ohoEbLVwiXdrp^CUu_c_x`UKcl1SPpPU%
z5ky`|rRys(WSz3RqHv%ekb%M{oidV@`K6EHq_c;1XF?fvT>K!2eP)Ypq@9AMHM0bb
zE2*g5y18I@8Vy}Bl}2EVwAN)4Ja6`k%cf45q`C$r$Z8++?5??wS_gZ|;7IwB%~mac
z;B_rgeBtO({OdPdgG_-AswMBFMaOgb7+VU5!%&ny4kLJG$6bAglL6$q{Q;Z!i;Mg(
z0F?1=Tk0{7w(q>SI`INt)Kf1j@>|ut+F23q&3>&^Km}43=M~~gW8R8B+ZXa~J00O2
z*CtesyR*Iu5ijOTA)&yRlz|M*m5^c~2WwYB<(AqK`{`rxc2KZ*S1;`#d*m9DgT4QV
zNunql@FJDVeXEtL@ww6+t3^q?v-7Itq0LNGnWH9DDyObtF>SGK!CO|YtWZcVN<}e!i)1s%G2Tct4j1eb!gm`*o8Jd@qQMs;PtWNOnT(*k7K9X#YEscz+6Gk&f
z3`v2T%aqY1_T|;a-fk(bp+V${h?R=8TnBI(&yZR*3#hANth5qAF&?-*e>{8fB9fB}
zr+xqRQ3M*fsU~(LpOL}$g1zf*<#Ky?5VDll4_13L>kPLJrV6%Z%T%^r9N~>5S2K}*C1gC
zuw=lJ{%h12#k4^yF$qLMbHQc7;>y4B$$U@b!`W7xfJqRa{agF{!*Rr-e~xe8
zeLnR~veX&iOJqA1&tra0k^+40ElU>y1p2%
zM7O><7iw|WXb)QIa%5F(aLe^U_xeC`c4pH1i#h6f`->u+f;%O8i~D5vW|IZaF2+*E
z&CdtM-O*j^ozdtSyjBdcquti`Gzvz&RQxCX+GhN8Nv{yd~mE=XU0sw6Y(_!SnPRxGJKfzakZ4Jl=R4M}W!
z<-@qY6Gp#_dQNR-_7cIQIrsa$`MICk@;aoP*!k#~S%%qhtXlXkWF*5S
zW}B-Vpta_>JNQBP}Mg2C&-
zA2IQH*llXFI>6v{7o5$XAbf+nQhfR?X#U?953+w8_KC`ol=P
z(tbrIn+xl+g@fTJ#_PPE9w2qW@KmuZg}2*1xtWaTx){+gg8pFlI*&w4nXsXxL$W>+
zPI3^~SF=wM8wwEtWVrLt?0nk_I_u`%=AU9e0fE_U8sPjoI1ivu!HdkUR26+X7js#5
zBevZ`%MM0ZQ`1teJ!;;2+0MG*Hlj1h9&qXQF@aq}4VAU5iZL%^ZizfIJU~vfVKmXm
z^vrLF*IZt->TtF)ZSG5?5mu1`C8G=bBQw>!J|zgmfcdN)zj3;x8xJ9pLuK3d3KMLG
zs1lEHYO%(b^xedI2e(JgDC;Sd6{Bz1>YCq3;sm|ebI-d!dGlNGqYn~O_{>Fqjknd+
zoFy|M9aQy0J(PC!7p&wOdruNq%qjKZX<%tnV+Sa^$=M~{5-)J5~JiyfFyNZI*HJW
zXOv)NB-1b4E(0A?E9<6ja!ywDh)*2vCRRRNYgKz)c9v?7oTaD%ou^8_a9sUIYksI;$#g+e|zTtr0~jX0TXpv-6CI4c1DeV9*XP_`?{)Pj1VC_E7=6);Mx)z1D*&Ot+CF{V3`dI5+wpC&e7gJke
z>CrZImNigp&rp8wpz416F7faxHeV#>D*t-N#!y*9F;DT2lTAC1LejJuM%R6(jO+#8#P7amV7-p~myK>sI?1Oub1J;s-GPDDFhE2!kndMCSPHqL
z15WvCq*0D?7QrTcBZAF94n{iL$&n0mp|W)R_?-S?n@4#0+J1zSFtgL(&auXAHIGSG
z!4T|L2MI^tYvk!=vDyn~M72jlPaJ91!7+Q@MzHjhF-80+-{
z*rgOrsy%^@U7V6Po;JmNMbtbcQ0(PE6M;?)W6`v>yS8C&N{Ifm@TSM7p5L-mw)q%5
zzr}^Qa*gkBg{#b_!p^+u4HIfRXf6BvX-=$4B>h)Vbd~*WIbHiC4{`P&zGnh2q7)gw
zGIiK9d!C_es+>rwUnv&>BrZdNp_Bk{WYs4W8dW6iEt_oYcqTj~l3;j;RGcpZ0N`3u
zZ$3aqK-JKI!*GEepyN~|SFSLY$csE}^OaA6sQV*pF9{eF2G70?62YaYfg+3Y!+{ED
z@0*UgjGi)A^+?EDTLa}JPG!i3=f!U%p;hAGrYjL^Y)zRR=LSs3@@d_vB;uLiq1GQ*
z5r}a2IIvRYbAo5yopqcVy1dP{0_r#@H^>NV+M1D`u)0Sszhc4yBI)A1yOjnaS$iT0
z=B{8kd2tPL>ikQUyW;HYJzDq|BxCT9A!-J+{YfBdcXxLm7_7M3>7laP<;6LgJycuE
z1UXxFseRsLg4hgq&RMC~p?;@ZqLkqab`cW#014YkWG`n-b7_p)W!b!UKaBq1x`e?2Iuo8xEGX5mgDWBVfR_DuwlDP)IfP-O
zZxa7FZAyS&a;PzXp?(t=r#@H(6Z3Oze}AnZ;;UQ@;aXCRw4Orf1h5
zGhoTM4-FGUC9S?hgQu(a2US>eAamg~K7@^bN5B
z^OWKXlb-%${x$^-{9DY|bQCm+mB1Tk%)R(r-J
zp;VJ^vvDDU@A6YM7!6h%+7jqJgGu0J7S=tiJZi4M{+8Iw;kXRrCYE*~;ArG3f+Bb8
zBNgKpp)aYi03tRWtubTuVfy{8!k8%NgWd>i_{*@CF^Ne+Zt)}ydCR?+Dkxytj}EvD
zonfvwuBV>`4O3cp&m_N6R0SW;M
z8+RGIIm&o5;m*b6fd{${U7pA2R)y?`^t$m4N7
zsQltD@&p^7rm)Ld7aKkQZTIGQ#xRw-^!ROcI8{t^wt%uVJhcsWsM5|vsS`!9miFR$KzUZ|x7o3Pu
zrtL1NcM60ncI+z|)c|QWMD~X^vor)}&;y#cL$^aTMqw9)-W9Aa((96h#ZM|@{llaB
zs`ixjUYdeUHq=PXA2DYSn=$-8hV5JjJH8|G%$*KP6cKN$+ZfGs6015#{JtP4HzNK8
zM`#fDgFzl~+Qr#fE-AWWy5@z%vj${l!snsdYj{KPcJ`UruOU2-QmwN4(
z9nuEItiL;G2;GZWBSY$2WQkAVg8}lMmu?q9NVAfK%py4tGABli(e5}8NI)QxA)6)_
zW9}+crTE5~!UhkN!J#lU?s8Xbxt5VM{b}y%+SreH$d+3~vt+OH29ao*ffD^#wJ0eR
zCn7awjfOc~L9+Q%FxAe5<>=QBE)+6zHHx>oH411xi8?j4K)H192)b9qy1P)vMeR4!
zFILZA$$im=5+P!h0{-bjjfpO)nX1r)Og10AZfy7r;vXZ|s)L{=p3mZz^LY{s?5Z^~
zLzx|=?T)l1@V8~epDz7K?CsPdGj=155R;@BPNyjc^KLjR(_B-oKCfJhjs`{117G;m
z#oxq3Z+LhHd<Hun~>
zF~^(c;1>i{HmY1sMlfdh^2Q`3~X4*toQ7f|B5gk
z`<4BOjA5uuHU}`fW)X#&1wlFzTc1HMdSG
zLP;^dki{O~VkkJaf}5>EAleW|4<|JXjZPIB_k$6aFe6|H@1yD>7nf1ZXC?gZnaP{rQBj2P@Ok|=f{s|7+@sxx
z36ljr>qEf~vKum=m~3n!v7p-x`S-e9IO?1l!0QLC5h_^f!i^U-r!myVYL+P
zdo(f}UH_Fw+9969I9DcZaZ2C$ZPIuShygd|$#LkdEDlZ6UFgI&qBtc
z%+8vzYO<`}@~qQVUx@swn$V@-SYFRsnO=)~g2si>-cW-FXGY<@$x;F<*2mpRoD{sq
zyfe(IktaLH5G1OI56Lv)1dmb-S$E=S)za*d>n@p}*h@3G1FuV7nY1$H6M^0y*31I%
zJ4N}#b{%=nBdw8)5lQ*Gf0j<#v7aQmMC|%*?x`oco%0MQP%d%jsf+*m-O=5%7nP~a
zJsHx$<$lrFf}F2Bq*lIu-a5jHs~Y)(H1(CST;JMvJg_u-eXHe_FqNeVCS1
z5UpmoPMf*HP81~UUB`T{S3Rh>p2?fZf>;;DuoPwQQkR6&3Z{sa{4m;1`&a{`r*(b-
zHQub4=h}DY7I84L%Wf;1Vz7=uG!MAt8lVb7=)x*kX-Wnq4x(pve`##A?kQ}hu{w*
z-Nl85?;KtT@@{T!1x7}~GB6M{>Y$=rN2cTlaXM}tFsLh9hNms09RUc*H2tAaAJy1+YBN4mG
zqGzZJb)Ux8_E+DPZXcd{F~kAZZ|u$Pc*?z+zn_z?SS7|70TdtW@t`X{xeT<>Yk(7v
zRLegF&m6=bGgPDyl3(@e;+RIQNmTD(*J4)Ku&a5FX)pvnH8X06tY5~@v7BK-wjKf$
zNX*giUs#7;&2HVsV<`j~^qCVYM1qaMCeCWUe}9kmTkG(ISjG-paRpesn!flG&9hzb1KWpo#eQOgj
z7DM|4y(E}o;5Ei{SMrt23AP7{JsX`SUPE^`KZkkA+khyL7PCgQS0>3x{xm}_LBQAI
zEx;7W<6<~psRhdB&s=ISE3vF$_h05m)MtP>o_t0L(g+#=bO?xq>m%cKskB
zd@GiSywVc4$T)A|qRo`4{K2I;g^mjlKyZwCaU6AS!Dhu5?Yl921nydAnxbK|WKW9<
zM=VhL0uz1`lB7@enEI$!F%zJqg!bXv7~D$#ZE=U@-7}r3Gvn|8HK4%(tf(r_H7obz
zT*fl({`9Fm)k^9<&%@%`6ga}{&>GhvMlC}={r3aB{#t57dktcSKfU+TotLdwQi0>T
zSZHNo)Ot|D#MdA7T7n^!r*e<3{Ey|9^7AdR0LrqP=EX3kzCP3>TJoqkrGRLCLjEp^
zSbbV(M0iD|7OG#tzld)c0=;tQ*hyGt_YETW_7TeMoZD0`LaJw7X}>
zQt(50=i6aQKbn}Zx+&)L0)Ut~ozz^aumMWkGw$=kLuyTY&l-77quGJ5)j#=ov%!*r
zg{J`xQS?@@K2OZG;uQLd0h&$zlE<0#&guqmCyz9PH#dE!}vCjZ;li;1ey?PVXx;r(z7yG9W4y#vcYZHt@jRmYgIYp9*^J`Jk>o
zOegic>2%u$HO!1+@saEIR$yGZ&^u)*i)Vsz6>*T61l!&Rf^Refrby&}awsEW4M*&zx;xxk!SKRGYWbjkE%<9isG-2%HPCo*#zx>8ti4^_(4B&*uo?PTw@YOE
z3288ooVN9qEs4=_`BMBQ!;mE{SC+yNxHxvJm0V?KA})-~TvI7S0l-og&l!W>?Q;3?
z!ERND-mAJORxP7PN2N-g)#vG^6B-xVY}L3SZqek&RD)>UAUh_8Fj-B+>JV=W8rGm9S3T1F;H_TXHSXbbR>)dlXvl
zF$D7h%57;8r44jPPD+T-l>ZtV6}IC0f^XpiW0Rea=IY)X@3ZgWlQ3`}z01NUlSEYU
z2=|Qet>=m7y*kAQVF6s5`R3e7`7neg`8(Pdec+%lPKw1|iNfE1T4s@~;{182s
z|4g~v!*2at9g$tf7i-qt0`sYSwC4@z6tGRS=33+#?0Ki~HZwI2%R;3%vOHGHo9}~m
z)^ca?}*(nF=JP1XmSygP4w^@&Xj!P;%`7|-)@6QDajPZM@5lKaBQki^Uj@7BFlJUquXx~5gzi4+lxDQXPHvxSz}g6a?G<82owfAlAZG0sYzPWZ1rwbY}J)IgQ6
zBvbW4-Ql^T%wVB?Ay^zIS7WA0armGoEQ3+EIhhUA^-GLrv}C7=!ZO-dCa5cxl%@FP
zJ^4gGk262IW1^=Va@Gk#--u*>e)?pF(bzvz%muLF@7>|iw$17iO4Fe2CRj{^5r_TJ
zs>C+>AWt5)l#L$q)WkZ9(rr%f??LQPXW(@p?b
z-xq+#%hO%4hXy2Z*G^*SyIkxb!32V~CJDX}T!0GEHP(aclPaxQ|WwRm$
zVE9x_-9`$o`#DQijgIxZMqW=@ixA7UO*NZ9ci_k$FA20A!E?Yed@i2v;?%DN_0&gB
z^&86Q6GjePkgu{-&y2id9Q^Wh6^*~5in*7~UT1nu-}C-ZseJ$^LS~NnA<o5C@<98Ew_8^MpE!y6`Wa&6Zb4WcmT^`Se)j!l6(;|q{L&79(T
zv1(VJhJQ6myvP9;y=#ON@KHJjZ2FfC!@Z4lgKIMicxHRX0jb~leBHeP>!z?@6U
zepNF3aDbP&4>Ek*g>%;84lkDG=TpXVcfKV0=8TgcNa(aZp#GNDl44bDlg`@1>G8wy
zl3wrg*L!NQTWO%VEoa1f)Ag3McVBcShwAwn*WVG)CmMONB!ur|
zLiJ0pZ&J)cA}Ato;pa}1S@NzWs}S83(CrM^)-W>NL(yiFx_Gy#dbg5BO5mKYH9er^X~wq?SI$x}3`F|sz|9;R0N~3XI+=XP__(5`9-H2K
zi1M0tfZ2DrJxsc9GC&V-xN1wMnl~W2#cVW^3W`}JeBm&uUvW5(&1uv7i@&VyEPWvz
zaRJa^^?3D(NSaincn*E>@#ZH~;E7tn9WOgI{cQ{ib_2oxc8#u7_$p`Hc6kR9fsSQW7!hX9t1lC7+
zmVj7w82Q@m{poq}?Gby%;u$GeT_aJWHJl&!pAN~SRy%>EYaNv2^Ku2Uv7K_($(gb_
z@Fu-EMcB(7Ut>N%tz6k|V>c4a$1op$`>}M#uM%QIE;^&uc*hn<%xi~uI0kA*HD3hv
zjZCVI9wYT#s<^#zehsnpf&A5sVnY702}XklEMpE
zMm+E4aQSi@{t$htbjO^G5$#;5|E$#s6Hh+W0o!pF1MCf;(_h~KeRx3YeJgO0%Xx?C
zqTL^ua?I$-w}Y|x&Fv)&V-nlO0{&~r_H*(~KA{5*
z^*&hfkVK5txOzOx=b2j2g#~sYy6E~Fr^iow3Z1tERgVsy`y3vJv;fXw#{B!7&*^LJ
z2#NTPn+rzY<@K)zuu1h#zg^7FN>n-@%6H&=uWpW^lcT`7vx;5NV1+*X6N>?MtGgAH
z50~?>Xd?0Vo}*y3IdlxE#vfi&Vy))xysVnaZV|D@s^>7>yi1NE;L1jx*mGLR3YxZy
z`8m@VpemrI$UuJm{<)nicH!5j#1ENH16^r0fffGd8S6|$cY
zZF{4&(5BZo$&s(}j%6G;5HC?%VS>jUXnccDbKHpU+{I-zI!0?pm)Dd`Z1(bV+!_B)
zhjYd2CNj~wOFw~|sLs-^O~Q+w@(wLY1D7Lt9?hH4^
ztvST$e(^Mb-MGA(b|3019R%Kp$N~#c%vMbN!YWvPl>b{I>O%m?8V!;ow$fsA7Qn2A
zGIAP`>P}#Hw>#wunYSm|!kA*dWBtmhpVJgn;{lw)UR&%GJtBFz>(9}?dvld#{z)No`wOB5
zS*FZyJwq*Dd8a#!DeR^kXYsDkbKGuA;+#YZ3P0}-{~z|=Iw+3qTN^%vK!S%5+yVp$
z?rsSh2$JA3xVyVd2*KSQ0t5(d!QCB#4lwxO?l9=v$;mnQci*b}$)E36Usq8DyP4g4
z_Fj9f)!onYtlP*3cXZ>m?3EuW_QauLs;H`462Z(UC=fmRnN$^GayGLrk~wAKAt6wo
z_Kvfp_3ft&6q&0x0V%T3A0$enADf_d!db~)-+A<@ssubG(~h3rB!9}eM1gxUmcb*!
z*;Yh&hB-DYx@iUE!U*}pXBX6tUCXg+=c(N+4GiaAQG&D%bT>DL(UJs?NqETeI;j-w
z3!U!2Pg~X+CU9N~@D2zwVp7hyJwRTj=zSD%x^;oLSaM<=
zwjE}4@FQ6Szm-gC*g$Yy*fXe;M~qJ>pUrrq%Z)Fk?DEwN(Kd@f01&|a)bX5cf&^%MlaQZQ_eJ-Q&z$qUzNDS97mvw
zt^~RzD7lcRF-|S}Omu$>4!s3XKwKCoHK3SNyDnJZ@Iv1~4NMd3S2?^xj5809qR8dO8ha>_Vj=IDL)L}~rnZxf!T+pueylf@qZRD#hu~>QQ
zV(2{WEjI0CmR)RKDFqWLk2LxB5AWJ-W}(!2`Am{l1WO}YN-gs8-0ycpRbz*`fFoU%;UW^_Q6q~R=OVq
zOl8II5%1%@gTC?4F^j*-ef;?GGXjElK!-Sja@7`7NnDnA0IgC$KwxigFZ+QzSvG0d
zWXObrCKe@UjNlFsb^*}OW07o6ex?`W?EaE71b*Ci;DBn($1(g>|Mqp@xfCy=~fQ_<^?Dq!e-AbDUen3&kP;Bzz1;hoZDQcfyjvGxOBI@?Uy>C4>
zzs?kWy=xpnws|o|!SjVuIUre!9i1reHQWNb(Ty2dS|U#!zEE!~RV&juJB@KNzYj1m
zPgv(sbt;vYUTQ!W*z5ce&QUJm%%3oTt=|-<;@vONy=;!}j;n$0gpy
zD*vhk2ORwe?kQ|ia#I$uRxa5*5Ff
z81+Of{ccWeY_>ovAL4Hr+8v`9`fEn8qro2yEQ@hVbul$BgJTU=t^rlnH{w!f<_q8z
zp^$ecW5VJFIs2mG;%n+Pk_cY-fKX`mMNJ5QVFv5!%kK;jiH08SPT#3Ju8V%G=A#>ujg7?L?`
z*K8I+Eb4w8&JKsN{T82Z@{4S3OD$mzagT6+5N>1n4Gx1p)&
z<&2^740r0VeZbyK2ls2x;9zf(BARN|pMvw!48PYWMsr&nQ1I=0PbIhbbFVL&>M=4h
zjRldKni_ylh?S*&{%hK?qkk~UO@p2Qr}$r!^EtdqmZ1VJEI2ON?EEwz0`9t&s&+4{
zSC|uIjZRF8Peot=2*(V=o-OhRFBbZbhnMlJE4A`fhA3sCZ=^N9mWoPtulHKjTfk|M6@0P;UD&l?WNzJ>g*uWfj1Or|{SUZ&O{b?k<-qYJqwvXvascEvW+0gbeq+S-KB2
zGh31w2Dom`uy-^rbiP_Pwy2()9aE3Lr0d!2N?Q5c{8cSuEPEYzVa9S7I;~eQsyI?&
zE4D#v^TX`{iuuB-E*v=(yq+@`5j`5<=nQ2g;=D=f$h_;Oy|*u}`dQ-Cuarqz=G}8(
zEZ1)xa;&+GN@TPvzBNB+b&$lDL8fW_*xDnKr!m(MK1}a+M_a2LJu|;~Dw*uQdGTW8
zU`LD$<}GX+=RbnqP^sS=I63kHmTKJG7FdR-AGWDk3MP>uuFWgF+o
zC-T4Er#=f#lymo}g3VhMI(*Xiv1PqeewM|J=(4TdzoYVb0E3^+=mUdH1L0o`t)qpK
zSrp0ewT(nh*N{V@nL1Yoq?`bszPUToIwGqMu0WoaYR}{Lrii$AmsgWn(1sWYn4A>z
zw(D^#fk5CUqb2Q6*s@t=`$wL0L=2|j+t|WWAVbR6x_3yJ=
zq#n*d&T`6e5!dfhOgs|ath7C8hzmC*+vrTeCjYcQhpMSUHgZqPgE{1OeidSU7~d5Z
zuN9cUsy{$$bpxl@AE`g~t`eXM;LHtDA~{^Ik6aMDa<`|QlNbEF6h^Kwe^h|M`K7V^v`
znyXoh-N{ovU|&Y*7Byv
z_?X+Q>LLqHZ=}VQ^92h*=QrHOtMi&*Mv0;ci=!K-k~k!#JtaZ4vgSQfmIL`=b^X979|9n
z*&Mj+M45c8QX)}p)<1iitF+)&vlcaEyzTI>g2qwCANAGvnrsL&t>=eVbnhuW
zAsEH`84tndrZAf}bg_m~ZKskR!fHP4(R=J`(PX(rq<5NvikqK2sp$+sp;sWuTK!
zKDCuvy-d4F1Hjq<4Es)54fGLAyTQD&6SBJmTV4Ge8;eu#xGDe3F`J*H>|Oy_tOS6P
z!>ZTzDGp`2T`GC!fxpwoE?2czc!*#n{E2Iw+n%RS9gd8LpO6nw)b!y~-*KnT(aAo;
z&uk5ZjS+ZSxSG}ko#zm#(lcnwRY0n{=ETlaQ1hPOl54rqfEf)l*0Q7-GHP$*CsyWS
zw?O1s_9{aeuKDUE9BC77QTd^beVWT>f
zs9JJ*7++NR>3woiA-vmnv&efeb@21X^cs3I<87mZMIlwx?KR%~G+`*RQ{^m_~f0gtbI?gZnH47o5E
zPRlc@e(VSvSJ=~OPFgHuHh>3}&G2Tk+eF{gNVPI%Yn13%akR<=W=y!1MG5GGNew4@
zPtA4B5pUEEjWWQ~>I$#t$mUl)zEauBE)6YLN|G(!r7mH54_4BJYczc43*~0XRk=6y
zkHNpux;`4cE7oE4tG5KNxg_Dh4ZmTC_3(%39(H=~iVU_iULszPT&G&7oalE*UL6i@
zkFAIuaFGmW+Y5htI28~k5&q&zW^8aXxx?EFCwrb++F5txuE6i@g%HbTUTtxl0B`@8
z5Ox>WN?&cLY|64%wr4OF=(x@G5=Tp|hB|R9NTHV6%If)r0tzGkmBF&!ePSbhE(we-
z@tj_3(XuxVrn-lL;>qGthQ2Xq+n^Ut#`Gl<@4|4X;$-r*MOcQ^kplTd4)jrJyu|=i
zyJw6+*Tn%p$m32L&qre>dE`{goYMqFDMXzQKC9m2wDZ}%Q?tQc0!*r9+ruQU5XheNrg
zO_vYz1zh8b?k!aJ^(F-U>%_Ore&|;uYbmE54o~Kj)Q99Amz%#P8qf5rb5WU6F0%UD
z=GTT&5hrj?G5FXLM}%5eB|XTrN@rhmAl4S1rH!ZVZD^6`3Z)YwdaUj>2j5E!yOR_l>zIA9pns6{bmUZ{A_D3Ij~!3COlO^tQ8OC9AujvV!eKUiO>p
znqz*?jXn=qTZBVwBcrTRS#5pV?b8EwNwmFzOwj9hH?-o7oZ
zFx}9TWSZ@%2)*q8P)CzaGi1_5T>n6MEE(-wBI`N@vSd_kyPP(Vcswc#
zB~K7>fU02o=Q+Zcw~S~FO@j$%)^LXh${pkIxm512?l&fJ$7LN~Oa0vM)*Oi3x+)a7
zzfpM}t9=LYtrm+jJ#07~s0U7~%#D>OX$;sNiuryi9zRNb%$VL6rsB!x&PT9$8*eV`
z#_3(S88qMOf4n(TFPl~s{RxN`L(Q+Jpjd{=`3^>$k7{1JD3JH$yXu;5NvUW|IUmDL
zc}%;C+a*+lp&Y?UI-j4h~+2*3<*2BjNnu^HTHlrEz6(xqZmH-s5EK^5x=o_{b2JVC#6
zp`{UUTsw5ebpS0$%3ZgUw+t{VVkl}
zz35>$DBYrY%28_TJ@oOC$ZyF{b@nm1iCE_fy)qfu7%UQ;9rQ>n)BSdz7F
ztsT;eZy3<*Un@pJzR>xMEa0xM%R)!?aXG_apLUVkUAtKE+uiN8X0pF>JUJ4`G9=GO
zV6XAH>~l|eGZ$r{i1}Ak^W?6MpwpYr?4Qo3ZD?xeJCZY*Puu9QjCanBx;$+^zCJNp
z&w*8K^R4sSmfa@UNEq_@nK$?JWZliI@kQIxwn`@O3cEswt;2Szo<~xg7SvYoaTqq4
z3{8E?%6Wjdie@!>wOR}2`?&ul!TxETv+)8%3$E&RD$#zy@xe$Ozs8niAPYv}G~&pd
zG5RyoQ$V!>uF4l6w~
zA#wEz3+4XnJ-*b?+B|Cwh9|27&vo1?7Mds*=Qbl>s3`2i5_~B!kl8JVRWE!Vn5vf8E4LRC!UPC%hO@kx^6u?o
zVrdl|PL^L4Qh%2m8kiE(tvYwmzxHWJPKQpjdA?yfNg}g%`YduEJEG{YOl!4JpMae^
z%5k@kH*|kq(Bnl}5W;4PgI7u_n#VCL#qCysZBwBaOx9sIDcRlgDK=2xLh$9)
z+tthcJVF20QNmF|ncgF#Z&{AHyph%DF$pEAWD_--t%xuG8XRTNaQP{^exKvUv5Vu`
zchT8VOS<`z<$&W55J<-)?@Uot7b7wV0b$wQ>xBX<$8sxdyY)FvDcv
zi)WrwOqFS>_lxC0=a9WCPh(FVx)rf)HKK9P3!Ty)XY~|0#X@yy`swYiHKtY^BEBWyZBd2Y4I+B$NS=B9
z^Q$Y>{Mj~Rl15TPXw7YVAwnmwjx)a!@n$fu26pdF&0`(C%Y0%!m}We`aXMuN&)Q2}
zW*o3rtDmkPRZZ4c@<{t>^`!A`5}YH#xu+;if!$jno&n#jC*I{{clVK95Sxzpi`+Dx
zJwME#b_ev5bDgji!#TAzHuv_10J-iN;K3kJC8@Kslgi(+q}}Bdy%of>s$Fv?QeA~&
z&?fBY6N1Y^c9%6<2+7Zz;BQ%Q@XhK*GpM}nXhE#)Q)!g!!s@R|I+>_cV(M|aNmDVs
zKK5b5nYlNCZ3;Xs6O5nszfW#IO~;Ux$~qX-@cKr{q>1_UwjJJdKRIpEQFIM;lekw~
z9PMcty+e6g7gnvfR)vi3fX^gdZXn{`pOBQ&
zVwWMV(%$L4n5r$W)Y2ttx2Ifk-{4Teo}F-}eO!pQ7Tbs|mf@iAW*Av%eswBoNlxZF
z1wUNK?9ZKkG%4#ZNK30H{nkUot-fvZ!{*A|qFXLy|R)p)9wAnDCK|6_R@QVHT}oTJrt(i81W?DYG(D
z%ZalCt-yfP#jkw}`+Gl}j1KxV>je(kGB*~+#M|gn^osQ>nW$j=|?>97v~z~*4s=%
zcv2NDh