diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml index 49acdd9..6b8be37 100644 --- a/.github/workflows/lint-actions.yaml +++ b/.github/workflows/lint-actions.yaml @@ -9,8 +9,7 @@ on: pull_request: jobs: - run-linters: - name: Black & mypy + Black: runs-on: ubuntu-latest steps: - name: Set up python @@ -28,10 +27,29 @@ jobs: virtualenvs-path: ~/local/share/virtualenvs - run: poetry install --without develop,doc,test - name: Run linters - uses: wearerequired/lint-action@v2 + run: | + black src tests + + mypy: + runs-on: ubuntu-latest + steps: + - name: Set up python + id: setup-python + uses: actions/setup-python@v4 with: - black: true - mypy: true + python-version: '3.11' + - name: Check out Git repository + uses: actions/checkout@v3 + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + version: 1.4.2 + virtualenvs-create: false + virtualenvs-path: ~/local/share/virtualenvs + - run: poetry install --without develop,doc + - name: Run linters + run: | + mypy src tests ruff: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2afced9..fba3ea3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,13 +25,13 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.277 + rev: v0.0.284 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black args: [--config=pyproject.toml] @@ -40,7 +40,7 @@ repos: - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.9.0 + rev: v2.10.0 hooks: - id: pretty-format-ini args: [--autofix] @@ -61,7 +61,7 @@ repos: - types-requests - repo: https://github.com/frnmst/md-toc - rev: 8.1.9 + rev: 8.2.0 hooks: - id: md-toc @@ -76,6 +76,6 @@ repos: - id: validate-html - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.23.2 + rev: 0.24.0 hooks: - id: check-github-workflows diff --git a/Jupyter/mongoDB/configuration.py b/Jupyter/mongoDB/configuration.py index c7dcded..477ecdc 100644 --- a/Jupyter/mongoDB/configuration.py +++ b/Jupyter/mongoDB/configuration.py @@ -1,5 +1,6 @@ -HOSTNAME="stagingdbtransparenzreg.ioappzs.mongodb.net" -DATABASE="transparenzregister" -PORT=None -USERNAME="db_user" -PASSWORD="secret_password" +"""Placholder to login int the FH db.""" +HOSTNAME = "stagingdbtransparenzreg.ioappzs.mongodb.net" +DATABASE = "transparenzregister" +PORT = None +USERNAME = "db_user" +PASSWORD = "secret_password" # noqa: S105 diff --git a/Jupyter/mongoDB/configurationFH.py b/Jupyter/mongoDB/configurationFH.py index 49493ec..1455831 100644 --- a/Jupyter/mongoDB/configurationFH.py +++ b/Jupyter/mongoDB/configurationFH.py @@ -1,5 +1,6 @@ -HOSTNAME="172.17.38.210" -DATABASE="transparenzregister" -PORT=30217 -USERNAME="root" -PASSWORD="secret_password" \ No newline at end of file +"""Placholder to login int the FH db.""" +HOSTNAME = "172.17.38.210" +DATABASE = "transparenzregister" +PORT = 30217 +USERNAME = "root" +PASSWORD = "secret_password" # noqa: S105 diff --git a/documentations/meeting-notes/Meeting_2023-08-03.md b/documentations/meeting-notes/Meeting_2023-08-03.md new file mode 100644 index 0000000..74cb8ca --- /dev/null +++ b/documentations/meeting-notes/Meeting_2023-08-03.md @@ -0,0 +1,27 @@ +# Weekly *X*: 03.08.2023 + +## Teilnehmer +- Prof. Arinir +- Tristan Nolde +- Tim Ronneburg (Protokollant) +- Sebastian Zeleny + +## Themen + +- Präsentieren der Ergebnisse der letzten Wochen: + - Named Entity Recognition + - Vorstellung Datenbank auf dem FH-Cluster: + - Mongo Connector + - Datenspeicherung auf dem Cluster +- Weitere Vorgehensweise: + - Idee: Kleine Workshops/Teams + - In 2er Teams die einzelnen Funktionen über Feature Branches erstellen + +## Abgeleitete Action Items + +| Action Item | Verantwortlicher | Deadline | +|-------------|------------------|-----------------| +| Mergen aller Branches zu jedem neuen Termin mit Herrn Arinir | Jeder | jedes Weekly | +| Erstellen der Pipelines | Sebastian, Tristan und Tim | nächstes Weekly | +| Erstellen der Development Datenbank-Instanzen je Entwickler | Sebastian, Tristan und Tim | nächstes Weekly | +| Anlegen der relationalen Postgres DB via Script auf den FH-Cluster | Sebastian, Tristan und Tim | nächstes Weekly | diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/Graph.png b/documentations/seminararbeiten/Verflechtungsanalyse/Graph.png new file mode 100644 index 0000000..fb7f0ae Binary files /dev/null and b/documentations/seminararbeiten/Verflechtungsanalyse/Graph.png differ diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/Verflechtungsanalyse des Transparenzregisters.pdf b/documentations/seminararbeiten/Verflechtungsanalyse/Verflechtungsanalyse des Transparenzregisters.pdf new file mode 100644 index 0000000..8fd8dbd Binary files /dev/null and b/documentations/seminararbeiten/Verflechtungsanalyse/Verflechtungsanalyse des Transparenzregisters.pdf differ diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/companies.csv b/documentations/seminararbeiten/Verflechtungsanalyse/companies.csv new file mode 100644 index 0000000..0097ec9 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/companies.csv @@ -0,0 +1,12 @@ +id;label;type;branche +1;Porsche Automobil Holding;Company;Automobilhersteller +2;Volkswagen AG;Company;Automobilhersteller +3;Volkswagen;Company;Automobilhersteller +4;Audi;Company;Automobilhersteller +5;Seat;Company;Automobilhersteller +6;Skoda Auto;Company;Automobilhersteller +7;Porsche AG;Company;Automobilhersteller +8;Lamborghini;Company;Automobilhersteller +9;Bentley;Company;Automobilhersteller +10;Forvia;Company;Automobilzulieferer +11;Hella;Company;Automobilzulieferer \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/betweeness_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/betweeness_networkx.html new file mode 100644 index 0000000..f4ac680 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/betweeness_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/closeness_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/closeness_networkx.html new file mode 100644 index 0000000..d177118 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/closeness_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/degree_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/degree_networkx.html new file mode 100644 index 0000000..57aade6 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/degree_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/edges_path_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/edges_path_networkx.html new file mode 100644 index 0000000..9e68207 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/edges_path_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/eigenvector_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/eigenvector_networkx.html new file mode 100644 index 0000000..8da2b79 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/eigenvector_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/metrics/pagerank_networkx.html b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/pagerank_networkx.html new file mode 100644 index 0000000..507039f --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/metrics/pagerank_networkx.html @@ -0,0 +1,180 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/mockup_verflechtungsanalyse_with_networkx.ipynb b/documentations/seminararbeiten/Verflechtungsanalyse/mockup_verflechtungsanalyse_with_networkx.ipynb new file mode 100644 index 0000000..7d07996 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/mockup_verflechtungsanalyse_with_networkx.ipynb @@ -0,0 +1,654 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Networkx und Pyvis - Minimal Working Example" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Referenzen: \n", + "- [Networkx Dokumentation](https://networkx.org/documentation/stable/)\n", + "- [Pyvis Dokumentation](https://pyvis.readthedocs.io/en/latest/index.html)\n", + "- [Introduction to Python for Humanists](https://python-textbook.pythonhumanities.com/06_sna/06_01_05_networkx_pyvis.html)\n", + "\n", + "\n", + "Networkx ist eine Python Bibliothek zur Erstellung und Analyse von Netzwerken. Pyvis ist eine Python Bibliothek zur interaktiven Visualisierung von Netzwerkgraphen. Beide können mit `pip` installiert werden. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: networkx in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (3.0)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 23.1.1 -> 23.1.2\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pyvis in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (0.3.2)\n", + "Requirement already satisfied: ipython>=5.3.0 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pyvis) (8.4.0)\n", + "Requirement already satisfied: jinja2>=2.9.6 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pyvis) (3.1.2)\n", + "Requirement already satisfied: jsonpickle>=1.4.1 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pyvis) (3.0.1)\n", + "Requirement already satisfied: networkx>=1.11 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pyvis) (3.0)\n", + "Requirement already satisfied: backcall in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.2.0)\n", + "Requirement already satisfied: decorator in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (5.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.18.1)\n", + "Requirement already satisfied: matplotlib-inline in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.1.3)\n", + "Requirement already satisfied: pickleshare in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.7.5)\n", + "Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (3.0.30)\n", + "Requirement already satisfied: pygments>=2.4.0 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (2.12.0)\n", + "Requirement already satisfied: setuptools>=18.5 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (58.1.0)\n", + "Requirement already satisfied: stack-data in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.3.0)\n", + "Requirement already satisfied: traitlets>=5 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (5.7.1)\n", + "Requirement already satisfied: colorama in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from ipython>=5.3.0->pyvis) (0.4.5)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from jinja2>=2.9.6->pyvis) (2.1.1)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.0 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from jedi>=0.16->ipython>=5.3.0->pyvis) (0.8.3)\n", + "Requirement already satisfied: wcwidth in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=5.3.0->pyvis) (0.2.5)\n", + "Requirement already satisfied: executing in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from stack-data->ipython>=5.3.0->pyvis) (0.8.3)\n", + "Requirement already satisfied: asttokens in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from stack-data->ipython>=5.3.0->pyvis) (2.0.5)\n", + "Requirement already satisfied: pure-eval in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from stack-data->ipython>=5.3.0->pyvis) (0.2.2)\n", + "Requirement already satisfied: six in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from asttokens->stack-data->ipython>=5.3.0->pyvis) (1.16.0)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 23.1.1 -> 23.1.2\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + } + ], + "source": [ + "# install networkx and pyvis using pip\n", + "!pip install networkx\n", + "!pip install pyvis" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Panda Dataframe mit Beispieldaten\n", + "\n", + "Um ein Netzwerk aufbauen zu können, brauchen wir Daten für die Knoten (nodes) und Kanten (edges). Die Daten speichern wir jeweils in einem Panda Dataframe. Pandas kann ebenfalls mit `pip` installiert werden. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pandas in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (1.4.3)\n", + "Requirement already satisfied: python-dateutil>=2.8.1 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pandas) (2.8.2)\n", + "Requirement already satisfied: pytz>=2020.1 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pandas) (2022.1)\n", + "Requirement already satisfied: numpy>=1.18.5 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from pandas) (1.23.0)\n", + "Requirement already satisfied: six>=1.5 in c:\\users\\tim\\appdata\\local\\programs\\python\\python39\\lib\\site-packages (from python-dateutil>=2.8.1->pandas) (1.16.0)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 23.1.1 -> 23.1.2\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + } + ], + "source": [ + "# install pandas using pip\n", + "!pip install pandas" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Die Knoten unseres Netzwerks sollen die Unternehmen und Personen darstellen. Eine `id` ermöglicht die eindeutige Identifizierung eines Knoten und hilft Duplikate zu vermeiden. Um Unternehmen von Personen differenzieren zu können, wurde zusätzlich die Information `type` aufgenommen. Sie dient in unserem Beispiel dazu, die Form des Knoten zu bestimmen. Durch `label` bekommt der Knoten eine für den User verständliche Bezeichnung. Weitere Informationen, wie zum Beispiel `branche`, können später für das Mouse Over oder die Größe oder Farbe der Knoten verwendet werden. \n", + "\n", + "Um in einem späteren Schritt die Attribute der Knoten an das Netzwerk zu übergeben, generieren wir zusätzlich eine Spalte `shape`, eine Spalte `color` und eine Spalte `title`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " id label type branche shape \\\n", + "0 1 Porsche Automobil Holding Company Automobilhersteller dot \n", + "1 2 Volkswagen AG Company Automobilhersteller dot \n", + "2 3 Volkswagen Company Automobilhersteller dot \n", + "3 4 Audi Company Automobilhersteller dot \n", + "4 5 Seat Company Automobilhersteller dot \n", + "\n", + " color title \n", + "0 #729b79ff Porsche Automobil Holding\\nAutomobilhersteller \n", + "1 #729b79ff Volkswagen AG\\nAutomobilhersteller \n", + "2 #729b79ff Volkswagen\\nAutomobilhersteller \n", + "3 #729b79ff Audi\\nAutomobilhersteller \n", + "4 #729b79ff Seat\\nAutomobilhersteller \n" + ] + } + ], + "source": [ + "# import pandas\n", + "import pandas as pd\n", + "\n", + "# create dataframe based on the sample data\n", + "df_nodes = pd.read_csv('companies.csv', sep = ';')\n", + "\n", + "# define shape based on the type\n", + "node_shape = {'Company': 'dot', 'Person': 'triangle'}\n", + "df_nodes['shape'] = df_nodes['type'].map(node_shape)\n", + "\n", + "# define color based on branche\n", + "node_color = {'Automobilhersteller': ' #729b79ff', 'Automobilzulieferer': '#475b63ff', 'Branche 3': '#f3e8eeff', 'Branche 4': '#bacdb0ff', 'Branche 5': '#2e2c2fff'}\n", + "df_nodes['color'] = df_nodes['branche'].map(node_color)\n", + "\n", + "# add information column that can be used for the mouse over in the graph\n", + "df_nodes = df_nodes.fillna('')\n", + "df_nodes['title'] = df_nodes['label'] + '\\n' + df_nodes['branche']\n", + "\n", + "# show first five entries of the dataframe\n", + "print(df_nodes.head())" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Die Kanten visualisieren die Beziehungen zwischen den Unternehmen und Personen. Um in Pyvis eine Kante darzustellen braucht es minimal die Information zwischen welchen beiden Knoten eine Kante dargestellt werden soll. In den Beispieldaten entspricht dies `from` und `to`. Es wird jeweils auf die eindeutige `id` der jeweiligen Knoten referenziert. `label` bezeichnet hier die Art der Beziehung, z.B. AR = Aufsichtsrat. " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " from to label\n", + "0 2 1 part_of\n", + "1 3 1 part_of\n", + "2 4 1 part_of\n", + "3 5 1 part_of\n", + "4 6 1 part_of\n" + ] + } + ], + "source": [ + "# create dataframe based on the sample data\n", + "df_edges = pd.read_csv('relations.csv', sep = ';')\n", + "\n", + "# show first five entries of the dataframe\n", + "print(df_edges.head())" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Erstellung eines Netzwerks mit networkx\n", + "\n", + "Zur Erstellung des Netzwerks nutzen wir `networkx`, da diese Bibliothek bessere Analysemöglichkeiten hat als `pyvis`. Das mit `networkx` erstellte Netzwerk können wir später an `pyvis` zur interaktiven Visualisierung übergeben werden. \n", + "\n", + "Wir erstellen die Knoten und Kanten auf Basis unsere beiden Dataframes." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1gAAANYCAYAAADZn0yoAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAAB7LklEQVR4nO3deZyP9f7/8ednhtlknUF2iZKyZC/JVp1UfOtEjPpWZJsoa6LldEj1tVXI0nYsdSwRhXROcigVEWVJixGJkaOxDbMZc/3+uMb8pJmYmc/n876uz/W4327nRsNc19MpM5/n5/V+vy+fZVmWAAAAAABFFmY6AAAAAACECgoWAAAAAPgJBQsAAAAA/ISCBQAAAAB+QsECAAAAAD+hYAEAAACAn1CwAAB/cOjQIfXo0UO1atVSkyZNdN1112np0qV+u37NmjX122+/Ffrzf/jhB7Vt21aNGjXSVVddpb59+0qSZs+erYEDB/orJgAABVbMdAAAgLNYlqU777xTDzzwgObNmydJ+vnnn7Vs2bI//N6srCwVKxb8byWPPvqohgwZov/5n/+RJG3fvj3oGQAAyAsTLADA7/znP/9RRESE+vfvn/uxGjVq6JFHHpFkT4k6d+6s9u3bq0OHDjp58qQ6dOigxo0bq379+nr//fclSXv37lXdunV177336qqrrlKXLl2Umpqae82pU6fmfs73339foIwHDx5U1apVc/+5fv36uT9PSkrSrbfeqjp16mjEiBG5H09ISFDTpk119dVX65lnnsn9eM2aNTVixAjVr19fzZs3V2JioiTp8OHDuvvuu9WsWTM1a9ZMn3/+uSTpk08+UaNGjdSoUSNde+21SklJKVB2AEBoo2ABAH7n22+/VePGjf/092zZskWLFy/WJ598oqioKC1dulRbtmzRmjVrNGzYMFmWJcleyvfwww/ru+++U6lSpTR9+vTca8TFxWnLli1KSEjQxIkTC5RxyJAhat++vTp27KiXXnpJx44dy/21b775RgsXLtT27du1cOFC/fLLL5Kk5557Tl999ZW2bdumTz75RNu2bcv9nNKlS2v79u0aOHCgBg8eLEkaNGiQhgwZok2bNundd99V7969JUkTJ07UtGnT9M0332jdunWKjo4uUHYAQGijYAEA/tSAAQPUsGFDNWvWLPdjN998s8qVKyfJXlL4xBNPqEGD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L3X55ZebjgPDmGABAAAAhZSdna2HHnpITz/9NOUKkihYAAAAQKG98sorsixLjzzyiOkocAiWCAIAAACFsHv3brVo0UJffPGFrrjiCtNx4BBMsAAAAIACys7OVu/evTVy5EjKFX6HggUAAAAU0Kuvvqq0tDQNGTLEdBQ4DEsEAQAAgAL4+eef1aRJE3366aeqV6+e6ThwGCZYAAAAwEWyLEu9e/fWsGHDKFfIEwULAAAAuEhvvPGGjh49qscee8x0FDgUSwQBAACAi/DLL7+ocePGWrNmja655hrTceBQTLAAAACAC7AsS3379tWjjz5KucKfomABAAAAFzBnzhz9+uuvGjlypOkocDiWCAIAAAB/IikpSY0aNdJHH32kRo0amY4Dh6NgAQAAAPmwLEv/8z//o0aNGmnMmDGm48AFipkOAAAAADjVvHnztGfPHi1evNh0FLgEEywAAAAgD7/++qsaNmyoDz74QE2bNjUdBy5BwQIAAADOY1mW7r77bl155ZV64YUXTMeBi7BEEAAAADjPO++8o++//17z5s0zHQUuwwQLAAAAOMfhw4dVv359vf/++2rRooXpOHAZChYAAABwjm7duql69eqaMGGC6ShwIZYIAgAAADmWLFmib775RrNnzzYdBS7FBAsAAACQlJycrPr162vRokVq1aqV6ThwKQoWAAAAIOm+++5TXFycXn75ZdNR4GIsEQQAAIDnLV++XBs2bNDWrVtNR4HLMcECAACApx09elT169fX22+/rbZt25qOA5ejYAEAAMDTevbsqZiYGE2bNs10FIQAlggCAADAsz788EOtXbtW27dvNx0FIYIJFgAAADzp+PHjuuaaazR79mx16NDBdByECAoWAAAAPKlPnz4KCwvTq6++ajoKQghLBAEAAOA5q1at0kcffcTSQPhdmOkAAAAAQDClpKSoT58+eu2111SqVCnTcRBiWCIIAAAAT3n44YeVnp6uf/zjH6ajIASxRBAAAACesWbNGi1btkw7duwwHQUhiiWCAAAA8IRTp06pd+/emjlzpsqUKWM6DkIUSwQBAADgCYMGDdKRI0f01ltvmY6CEMYSQQAAAIS8devWadGiRSwNRMCxRBAAAAAhLTU1Vb169dL06dNVrlw503EQ4lgiCAAAgJA2bNgwJSUlaf78+aajwANYIggAAICQtX79es2bN48HCiNoWCIIAACAkJSenq5evXppypQpiouLMx0HHsESQQAAAISkkSNHKjExUYsXLzYdBR7CEkEAAACEnE2bNmnWrFnatm2b6SjwGJYIAgAAIKRkZGSoZ8+eeumll1SxYkXTceAxFCwAAACElLFjx+ryyy9XfHy86SjwIPZgAQAAIGRs2bJFt956q7Zu3apKlSqZjgMPYoIFAACAkJCZmamePXtqwoQJlCsYQ8ECAABASHjhhRdUtWpV3X///aajwMNYIggAAADX27Ztmzp06KCvv/5aVatWNR0HHsYECwAAAK52+vRp9ezZU//3f/9HuYJxFCwAAAC42oQJExQXF6devXqZjgKwRBAAAADu9e2336pt27bavHmzqlevbjoOwAQLAAAA7pSVlaVevXrp2WefpVzBMShYAAAAcKWXXnpJJUqUUN++fU1HAXKxRBAAAACu88MPP6hVq1bauHGjatWqZToOkIsJFgAAAFzlzJkz6tWrl5555hnKFRyHggUAAABXmTJlisLDwzVgwADTUYA/YIkgAAAAXCMxMVEtW7bUhg0bVLt2bdNxgD9gggUAAABXyM7O1kMPPaQnn3yScgXHomABAADAFaZPn67Tp0/r0UcfNR0FyBdLBAEAAOB4e/bsUbNmzfTZZ5+pbt26puMA+WKCBQAAAEezLEu9e/fWiBEjKFdwPAoWAAAAHO21115TSkqKhg4dajoKcEEsEQQAAIBj7du3T02aNNHatWt19dVXm44DXBATLAAAADiSZVnq06ePBg8eTLmCa1CwAAAA4Ej/+Mc/9Ntvv2nEiBGmowAXjSWCAAAAcJz9+/fr2muv1erVq9WgQQPTcYCLxgQLAAAAjmJZlvr166eBAwdSruA6xUwHAAAAAM711ltvaf/+/Vq6dKnpKECBsUQQAAAAjnHw4EE1bNhQ//rXv9S4cWPTcYACo2ABAADAESzL0l133aVrrrlGY8eONR0HKBSWCAIAAMARFixYoMTERC1cuNB0FKDQmGABAADAuEOHDqlBgwZasWKFmjVrZjoOUGgULAAAABjXtWtX1apVS+PGjTMdBSgSlggCAADAqEWLFmn79u166623TEcBiowJFgAAAIw5fPiwGjRooCVLlui6664zHQcoMgoWAAAAjImPj1flypU1adIk01EAv2CJIAAAAIx47733tHnzZr355pumowB+wwQL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DrlwLZ+k1yWVljRTUmoA7xUjqZ/scsXSMgCAt23YsEErVqzQzp07TUcB4EBMsFzNJ2mSpPmy90RF+vXqlhWp48fD9PHHvXPuQ7kCAHhbVlaW+vfvr4kTJ6pMmTKm4wBwIApWSOgsKVHSHbKnTUX91xomKUY+XycdPLhO8fHztH379qKGBADA9aZNm6bY2FjFx8ebjgLAoShYIaOcpMWS1krqIvtBwNEFvEZ0zud1ybnOItWte71efPFFdenSRSkpKf6LC+M4ph0ACiYpKUnPPvuspk2bxmmsAPLls3iVFaKSZR/nvlzSVklpssuTJSlbdrf2SUqXXawayj4d8EFJsX+4Wt++fXXixAnNnz+fbyoh4MCBA2revLkOHDhgOgoAuEb37t11+eWX67nnnjMdBYCDUbA846CkbZJSJGXI3q9VUlIDSZUu+NlpaWm6/vrr1adPHz388MOBDIogoGABQMGsWrVKffv21bfffquYGFPPogTgBpwi6BmVdDFFKj/R0dFatGiRrr/+ejVv3lxNmzb1XzQAABwsPT1dAwYM0NSpUylXAC6IPVi4aLVr19b06dPVtWtXHT161HQcAACCYsKECbr66qt1xx13mI4CwAVYIogCGzRokPbu3av33nuP/VguxRJBALg4u3fvVosWLbRlyxZVr17ddBwALsAECwU2YcIE/frrr5o0aZLpKAAABIxlWRo4cKBGjBhBuQJw0diDhQKLiIjQO++8o+bNm6tly5a64YYbTEdCITC8BoA/t2TJEu3bt0+DBw82HQWAizDBQqHUqFFD//jHPxQfH6/Dhw+bjoMCYmknAPy5lJQUDR48WDNmzFBERITpOABchIKFQrv99tt133336b777tOZM2dMxwEAwG9Gjx6t9u3b68YbbzQdBYDLULBQJM8++6zS09N56CIAIGRs375dc+fO1YQJE0xHAeBCFCwUSbFixbRgwQLNnDlTq1evNh0HAIAiyc7OVkJCgsaMGaMKFSqYjgPAhShYKLJKlSrp7bff1n333aekpCTTcQAAKLQ5c+YoMzNTffr0MR0FgEtRsOAX7du318MPP6zu3bsrKyvLdBwAAAosOTlZI0eO1IwZMxQeHm46DgCXomDBb5588knFxMToqaeeMh0FF4Fj2gHg90aNGqV77rlHTZo0MR0FgIvxHCz4TVhYmN566y01btxYN9xw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AZo6daqio6NNxwGAC2KChZBSq1Ytvf766+rWrZuSk5NNxwEAFNH48eNVv3593X777aajAMBF4TlYCEnDhw/Xd999p+XLlyssjPcRzpecnKwrrriCEgrA0RITE9WyZUtt2bJF1atXNx0HAC4KrzwRkl544QUdO3ZM48aNMx0FAFAIlmVp4MCBevzxxylXAFyFPVgIScWLF9fChQvVtGlTXX/99WrTpo3pSACAAliyZIn279+vwYMHm44CAAXCBAshq2rVqpozZ4569OihX3/91XQcAMBFSklJ0eDBgzVjxgwVL17cdBwAKBAKFkLaX/7yFz300EPq0aOHzpw5YzqOo7D9EoBTjR49Wh06dFDr1q1NRwGAAuOQC4S8M2fO6JZbblGrVq00ZswY03Ec4ciRI6pdu7aOHDliOgoA/M62bdt000036dtvv1X58uVNxwGAAmOChZAXHh6uefPm6c0339S///1v03EAAPnIzs5WQkKCnn32WcoVANeiYMETKlasqHnz5umBBx7QL7/8YjoOACAPs2fPVlZWlvr06WM6CgAUGgULntGmTRsNHjxY3bt31+nTp03HAQCcIzk5WaNGjdLMmTN5fiEAV+MrGDxlxIgRKlOmjEaNGmU6CgDgHCNHjlT37t117bXXmo4CAEXCc7DgKWFhYZo7d66aNGmiG264QXfeeafpSADgeevXr9fKlSu1c+dO01EAoMiYYMFzYmNjtXDhQvXt21c//fST6TjGcIAoACfIyspSQkKCJk2apNKlS5uOAwBFRsGCJ7Vo0UJPPvmkunbtqvT0dNNxgs7n85mOAACSpFdeeUVxcXHq1q2b6SgA4Bc8BwueZVmWunbtqgoVKmj69Omm4wTV0aNHVatWLR09etR0FAAeduDAATVs2FCff/65rrzyStNxAMAvmGDBs3w+n958802tWrVK8+fPNx0HADxn6NChSkhIoFwBCCkccgFPK126tBYtWqSbb75Z1157rerWrWs6EgB4wkcffaRNmzZp9uzZpqMAgF8xwYLnNWrUSM8//7y6dOmi1NRU03EAIOSlp6drwIABeuWVVxQdHW06DgD4FQULkNS7d281btxYDz/8MKfrAUCAjR8/XvXr19dtt91mOgoA+B0FC5C9H2vGjBnatGmTZs2aZTpOUFAkAZiQmJioKVOmaPLkyaajAEBAsAcLyFGiRAktXrxYN954o5o0aaKGDRuajhQwHNMOwATLsjRw4EA9/vjjqlatmuk4ABAQTLCAc1x11VV66aWX1LVrV504ccJ0HAAIKe+++67279+vwYMHm44CAAHDc7CAPPTr109Hjx7VwoULQ3Lac+zYMdWsWVPHjh0zHQWAR6SkpKhevXqaN2+eWrdubToOAAQMEywgD5MnT9auXbs0bdo001EAICT8/e9/10033US5AhDy2IMF5CEqKkqLFy/Wddddp+bNm6t58+amIwGAa23btk1vvfWWvv32W9NRACDgmGAB+bj88ss1c+ZM3XPPPTpy5IjpOADgStnZ2UpISNDYsWNVvnx503EAIOAoWMCf+Otf/6q77rpLDzzwgLKzs03H8Su2XwIIhtmzZ+vMmTPq3bu36SgAEBQULOACxo0bp99++00TJ040HcVvQvHgDgDOk5ycrCeeeEIzZsxQWBgvOQB4A6cIAhdh3759at68uRYtWhQSG7SPHz+u6tWr6/jx46ajAAhhffr0UUxMDA8VBuApHHIBXITq1atr1qxZio+P15YtW1ShQgXTkQDA0davX6+VK1dq586dpqMAQFAxrwcuUseOHfXAAw/o3nvv1ZkzZ0zHAQDHysrKUkJCgiZNmqTSpUubjgMAQUXBAgpg9OjRysrK0rPPPms6CgA41iuvvKK4uDh169bNdBQACDr2YAEFdPDgQTVp0kRz5szRzTffbDpOobAHC0CgHDhwQA0bNtQXX3yhK664wnQcAAg6JlhAAVWqVEn//Oc/df/99+vAgQOm4xQa760ACIShQ4cqISGBcgXAsyhYQCG0a9dOAwcOVPfu3XX69GnTcQqMY9oBBMJHH32kTZs26YknnjAdBQCMoWABhTRq1ChdcsklevLJJ01HAQDj0tPTNWDAAL3yyiuKjo42HQcAjKFgAYUUFhamt956SwsWLNCyZctMxwEAo8aNG6cGDRrotttuMx0FAIzikAugiNavX68777xTX375pWrWrGk6zkU5ceKEqlatqhMnTpiOAiAEJCYmqmXLlvr6669VrVo103EAwCgKFuAHL730kubNm6fPPvtMkZGRpuNcEAULQN6SJG2TlCIpU1KEpJKSGkqqlOdnWJaljh076qabbtLw4cODFRQAHIuCBfiBZVm6++67VaVKFU2dOtV0nAuiYAGwJUuaJWm5pK2SMiRFSsqW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VK5cOQoWQkpKSkq+z4rKysrK95jzcuXKmY4eAEck1ZbZo9HLStqd86N/HDhwQB9//HHuksJSpUrlTrfatWun0qVL++1eAFBYFCzABbZv36727dtr7dq1uvrqq4t8PQoW3Co1NVWJiYl5ntCXkpKi2rVr53lCX/ny5S/ymPNQskxSvMwsFYyRtED2BCswsrOztX379tzp1vr169WgQYPcwtW8efMAHygCAHmjYAEuMXv2bI0fP14bN24s8qZvChacLCMjQz/99FOeJeq3337TZZddlmeJqly5sgdL1IUMk/SqpFNBvGcJSf0kTQriPe2va5999llu4dq7d6/atWuXu5zw8ssv578PAEFBwQJcpFevXsrIyNDbb79dpBcKFCyYlpWVpb179+Z5uERSUpKqV6+e5+ES1apV45CDArEk9ZE9TQpGySohe2r2miSzZebQoUO5ywk/+ugjRUZG5k632rdvH6JLQwE4AQULcJHU1FS1bNlSAwYMUL9+/Qp9HQoWgiE7O1u//PJLniXq559/VqVKlfIsUTVr1lTx4sVNxw8hlqThkmYqsMsFYyT1l/2wY2dNiizL0s6dO3OnW5999pmuuuqq3MLVsmVLRUREmI4JIERQsACX+eGHH3TDDTfo3//+txo3blyoa1Cw4C+WZengwYN5lqjdu3crNjY2z8MlatWqpaioKNPxPWaZpAdllyx/ni4YKbtczVEg91z5U0ZGhr744ovcwrVr1y7deOONucsJr7zySpYTAig0ChbgQgsXLtQTTzyhzZs3q0yZMgX+/LS0NJUtW1bp6en+D4eQY1mWfvvtt3yPOS9RokS+x5yXKFHCdHz8zhFJfSV9KCldRXsYcZikKEm3yd7n5d4ld7/99ptWr16dW7gsy8qdbt10002Ki4szHRGAi1CwAJcaOHCgkpKS9O677xb4ndb09HSVKVOGgoXfOXbs2B9K1Nmfh4WF5XvMOUdju9Em2Uv5lsleznfx0+yMjHBFRhaX1Fn20sOLe4iwW1iWpR9//DG3bH3yySeqXbt27nSrVatWioyMNB0TgINRsACXysjI0A033KAePXpoyJAhF/lZSZK2KTMzWX37PqjZs+dJKimpoaRKAcsK5zh58uQfJlBnS1R6enqeBeqKK65QbGys6egIiGRJsyUtl7RVdtGKkr1vK1v2lMone9oVrfT0KzV27DY9/XSiIiMrm4kcZKdPn9aGDRtyC9e3336rG264IbdwXX311SwnBPA7FCzAxfbs2aMWLVro/fff13XXXZfH70iWNEv//8VThqRIWVa2Tp5MUcmSJWW/gLI/bhetTpJ6SuIFtVulpaVp9+7dee6LOnbsmC6//PLflaizP69YsSIvFD3voKRtklL0/78ulJTUQGffhLnxxhs1fPhwde7c2VRIo44ePar//Oc/uacTpqen6+abb85dTnjppZeajgjAMAoW4HLLli3TwIEDtWXLlnP2CWyU/Qyagi//kaJlv3vdWfYzdJr7My78JDMzU3v27MmzRB06dEiXXXZZnif0ValSRWFhYabjw8VmzJihTz/9VPPnzzcdxRF2796dO91as2aNqlevnrt/q3Xr1oqOjjYdEUCQUbCAEDBixAht375dH3zwlsLC+su/G9g7yn6mjXs3sLvVmTNn9PPPP+dZovbv36+qVavmuS+qevXqKlasmOn4CFGHDx9W7dq1deDAgSI/9DzUZGVladOmTbmFa+vWrWrZsmXucsIGDRrwBgfgARQsIAScPn1aTz/dUM88s0fR0Za8fgSzm2RnZ+vAgQN5ntC3Z88eVaxYMc8Sddlll/HcHhjTsWNH/e///q969OhhOoqjHT9+XGvXrs0tXMeOHctdTnjzzTerSpUqpiM6gL032F6WmikpQuwNhttRsADXsx8imp09Q2FhgXyulXMfIup0lmXp0KFDeZaoxMRElSlTJs8Sdfnll7O8CI701ltv6Z133tHy5ctNR3GVvXv3atWqVVq1apVWr16tSy+9NHe61aZNG4881iDvvcH2igtL9vcX9gbD3ShYgKtZkvpIWiDpVBDuV0JSd0mvi5L1R8nJyXkec56YmKjIyMg8T+irXbt2zmEjgHukpKSoatWq2rNnj8qVY/lwYZw5c0ZbtmzJnW599dVXatasWW7huvbaaxUeHm46ph+xNxjeQcECXG2Y7Ad8BqNcnVVCUj/Z3yi95/jx4/kec56dnZ1niapTp47Kli1rOjrgV127dtUtt9yiPn36mI4SEk6ePKlPPvkkt3D997//VYcOHXKXE9aoUcN0xEIKxMOt2RsMZ6NgAa61TFK8pFQD946RPTULzT1Zp06dUmJiYp6HS5w6dUq1a9fO85jzuLg4jjmHZyxZskRTp07VmjVrTEcJSfv3789dTrhq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9NVXX2n48OGmoygqKkrFixfXyZMnTUcB4HGXXHKJbr31Vi1evNh0FAAoFAoWYEBWVpYGDRqkiRMnKjo62nQcSSwTBOAcPXr0YJkgANeiYAEGvPbaaypXrpzuvvtu01FycdAFAKe49dZbtWPHDu3fv990FAAoMAoWEGRHjhzR3//+d02ePNlR+52YYAFwisjISN15551auHCh6SgAUGAULCDInnnmGd19991q2LCh6Si/Q8EC4CScJgjArYqZDgB4yY4dO7Rw4ULt3LnTdJQ/YIkgACdp166dDhw4oB9//FFXXHGF6TgAcNGYYAFBYlmWBg8erKefftqRRw8zwQLgJOHh4brnnnuYYgFwHQoWECTvv/++Dh48qP79+5uOkicmWACc5uwyQR7ZCcBNKFhAEKSnp2vo0KGaPHmyihcvbjpOnphgAXCaFi1aKDMzU998843pKABw0ShYQBC89NJLatCggW666SbTUfJFwQLgND6fT927d9e8efNMRwGAi0bBAgLswIEDmjhxoiZNmmQ6yp9iiSAAJ+rRo4cWLFig7Oxs01EA4KJQsIAAGzVqlPr27avLL7/cdJQ/xQQLgBNdc801KlOmjD7//HPTUQDgonBMOxBAGzZs0OrVq/X999+bjnJBTLAAONXZwy5at25tOgoAXJDP4mgeICCys7PVsmVLDRw4UPfff7/pOBdkWZaio6N19OhRRUdHm44DALl++ukntWjRQklJSY49KAgAzmKJIBAgc+fOVVhYmO677z7TUS6Kz+djmSAAR6pVq5Zq166tjz/+2HQUALggChYQACdOnNATTzyhyZMnKyzMPX/NWCYIwKnOLhMEAKdzzys/wEWee+453XLLLWrRooXpKAXCBAuAU91zzz1avny50tLSTEcBgD9FwQL8bNeuXXrzzTf1wgsvmI5SYEywADjVpZdeqiZNmmjFihWmowDAn6JgAX42bNgwPfbYY6pUqZLpKAXGBAuAk/Xo0YNlggAcj4IF+NG///1v7dy5U4MHDzYdpVAoWACc7K9//atWr16t48ePm44CAPmiYAF+cvr0aQ0ePFgvvviiIiMjTccpFJYIAnCyMmXKqF27dlq6dKnpKACQLwoW4CfTpk1TtWrV1KlTJ9NRCo0JFgCni4+P17x580zHAIB8UbAAPzh8+LCee+45vfzyy/L5fKbjFBoTLABO16lTJ23cuFGHDh0yHQUA8kTBAvzgqaee0r333qt69eqZjlIkTLAAOF1MTIzuuOMOLVq0yHQUAMgTBQsoom+++UbvvfeennnmGdNRioyCBcANeOgwACfzWZZlmQ4BuJVlWWrTpo3uvfde9evXz3ScIjt+/LiqVaumEydOmI4CAPnKzMxU5cqV9dVXX6lmzZqm4wDA7zDBAopg0aJFOnHihHr37m06il+UKlVKaWlpyszMNB0FAPIVERGhLl26aMGCBaajAMAfULCAQkpNTdVjjz2myZMnKzw83HQcv/D5fCwTBOAKLBME4FQULKCQJkyYoBYtWqhNmzamo/gVBQuAG7Ru3VrJycn69ttvTUcBgN+hYAGFsG/fPk2ZMkUTJkwwHcXvKFgA3CAsLEzdunVjigXAcShYQCGMGDFCAwcOVI0aNUxH8TuehQXALXr06KH58+eL87oAOAkFCyigTz/9VF988YUef/xx01ECggkWALdo3LixwsPDtWnTJtNRACAXBQsogDNnzmjQoEEaP368YmJiTMcJiNjYWCZYAFzB5/Nx2AUAx6FgAQXw5ptv6pJLLlG3bt1MRwmYuLg4JlgAXCM+Pl4LFy7UmTNnTEcBAEkULOCiHTt2TH/72980ZcoU+Xw+03EChiWCANykbt26uvTSS/XJJ5+YjgIAkihYwEUbPXq0OnfurGuvvdZ0lIDikAsAbsMyQQBOUsx0AMANvvvuO7399tueeN4KEywAbtOtWzdde+21euWVVxQZGWk6DgCPY4IFXIBlWRo8eLCeeOIJVahQwXScgOOQCwBuU716ddWrV0///ve/TUcBAAoWcCErVqzQvn37NHDgQNNRgoJDLgC40dlnYgGAaT6Lp/MB+crIyNA111yjqVOn6tZbbzUdJyjOnDmjyMhIpaenq1gxVhEDcIfDhw+rTp06OnDggEqUKGE6DgAPY4IF/InJkyerbt26nilXkhQeHq4yZcro6NGjpqMAwEUrX768rrvuOi1btsx0FAAeR8EC8vHrr79q/PjxevHFF01HCToOugDgRpwmCMAJKFhAPkaNGqVevXqpTp06pqMEHQddAHCjO++8U5988omOHDliOgoAD6NgAXnYtGmT/v3vf+upp54yHcUIDroA4EalSpXSLbfconfffdd0FAAeRsECzpOdna1HH31Uzz33nEqVKmU6jhEsEQTgViwT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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# import networkx\n", + "import networkx as nx\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig = plt.figure(figsize=(12,12))\n", + "ax = plt.subplot(111)\n", + "ax.set_title('Graph - Shapes', fontsize=10)\n", + "\n", + "# initiate graph\n", + "graph = nx.MultiGraph()\n", + "\n", + "# create edges from dataframe\n", + "graph = nx.from_pandas_edgelist(df_edges, source = 'from', target = 'to', edge_attr= 'label')\n", + "\n", + "# update node attributes from dataframe\n", + "nodes_attr = df_nodes.set_index('id').to_dict(orient = 'index')\n", + "nx.set_node_attributes(graph, nodes_attr)\n", + "\n", + "\n", + "pos = nx.spring_layout(graph)\n", + "nx.draw(graph, pos, node_size=1500, node_color='yellow', font_size=8, font_weight='bold')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()\n", + "plt.savefig(\"Graph.png\", format=\"PNG\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualisierung des Netzwerks mit pyvis\n", + "\n", + "Für die Visualisierung importieren wir `Network` von `pyvis.network` und initialisiern das `pyvis` Netzwerk. Mit der Methode `from_nx` können wir das `networkx` Netzwerk übergeben. \n", + "\n", + "Die Größe der Knoten bestimmen wir je nach Auswahl entweder aufgrund der Anzahl der Verbindungen zu anderen Knoten oder anhand der Eigenvektor-Zentralität. Knoten mit vielen Verbindungen bzw. höherer Zentralität werden größer dargestellt." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [], + "source": [ + "# visualize using pyvis\n", + "from pyvis.network import Network\n", + "\n", + "def create_centrality_graph(df, measure_type, save_path):\n", + " # initiate network\n", + " net = Network(directed=False, neighborhood_highlight=True, bgcolor = \"white\", font_color=\"black\")\n", + "\n", + " # pass networkx graph to pyvis\n", + " net.from_nx(graph)\n", + "\n", + " # set edge options \n", + " net.inherit_edge_colors(False)\n", + " net.set_edge_smooth('dynamic')\n", + "\n", + " adj_list = net.get_adj_list()\n", + "\n", + " measure_vector = {}\n", + "\n", + " if measure_type == \"eigenvector\":\n", + " measure_vector = nx.eigenvector_centrality(graph)\n", + " df[\"eigenvector\"] = measure_vector.values()\n", + " if measure_type == \"degree\":\n", + " measure_vector = nx.degree_centrality(graph)\n", + " df[\"degree\"] = measure_vector.values()\n", + " if measure_type == \"betweeness\":\n", + " measure_vector = nx.betweenness_centrality(graph)\n", + " df[\"betweeness\"] = measure_vector.values()\n", + " if measure_type == \"closeness\":\n", + " measure_vector = nx.closeness_centrality(graph)\n", + " df[\"closeness\"] = measure_vector.values()\n", + " if measure_type == \"pagerank\":\n", + " measure_vector = nx.pagerank(graph)\n", + " df[\"pagerank\"] = measure_vector.values()\n", + " if measure_type == \"average_degree\":\n", + " measure_vector = nx.average_degree_connectivity(graph)\n", + " # df[\"average_degree\"] = measure_vector.values()\n", + " print(measure_vector.values())\n", + " \n", + "\n", + " # calculate and update size of the nodes depending on their number of edges\n", + " for node_id, neighbors in adj_list.items():\n", + " \n", + " # df[\"edges\"] = measure_vector.values()\n", + " \n", + " if measure_type == \"edges\":\n", + " size = 10 #len(neighbors)*5 \n", + " else:\n", + " size = measure_vector[node_id]*50 \n", + " next((node.update({'size': size}) for node in net.nodes if node['id'] == node_id), None)\n", + "\n", + " # set the node distance and spring lenght using repulsion\n", + " net.repulsion(node_distance=150, spring_length=50)\n", + "\n", + " # activate physics buttons to further explore the available solvers:\n", + " # barnesHut, forceAtlas2Based, repulsion, hierarchicalRepulsion\n", + " net.show_buttons(filter_=['physics'])\n", + "\n", + " # save graph as HTML\n", + " net.save_graph(save_path)\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alle zusammen ausführen und ein DataFram erstellen mit allen Nodes je nach Kennzahl aufgeteilt." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
eigenvectordegreebetweenessclosenesspagerank
00.2417030.10.0000000.4166670.053313
10.6330390.60.7777780.6666670.280074
20.2417030.10.0000000.4166670.053313
30.4494590.40.6444440.6250000.187550
40.2417030.10.0000000.4166670.053313
50.2417030.10.0000000.4166670.053313
60.2417030.10.0000000.4166670.053313
70.1716110.10.0000000.4000000.053491
80.1716110.10.0000000.4000000.053491
90.0767070.10.0000000.3125000.056939
100.2009000.20.2000000.4347830.101890
\n", + "
" + ], + "text/plain": [ + " eigenvector degree betweeness closeness pagerank\n", + "0 0.241703 0.1 0.000000 0.416667 0.053313\n", + "1 0.633039 0.6 0.777778 0.666667 0.280074\n", + "2 0.241703 0.1 0.000000 0.416667 0.053313\n", + "3 0.449459 0.4 0.644444 0.625000 0.187550\n", + "4 0.241703 0.1 0.000000 0.416667 0.053313\n", + "5 0.241703 0.1 0.000000 0.416667 0.053313\n", + "6 0.241703 0.1 0.000000 0.416667 0.053313\n", + "7 0.171611 0.1 0.000000 0.400000 0.053491\n", + "8 0.171611 0.1 0.000000 0.400000 0.053491\n", + "9 0.076707 0.1 0.000000 0.312500 0.056939\n", + "10 0.200900 0.2 0.200000 0.434783 0.101890" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "centrality_comparison_df = pd.DataFrame()\n", + "\n", + "eigenvector_path = \"./metrics/eigenvector_networkx.html\"\n", + "degree_path = \"./metrics/degree_networkx.html\"\n", + "betweeness_path = \"./metrics/betweeness_networkx.html\"\n", + "closeness_path = \"./metrics/closeness_networkx.html\"\n", + "pagerank_path = \"./metrics/pagerank_networkx.html\"\n", + "average_degree_path = \"./metrics/average_degree_path_networkx.html\"\n", + "edges_path = \"./metrics/edges_path_networkx.html\"\n", + "\n", + "create_centrality_graph(centrality_comparison_df, \"eigenvector\", eigenvector_path)\n", + "create_centrality_graph(centrality_comparison_df, \"degree\", degree_path)\n", + "create_centrality_graph(centrality_comparison_df, \"betweeness\", betweeness_path)\n", + "create_centrality_graph(centrality_comparison_df, \"closeness\", closeness_path)\n", + "create_centrality_graph(centrality_comparison_df, \"pagerank\", pagerank_path)\n", + "# create_centrality_graph(centrality_comparison_df, \"average_degree\", average_degree_path)\n", + "create_centrality_graph(centrality_comparison_df, \"edges\", edges_path)\n", + "\n", + "centrality_comparison_df" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.18181818181818182\n", + "4\n", + "2.327272727272727\n", + "Graph with 11 nodes and 10 edges\n", + "0.0\n", + "{1: 5.0, 6: 1.5, 4: 2.5, 2: 2.5}\n", + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}\n", + "[]\n" + ] + } + ], + "source": [ + "print(nx.density(graph))\n", + "print(nx.diameter(graph))\n", + "print(nx.average_shortest_path_length(graph))\n", + "print(nx.k_core(graph))\n", + "print(nx.average_clustering(graph))\n", + "print(nx.average_degree_connectivity(graph))\n", + "# print(nx.community.modularity(graph, [{ 1, 2}]))\n", + "print(max(nx.connected_components(graph)))\n", + "s = [graph.subgraph(c).copy() for c in nx.connected_components(graph)]\n", + "print(s)" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mc:\\Users\\Tim\\Documents\\Master\\Semester 4\\Projektgruppe\\aki_prj23_transparenzregister\\documentations\\seminararbeiten\\Verflechtungsanalyse\\mockup_verflechtungsanalyse_with_networkx.ipynb Cell 17\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m net \u001b[39m=\u001b[39m Network(directed\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m, neighborhood_highlight\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m, bgcolor \u001b[39m=\u001b[39m \u001b[39m\"\u001b[39m\u001b[39mwhite\u001b[39m\u001b[39m\"\u001b[39m, font_color\u001b[39m=\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mblack\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[0;32m 3\u001b[0m \u001b[39m# pass networkx graph to pyvis\u001b[39;00m\n\u001b[1;32m----> 4\u001b[0m net\u001b[39m.\u001b[39;49mfrom_nx(s)\n\u001b[0;32m 6\u001b[0m \u001b[39m# set edge options \u001b[39;00m\n\u001b[0;32m 7\u001b[0m net\u001b[39m.\u001b[39minherit_edge_colors(\u001b[39mFalse\u001b[39;00m)\n", + "File \u001b[1;32mc:\\Users\\Tim\\AppData\\Local\\Programs\\Python\\Python39\\lib\\site-packages\\pyvis\\network.py:689\u001b[0m, in \u001b[0;36mNetwork.from_nx\u001b[1;34m(self, nx_graph, node_size_transf, edge_weight_transf, default_node_size, default_edge_weight, show_edge_weights, edge_scaling)\u001b[0m\n\u001b[0;32m 660\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mfrom_nx\u001b[39m(\u001b[39mself\u001b[39m, nx_graph, node_size_transf\u001b[39m=\u001b[39m(\u001b[39mlambda\u001b[39;00m x: x), edge_weight_transf\u001b[39m=\u001b[39m(\u001b[39mlambda\u001b[39;00m x: x),\n\u001b[0;32m 661\u001b[0m default_node_size \u001b[39m=\u001b[39m\u001b[39m10\u001b[39m, default_edge_weight\u001b[39m=\u001b[39m\u001b[39m1\u001b[39m, show_edge_weights\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m, edge_scaling\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m):\n\u001b[0;32m 662\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[0;32m 663\u001b[0m \u001b[39m This method takes an exisitng Networkx graph and translates\u001b[39;00m\n\u001b[0;32m 664\u001b[0m \u001b[39m it to a PyVis graph format that can be accepted by the VisJs\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 687\u001b[0m \u001b[39m >>> nt.show(\"nx.html\")\u001b[39;00m\n\u001b[0;32m 688\u001b[0m \u001b[39m \"\"\"\u001b[39;00m\n\u001b[1;32m--> 689\u001b[0m \u001b[39massert\u001b[39;00m(\u001b[39misinstance\u001b[39m(nx_graph, nx\u001b[39m.\u001b[39mGraph))\n\u001b[0;32m 690\u001b[0m edges\u001b[39m=\u001b[39mnx_graph\u001b[39m.\u001b[39medges(data \u001b[39m=\u001b[39m \u001b[39mTrue\u001b[39;00m)\n\u001b[0;32m 691\u001b[0m nodes\u001b[39m=\u001b[39mnx_graph\u001b[39m.\u001b[39mnodes(data \u001b[39m=\u001b[39m \u001b[39mTrue\u001b[39;00m)\n", + "\u001b[1;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "net = Network(directed=False, neighborhood_highlight=True, bgcolor = \"white\", font_color=\"black\")\n", + "\n", + "# pass networkx graph to pyvis\n", + "net.from_nx(s)\n", + "\n", + "# set edge options \n", + "net.inherit_edge_colors(False)\n", + "net.set_edge_smooth('dynamic')\n", + "\n", + "adj_list = net.get_adj_list()\n", + "\n", + "# calculate and update size of the nodes depending on their number of edges\n", + "for node_id, neighbors in adj_list.items():\n", + " \n", + " # df[\"edges\"] = measure_vector.values()\n", + " \n", + " \n", + " size = 10 #len(neighbors)*5 \n", + " \n", + " next((node.update({'size': size}) for node in net.nodes if node['id'] == node_id), None)\n", + "\n", + "# set the node distance and spring lenght using repulsion\n", + "net.repulsion(node_distance=150, spring_length=50)\n", + "\n", + "# activate physics buttons to further explore the available solvers:\n", + "# barnesHut, forceAtlas2Based, repulsion, hierarchicalRepulsion\n", + "net.show_buttons(filter_=['physics'])\n", + "\n", + "# save graph as HTML\n", + "net.save_graph(\"./metrics/connected_components_networkx.html\")" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + }, + "kernelspec": { + "display_name": "Python 3.10.1 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/documentations/seminararbeiten/Verflechtungsanalyse/relations.csv b/documentations/seminararbeiten/Verflechtungsanalyse/relations.csv new file mode 100644 index 0000000..bd436e4 --- /dev/null +++ b/documentations/seminararbeiten/Verflechtungsanalyse/relations.csv @@ -0,0 +1,11 @@ +from;to;label +2;1;part_of +3;1;part_of +4;1;part_of +5;1;part_of +6;1;part_of +7;1;part_of +8;4;part_of +9;4;part_of +11;10;part_of +10;4;supplierer \ No newline at end of file