diff --git a/pdm.lock b/pdm.lock index f8a7afd..d5f3208 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "notebooks", "trials"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:fc88dc465a3d04eb53b847d7b58db1e55ce8adb004489102cceea01fb52527dc" +content_hash = "sha256:7574154c6728ede3eaf76a8b1a3b5d4339fcc8f2dc8c41042401004b6583e151" [[package]] name = "annotated-types" @@ -182,6 +182,17 @@ files = [ {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, ] +[[package]] +name = "blinker" +version = "1.8.2" +requires_python = ">=3.8" +summary = "Fast, simple object-to-object and broadcast signaling" +groups = ["trials"] +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + [[package]] name = "blis" version = "0.7.11" @@ -220,7 +231,7 @@ name = "certifi" version = "2024.2.2" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, @@ -265,7 +276,7 @@ name = "charset-normalizer" version = "3.3.2" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, @@ -306,7 +317,7 @@ name = "click" version = "8.1.7" requires_python = ">=3.7" summary = "Composable command line interface toolkit" -groups = ["default"] +groups = ["default", "trials"] dependencies = [ "colorama; platform_system == \"Windows\"", ] @@ -331,7 +342,7 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, @@ -386,6 +397,61 @@ files = [ {file = "cymem-2.0.8.tar.gz", hash = "sha256:8fb09d222e21dcf1c7e907dc85cf74501d4cea6c4ed4ac6c9e016f98fb59cbbf"}, ] +[[package]] +name = "dash" +version = "2.17.0" +requires_python = ">=3.8" +summary = "A Python framework for building reactive web-apps. Developed by Plotly." +groups = ["trials"] +dependencies = [ + "Flask<3.1,>=1.0.4", + "Werkzeug<3.1", + "dash-core-components==2.0.0", + "dash-html-components==2.0.0", + "dash-table==5.0.0", + "importlib-metadata", + "nest-asyncio", + "plotly>=5.0.0", + "requests", + "retrying", + "setuptools", + "typing-extensions>=4.1.1", +] +files = [ + {file = "dash-2.17.0-py3-none-any.whl", hash = "sha256:2421569023b2cd46ea2d4b2c14fe72c71b7436527a3102219b2265fa361e7c67"}, + {file = "dash-2.17.0.tar.gz", hash = "sha256:d065cd88771e45d0485993be0d27565e08918cb7edd18e31ee1c5b41252fc2fa"}, +] + +[[package]] +name = "dash-core-components" +version = "2.0.0" +summary = "Core component suite for Dash" +groups = ["trials"] +files = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, + {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, +] + +[[package]] +name = "dash-html-components" +version = "2.0.0" +summary = "Vanilla HTML components for Dash" +groups = ["trials"] +files = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, + {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, +] + +[[package]] +name = "dash-table" +version = "5.0.0" +summary = "Dash table" +groups = ["trials"] +files = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, + {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, +] + [[package]] name = "debugpy" version = "1.8.1" @@ -459,6 +525,24 @@ files = [ {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] +[[package]] +name = "flask" +version = "3.0.3" +requires_python = ">=3.8" +summary = "A simple framework for building complex web applications." +groups = ["trials"] +dependencies = [ + "Jinja2>=3.1.2", + "Werkzeug>=3.0.0", + "blinker>=1.6.2", + "click>=8.1.3", + "itsdangerous>=2.1.2", +] +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + [[package]] name = "fqdn" version = "1.5.1" @@ -550,12 +634,26 @@ name = "idna" version = "3.7" requires_python = ">=3.5" summary = "Internationalized Domain Names in Applications (IDNA)" -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "importlib-metadata" +version = "7.1.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +groups = ["trials"] +dependencies = [ + "zipp>=0.5", +] +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + [[package]] name = "intel-openmp" version = "2021.4.0" @@ -651,6 +749,17 @@ files = [ {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +requires_python = ">=3.8" +summary = "Safely pass data to untrusted environments and back." +groups = ["trials"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + [[package]] name = "jedi" version = "0.19.1" @@ -670,7 +779,7 @@ name = "jinja2" version = "3.1.4" requires_python = ">=3.7" summary = "A very fast and expressive template engine." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] dependencies = [ "MarkupSafe>=2.0", ] @@ -1038,7 +1147,7 @@ name = "markupsafe" version = "2.1.5" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, @@ -1203,7 +1312,7 @@ name = "nest-asyncio" version = "1.6.0" requires_python = ">=3.5" summary = "Patch asyncio to allow nested event loops" -groups = ["notebooks"] +groups = ["notebooks", "trials"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -1974,7 +2083,7 @@ name = "requests" version = "2.31.0" requires_python = ">=3.7" summary = "Python HTTP for Humans." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] dependencies = [ "certifi>=2017.4.17", "charset-normalizer<4,>=2", @@ -1986,6 +2095,19 @@ files = [ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] +[[package]] +name = "retrying" +version = "1.3.4" +summary = "Retrying" +groups = ["trials"] +dependencies = [ + "six>=1.7.0", +] +files = [ + {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, + {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, +] + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -2229,7 +2351,7 @@ name = "setuptools" version = "69.5.1" requires_python = ">=3.8" summary = "Easily download, build, install, upgrade, and uninstall Python packages" -groups = ["default"] +groups = ["default", "trials"] files = [ {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, @@ -2240,7 +2362,7 @@ name = "six" version = "1.16.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" summary = "Python 2 and 3 compatibility utilities" -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2806,7 +2928,7 @@ name = "typing-extensions" version = "4.11.0" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, @@ -2839,7 +2961,7 @@ name = "urllib3" version = "2.2.1" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." -groups = ["default", "notebooks"] +groups = ["default", "notebooks", "trials"] files = [ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, @@ -2923,6 +3045,20 @@ files = [ {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, ] +[[package]] +name = "werkzeug" +version = "3.0.3" +requires_python = ">=3.8" +summary = "The comprehensive WSGI web application library." +groups = ["trials"] +dependencies = [ + "MarkupSafe>=2.1.1", +] +files = [ + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, +] + [[package]] name = "widgetsnbextension" version = "4.0.10" @@ -2933,3 +3069,14 @@ files = [ {file = "widgetsnbextension-4.0.10-py3-none-any.whl", hash = "sha256:d37c3724ec32d8c48400a435ecfa7d3e259995201fbefa37163124a9fcb393cc"}, {file = "widgetsnbextension-4.0.10.tar.gz", hash = "sha256:64196c5ff3b9a9183a8e699a4227fb0b7002f252c814098e66c4d1cd0644688f"}, ] + +[[package]] +name = "zipp" +version = "3.18.2" +requires_python = ">=3.8" +summary = "Backport of pathlib-compatible object wrapper for zip files" +groups = ["trials"] +files = [ + {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, + {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, +] diff --git a/pyproject.toml b/pyproject.toml index 2388983..7218e7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,4 +32,5 @@ notebooks = [ ] trials = [ "plotly>=5.22.0", + "dash>=2.17.0", ] diff --git a/src/lang_main/__init__.py b/src/lang_main/__init__.py index ced02b3..c6ae768 100644 --- a/src/lang_main/__init__.py +++ b/src/lang_main/__init__.py @@ -1,5 +1,8 @@ from typing import Final, Any import inspect +import sys +import logging +from time import gmtime from pathlib import Path from lang_main.shared import ( @@ -11,7 +14,6 @@ from lang_main.shared import ( from lang_main.analysis.preprocessing import Embedding, PandasIndex from lang_main.analysis.graphs import TokenGraph - __all__ = [ 'save_pickle', 'load_pickle', @@ -21,6 +23,15 @@ __all__ = [ 'TokenGraph', ] +logging.Formatter.converter = gmtime +LOG_FMT: Final[str] = '%(module)s:%(levelname)s | %(asctime)s | %(message)s' +LOG_DATE_FMT: Final[str] = '%Y-%m-%d %H:%M:%S +0000' +logging.basicConfig( + stream=sys.stdout, + format=LOG_FMT, + datefmt=LOG_DATE_FMT, +) + USE_INTERNAL_CONFIG: Final[bool] = True # load config data: internal/external diff --git a/src/lang_main/analysis/graphs.py b/src/lang_main/analysis/graphs.py index 98eafb2..0c524a2 100644 --- a/src/lang_main/analysis/graphs.py +++ b/src/lang_main/analysis/graphs.py @@ -1,7 +1,6 @@ import typing from typing import Any, Self, Literal, overload, Final import sys -import logging from collections.abc import Hashable from pathlib import Path import copy @@ -12,14 +11,12 @@ from networkx import Graph, DiGraph import networkx as nx from pandas import DataFrame +from lang_main.loggers import logger_graphs as logger from lang_main.shared import save_pickle, load_pickle # TODO change logging behaviour, add logging to file LOGGING_DEFAULT: Final[bool] = False -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.graphs') def get_graph_metadata( graph: Graph | DiGraph, diff --git a/src/lang_main/analysis/preprocessing.py b/src/lang_main/analysis/preprocessing.py index adbf142..feecfb6 100644 --- a/src/lang_main/analysis/preprocessing.py +++ b/src/lang_main/analysis/preprocessing.py @@ -1,7 +1,5 @@ from typing import cast, Callable from collections.abc import Iterable -import sys -import logging from itertools import combinations import re from math import factorial @@ -19,6 +17,7 @@ import sentence_transformers.util from tqdm import tqdm from lang_main.types import Embedding, PandasIndex +from lang_main.loggers import logger_preprocess as logger from lang_main.pipelines.base import BasePipeline from lang_main.analysis.shared import ( similar_index_connection_graph, @@ -27,10 +26,6 @@ from lang_main.analysis.shared import ( #from lang_main.analysis.graphs import update_graph, get_graph_metadata -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.preprocess') - # ** (1) dataset preparation: loading and simple preprocessing # following functions used to load a given dataset and perform simple # duplicate cleansing based on all properties @@ -436,6 +431,7 @@ def merge_similarity_dupl( similar_id_graph, _ = similar_index_connection_graph(similar_idx_pairs) for similar_id_group in similar_index_groups(similar_id_graph): + similar_id_group = list(similar_id_group) similar_data = merged_data.loc[similar_id_group,:] # keep first entry with max number occurrences, then number of # associated objects, then length of entry diff --git a/src/lang_main/analysis/shared.py b/src/lang_main/analysis/shared.py index 5011324..9165e96 100644 --- a/src/lang_main/analysis/shared.py +++ b/src/lang_main/analysis/shared.py @@ -19,16 +19,17 @@ def similar_index_connection_graph( # inplace operation, parent/child do not really exist in undirected graph update_graph(graph=similar_id_graph, parent=idx1, child=idx2) - graph_info = get_graph_metadata(graph=similar_id_graph, logging=True) + graph_info = get_graph_metadata(graph=similar_id_graph, logging=False) return similar_id_graph, graph_info +# TODO check returning tuple def similar_index_groups( similar_id_graph: Graph, -) -> Iterator[list[PandasIndex]]: +) -> Iterator[tuple[PandasIndex, ...]]: # groups of connected indices ids_groups = cast(Iterator[set[PandasIndex]], nx.connected_components(G=similar_id_graph)) for id_group in ids_groups: - yield list(id_group) \ No newline at end of file + yield tuple(id_group) \ No newline at end of file diff --git a/src/lang_main/analysis/timeline.py b/src/lang_main/analysis/timeline.py index 766de1a..3f67bb4 100644 --- a/src/lang_main/analysis/timeline.py +++ b/src/lang_main/analysis/timeline.py @@ -1,6 +1,4 @@ from typing import cast -import sys -import logging from collections.abc import Iterable, Iterator import numpy as np @@ -12,16 +10,13 @@ import sentence_transformers import sentence_transformers.util from tqdm.auto import tqdm # TODO: check deletion -from lang_main.types import PandasIndex, ObjectID +from lang_main.types import PandasIndex, ObjectID, TimelineCandidates +from lang_main.loggers import logger_timeline as logger from lang_main.analysis.shared import ( similar_index_connection_graph, similar_index_groups, ) -# ** Logging -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.time_analysis') def non_relevant_obj_ids( data: DataFrame, @@ -42,6 +37,8 @@ def non_relevant_obj_ids( data.loc[(data[feature_obj_id]==obj_id), feature_uniqueness] ) # check for uniqueness of given feature for current ObjectID + # ignore NaN values + feats_per_obj_id = feats_per_obj_id.dropna() unique_feats_per_obj_id = len(feats_per_obj_id.unique()) if unique_feats_per_obj_id > thresh_unique_feat_per_id: @@ -56,7 +53,7 @@ def remove_non_relevant_obj_ids( feature_uniqueness: str = 'HObjektText', feature_obj_id: str = 'ObjektID', ) -> DataFrame: - + logger.info("Removing non-relevant ObjectIDs from dataset") data = data.copy() ids_to_ignore = non_relevant_obj_ids( data=data, @@ -65,7 +62,9 @@ def remove_non_relevant_obj_ids( feature_obj_id=feature_obj_id, ) # only retain entries with ObjectIDs not in IDs to ignore - data = data.loc[~data[feature_obj_id].isin(ids_to_ignore)] + data = data.loc[~(data[feature_obj_id].isin(ids_to_ignore))] + logger.debug(f"Ignored ObjectIDs: {ids_to_ignore}") + logger.info("Non-relevant ObjectIDs removed successfully") return data @@ -80,14 +79,13 @@ def filter_activities_per_obj_id( ) -> tuple[DataFrame, Series]: data = data.copy() # filter only relevant activities count occurrences for each ObjectID - #relevant_activity_types = list(relevant_activity_types) # TODO: check deletion + logger.info("Filtering activities per ObjectID") filt_rel_activities = data[activity_feature].isin(relevant_activity_types) data_filter_activities = data.loc[filt_rel_activities].copy() num_activities_per_obj_id = cast( Series, data_filter_activities[feature_obj_id].value_counts(sort=True) ) - # filter for ObjectIDs with more than given number of activities filt_below_thresh = (num_activities_per_obj_id <= threshold_num_activities) # index of series contains ObjectIDs @@ -97,6 +95,7 @@ def filter_activities_per_obj_id( num_activities_per_obj_id = num_activities_per_obj_id.loc[~filt_below_thresh] data_filter_activities = data_filter_activities.loc[~filt_entries_below_thresh] + logger.info("Activities per ObjectID filtered successfully") return data_filter_activities, num_activities_per_obj_id @@ -109,7 +108,7 @@ def generate_model_input( 'VorgangsBeschreibung', ), ) -> DataFrame: - + logger.info("Generating concatenation of model input features") data = data.copy() model_input_features = list(model_input_features) input_features = data[model_input_features].fillna('').astype(str) @@ -117,6 +116,7 @@ def generate_model_input( lambda x: ' - '.join(x), axis=1, ) + logger.info("Model input generated successfully") return data @@ -133,16 +133,17 @@ def generate_model_input( def get_timeline_candidates_index( data: DataFrame, num_activities_per_obj_id: Series, + *, model: SentenceTransformer, cos_sim_threshold: float, feature_obj_id: str = 'ObjektID', model_input_feature: str = 'nlp_model_input', -) -> Iterator[tuple[ObjectID, list[PandasIndex]]]: +) -> Iterator[tuple[ObjectID, tuple[PandasIndex, ...]]]: # already sorted ObjIDs (descending regarding number of activities) obj_ids = cast(Iterable[ObjectID], num_activities_per_obj_id.index) - for obj_id in obj_ids: + for obj_id in tqdm(obj_ids): data_per_obj_id = cast( DataFrame, data.loc[data[feature_obj_id]==obj_id] @@ -220,7 +221,58 @@ def candidates_by_index( yield idx_pair -""" -next part: +def transform_timeline_candidates( + candidates: Iterator[tuple[ObjectID, tuple[PandasIndex, ...]]], +) -> TimelineCandidates: + """function to build a mapping of ObjectIDs to their respective collection of + timeline candidates (as tuple), each candidate group is separated as distinct + tuple within this outer tuple -""" \ No newline at end of file + Parameters + ---------- + candidates : Iterator[tuple[ObjectID, tuple[PandasIndex, ...]]] + Iterator provided by ``get_timeline_candidates_index`` + + Returns + ------- + dict[ObjectID, tuple[tuple[PandasIndex, ...], ...]] + dictionary: ObjectID -> tuple of candidate groups + """ + + candidates_by_obj_id: TimelineCandidates = {} + + obj_id_target: ObjectID | None = None + collection: list[tuple[PandasIndex, ...]] = [] + + for obj_id, cands in candidates: + if obj_id_target is None: + collection = [] + obj_id_target = obj_id + elif obj_id_target != obj_id: + candidates_by_obj_id[obj_id_target] = tuple(collection) + collection = [] + obj_id_target = obj_id + collection.append(cands) + + if collection and obj_id_target is not None: + candidates_by_obj_id[obj_id_target] = tuple(collection) + + return candidates_by_obj_id + +def map_obj_texts( + data: DataFrame, + obj_ids: Iterable[ObjectID], +) -> dict[ObjectID, str]: + obj_id_to_text: dict[ObjectID, str] = {} + + for obj_id in obj_ids: + data_per_obj = cast( + DataFrame, + data.loc[data['ObjektID']==obj_id] + ) + # just take first entry + obj_text = cast(str, data_per_obj['HObjektText'].dropna().iat[0]) + obj_text = obj_text.strip(r' ,.:') + obj_id_to_text[obj_id] = obj_text + + return obj_id_to_text \ No newline at end of file diff --git a/src/lang_main/analysis/tokens.py b/src/lang_main/analysis/tokens.py index b1436b1..02c05e9 100644 --- a/src/lang_main/analysis/tokens.py +++ b/src/lang_main/analysis/tokens.py @@ -1,6 +1,4 @@ from typing import cast -import sys -import logging import re from itertools import combinations from collections.abc import Iterator @@ -12,6 +10,7 @@ from spacy.lang.de import German as GermanSpacyModel from pandas import DataFrame from tqdm.auto import tqdm +from lang_main.loggers import logger_token_analysis as logger from lang_main.analysis.graphs import ( update_graph, TokenGraph, @@ -19,9 +18,9 @@ from lang_main.analysis.graphs import ( # ** Logging -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.token_analysis') +#LOGGING_LEVEL = 'INFO' +#logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) +#logger = logging.getLogger('ihm_analyse.token_analysis') # ** POS #POS_OF_INTEREST: frozenset[str] = frozenset(['NOUN', 'PROPN', 'ADJ', 'VERB', 'AUX']) diff --git a/src/lang_main/loggers.py b/src/lang_main/loggers.py new file mode 100644 index 0000000..f33302e --- /dev/null +++ b/src/lang_main/loggers.py @@ -0,0 +1,24 @@ +from typing import Final +import logging + +from lang_main.types import LoggingLevels + +LOGGING_LEVEL_PREPROCESS: Final[LoggingLevels] = 'INFO' +LOGGING_LEVEL_PIPELINES: Final[LoggingLevels] = 'INFO' +LOGGING_LEVEL_GRAPHS: Final[LoggingLevels] = 'INFO' +LOGGING_LEVEL_TIMELINE: Final[LoggingLevels] = 'DEBUG' +LOGGING_LEVEL_TOKEN_ANALYSIS: Final[LoggingLevels] = 'INFO' +LOGGING_LEVEL_SHARED_HELPERS: Final[LoggingLevels] = 'INFO' + +logger_shared_helpers = logging.getLogger('lang_main.shared') +logger_shared_helpers.setLevel(LOGGING_LEVEL_SHARED_HELPERS) +logger_preprocess = logging.getLogger('lang_main.analysis.preprocessing') +logger_graphs = logging.getLogger('lang_main.analysis.graphs') +logger_graphs.setLevel(LOGGING_LEVEL_GRAPHS) +logger_timeline = logging.getLogger('lang_main.analysis.timeline') +logger_timeline.setLevel(LOGGING_LEVEL_TIMELINE) +logger_token_analysis = logging.getLogger('lang_main.analysis.tokens') +logger_token_analysis.setLevel(LOGGING_LEVEL_TOKEN_ANALYSIS) +logger_preprocess.setLevel(LOGGING_LEVEL_PREPROCESS) +logger_pipelines = logging.getLogger('lang_main.pipelines') +logger_pipelines.setLevel(LOGGING_LEVEL_PIPELINES) diff --git a/src/lang_main/pipelines/base.py b/src/lang_main/pipelines/base.py index e77ac09..8273c4b 100644 --- a/src/lang_main/pipelines/base.py +++ b/src/lang_main/pipelines/base.py @@ -5,14 +5,9 @@ import logging from collections.abc import Callable from pathlib import Path +from lang_main.loggers import logger_pipelines as logger from lang_main.shared import save_pickle, load_pickle - -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.pipelines') - - # ** pipelines to perform given actions on dataset in a customisable manner class NoPerformableActionError(Exception): @@ -94,8 +89,9 @@ class BasePipeline(): self, filename: str, ) -> None: - target_filename = f'Pipe-{self.name}_Step-{self.curr_proc_idx}_' + filename + '.pickle' + target_filename = f'Pipe-{self.name}_Step-{self.curr_proc_idx}_' + filename target_path = self.working_dir.joinpath(target_filename) + target_path = target_path.with_suffix('.pkl') # saving file locally save_pickle(obj=self._intermediate_result, path=target_path) @@ -104,7 +100,7 @@ class BasePipeline(): saving_path: str, filename: str, ) -> tuple[Any, ...]: - target_path = saving_path + filename + '.pickle' + target_path = Path(saving_path + filename).with_suffix('.pkl') # loading DataFrame or Series from pickle data = load_pickle(target_path) diff --git a/src/lang_main/pipelines/predefined.py b/src/lang_main/pipelines/predefined.py index 367fefd..e440646 100644 --- a/src/lang_main/pipelines/predefined.py +++ b/src/lang_main/pipelines/predefined.py @@ -22,15 +22,6 @@ from lang_main.analysis.preprocessing import ( ) from lang_main.analysis.tokens import build_token_graph -""" -# ** config parameters -SAVE_PATH_FOLDER: Final[Path] = Path(CONFIG['paths']['results']) -DATE_COLS: Final[list[str]] = CONFIG['preprocess']['date_cols'] -FILENAME_COSSIM_FILTER_CANDIDATES: Final[str] =\ - CONFIG['export_filenames']['filename_cossim_filter_candidates'] -THRESHOLD_SIMILARITY: Final[float] = CONFIG['preprocess']['threshold_similarity'] -""" - # ** pipeline configuration # ** target feature preparation pipe_target_feat = BasePipeline(name='TargetFeature', working_dir=SAVE_PATH_FOLDER) diff --git a/src/lang_main/shared.py b/src/lang_main/shared.py index 428a353..e54286b 100644 --- a/src/lang_main/shared.py +++ b/src/lang_main/shared.py @@ -1,16 +1,11 @@ from typing import Any -import sys import os import shutil -import logging import pickle import tomllib from pathlib import Path -# ** Logging -LOGGING_LEVEL = 'INFO' -logging.basicConfig(level=LOGGING_LEVEL, stream=sys.stdout) -logger = logging.getLogger('ihm_analyse.helpers') +from lang_main.loggers import logger_shared_helpers as logger # ** Lib def create_saving_folder( diff --git a/src/lang_main/types.py b/src/lang_main/types.py index 5f53c66..85d032a 100644 --- a/src/lang_main/types.py +++ b/src/lang_main/types.py @@ -1,9 +1,19 @@ -from typing import TypeAlias +from typing import TypeAlias, Literal import numpy as np from spacy.tokens.doc import Doc as SpacyDoc from torch import Tensor +LoggingLevels: TypeAlias = Literal[ + 'DEBUG', + 'INFO', + 'WARNING', + 'ERROR', + 'CRITICAL', +] + PandasIndex: TypeAlias = int | np.int64 ObjectID: TypeAlias = int -Embedding: TypeAlias = SpacyDoc | Tensor \ No newline at end of file +Embedding: TypeAlias = SpacyDoc | Tensor + +TimelineCandidates: TypeAlias = dict[ObjectID, tuple[tuple[PandasIndex, ...], ...]] \ No newline at end of file diff --git a/test-notebooks/dashboard/app.py b/test-notebooks/dashboard/app.py new file mode 100644 index 0000000..c190b0d --- /dev/null +++ b/test-notebooks/dashboard/app.py @@ -0,0 +1,159 @@ +from typing import cast + +from dash import ( + Dash, + html, + dcc, + callback, + Output, + Input, + State, + dash_table, +) +import plotly.express as px +import pandas as pd +from pandas import DataFrame + +from lang_main import load_pickle +from lang_main.types import TimelineCandidates, ObjectID + +#df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv') + +# ** data +data = cast(DataFrame, load_pickle('./data.pkl')) +cands = cast(TimelineCandidates, load_pickle('./map_candidates.pkl')) +texts = cast(dict[ObjectID, str], load_pickle('./map_texts.pkl')) +table_feats = [ + 'ErstellungsDatum', + 'ErledigungsDatum', + 'VorgangsTypName', + 'VorgangsBeschreibung', +] +table_feats_dates = [ + 'ErstellungsDatum', + 'ErledigungsDatum', +] + +# ** graph config +markers = { + 'size': 12, + 'color': 'yellow', + 'line': { + 'width': 2, + 'color': 'red', + }, +} +hover_data = { + 'ErstellungsDatum': '|%d.%m.%Y', + 'VorgangsBeschreibung': True, +} + + +app = Dash(prevent_initial_callbacks=True) + +app.layout = [ + html.H1(children='Demo Zeitreihenanalyse', style={'textAlign':'center'}), + html.Div(children=[ + html.H2('Wählen Sie ein Objekt aus (ObjektID):'), + dcc.Dropdown( + list(cands.keys()), + id='dropdown-selection', + placeholder="ObjektID auswählen...", + ) + ]), + html.Div(children=[ + html.H3(id='object_text'), + dcc.Dropdown(id='choice-candidates'), + dcc.Graph(id='graph-output'), + ]), + html.Div(children=[ + dash_table.DataTable(id='table-candidates') + ]), +] + +@callback( + Output('object_text', 'children'), + Input('dropdown-selection', 'value'), + prevent_initial_call=True, +) +def update_obj_text(obj_id): + obj_id = int(obj_id) + obj_text = texts[obj_id] + headline = f'HObjektText: {obj_text}' + return headline + +@callback( + Output('choice-candidates', 'options'), + Input('dropdown-selection', 'value'), + prevent_initial_call=True, +) +def update_choice_candidates(obj_id): + obj_id = int(obj_id) + cands_obj_id = cands[obj_id] + choices = list(range(1, len(cands_obj_id)+1)) + return choices + +@callback( + Output('graph-output', 'figure'), + Input('choice-candidates', 'value'), + State('dropdown-selection', 'value'), + prevent_initial_call=True, +) +def update_timeline(index, obj_id): + obj_id = int(obj_id) + # title + obj_text = texts[obj_id] + title = f'HObjektText: {obj_text}' + # cands + cands_obj_id = cands[obj_id] + cands_choice = cands_obj_id[int(index)-1] + # data + df = data.loc[list(cands_choice)].sort_index() + # figure + fig = px.line( + data_frame=df, + x='ErstellungsDatum', + y='ObjektID', + title=title, + hover_data=hover_data, + ) + fig.update_traces( + mode='markers+lines', + marker=markers, + marker_symbol='diamond' + ) + fig.update_xaxes( + tickformat="%B\n%Y", + rangeslider_visible=True, + ) + fig.update_yaxes(type='category') + fig.update_layout(hovermode="x unified") + return fig + +@callback( + [Output('table-candidates', 'data'), + Output('table-candidates', 'columns')], + Input('choice-candidates', 'value'), + State('dropdown-selection', 'value'), + prevent_initial_call=True, +) +def update_table_candidates(index, obj_id): + obj_id = int(obj_id) + # cands + cands_obj_id = cands[obj_id] + cands_choice = cands_obj_id[int(index)-1] + # data + df = data.loc[list(cands_choice)].sort_index() + df = (df + .filter(items=table_feats, axis=1) + .sort_values(by='ErstellungsDatum', ascending=True)) + cols = [{"name": i, "id": i} for i in df.columns] + # convert dates to strings + for col in table_feats_dates: + df[col] = df[col].dt.strftime(r'%Y-%m-%d') + + table_data = df.to_dict('records') + return table_data, cols + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/test-notebooks/dashboard/data.pkl b/test-notebooks/dashboard/data.pkl new file mode 100644 index 0000000..ec76da0 Binary files /dev/null and b/test-notebooks/dashboard/data.pkl differ diff --git a/test-notebooks/dashboard/map_candidates.pkl b/test-notebooks/dashboard/map_candidates.pkl new file mode 100644 index 0000000..77c1a48 Binary files /dev/null and b/test-notebooks/dashboard/map_candidates.pkl differ diff --git a/test-notebooks/dashboard/map_texts.pkl b/test-notebooks/dashboard/map_texts.pkl new file mode 100644 index 0000000..49bdc55 Binary files /dev/null and b/test-notebooks/dashboard/map_texts.pkl differ diff --git a/test-notebooks/timeline_analysis.ipynb b/test-notebooks/timeline_analysis.ipynb new file mode 100644 index 0000000..86d4127 --- /dev/null +++ b/test-notebooks/timeline_analysis.ipynb @@ -0,0 +1,2335 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "6b2c6dad-a9b0-4463-a129-7b991d51487d", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "165ff3f2-fd3c-48ce-ad93-af237909d8d3", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "\n", + "from sentence_transformers import SentenceTransformer\n", + "\n", + "from lang_main import (\n", + " save_pickle,\n", + " load_pickle,\n", + " SAVE_PATH_FOLDER,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "1013213d-d651-4ec7-a8ac-321c83b2a344", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A:\\Arbeitsaufgaben\\lang-main\\.venv\\Lib\\site-packages\\huggingface_hub\\file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "model_stfr = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "d2c78879-ba2c-4b36-9fa2-6a9a138f04fc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "WindowsPath('A:/Arbeitsaufgaben/lang-main/test-notebooks/results/test_new2')" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p = Path(os.getcwd()) / SAVE_PATH_FOLDER\n", + "p" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "b9e65423-bc49-49d5-8bd0-875fcb014f7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[WindowsPath('A:/Arbeitsaufgaben/lang-main/test-notebooks/results/test_new2/Pipe-TargetFeature_Step-3_remove_NA.pickle'),\n", + " WindowsPath('A:/Arbeitsaufgaben/lang-main/test-notebooks/results/test_new2/map_candidates.pkl'),\n", + " WindowsPath('A:/Arbeitsaufgaben/lang-main/test-notebooks/results/test_new2/map_texts.pkl'),\n", + " WindowsPath('A:/Arbeitsaufgaben/lang-main/test-notebooks/results/test_new2/data.pkl')]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "folder = list(p.glob(r'*'))\n", + "folder" + ] + }, + { + "cell_type": "markdown", + "id": "c0679937-d669-4eaa-aec6-85d12edc4a0a", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Gather timeline candidates" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "e4abc1cc-e69d-43a3-b328-2ef3f08a9c90", + "metadata": {}, + "outputs": [], + "source": [ + "from lang_main.analysis.timeline import (\n", + " remove_non_relevant_obj_ids,\n", + " filter_activities_per_obj_id,\n", + " generate_model_input,\n", + " get_timeline_candidates_index,\n", + " transform_timeline_candidates,\n", + " map_obj_texts,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "369838fc-3e77-4a9a-ac1f-e71a380e0353", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "shared:INFO | 2024-05-22 13:45:11 +0000 | Loaded file successfully.\n", + "len(data)=123457\n", + "timeline:INFO | 2024-05-22 13:45:11 +0000 | Removing non-relevant ObjectIDs from dataset\n", + "timeline:DEBUG | 2024-05-22 13:45:12 +0000 | Ignored ObjectIDs: (0,)\n", + "timeline:INFO | 2024-05-22 13:45:12 +0000 | Non-relevant ObjectIDs removed successfully\n", + "\n", + "Index: 122958 entries, 0 to 123456\n", + "Data columns (total 20 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 VorgangsID 122958 non-null int64 \n", + " 1 ObjektID 122958 non-null int64 \n", + " 2 HObjektText 122953 non-null object \n", + " 3 ObjektArtID 122958 non-null int64 \n", + " 4 ObjektArtText 122880 non-null object \n", + " 5 VorgangsTypID 122958 non-null int64 \n", + " 6 VorgangsTypName 122958 non-null object \n", + " 7 VorgangsDatum 122958 non-null datetime64[ns]\n", + " 8 VorgangsStatusId 122958 non-null int64 \n", + " 9 VorgangsPrioritaet 122958 non-null int64 \n", + " 10 VorgangsBeschreibung 122958 non-null object \n", + " 11 VorgangsOrt 0 non-null object \n", + " 12 VorgangsArtText 122958 non-null object \n", + " 13 ErledigungsDatum 122958 non-null datetime64[ns]\n", + " 14 ErledigungsArtText 122465 non-null object \n", + " 15 ErledigungsBeschreibung 115562 non-null object \n", + " 16 MPMelderArbeitsplatz 5709 non-null object \n", + " 17 MPAbteilungBezeichnung 5709 non-null object \n", + " 18 Arbeitsbeginn 119459 non-null datetime64[ns]\n", + " 19 ErstellungsDatum 122958 non-null datetime64[ns]\n", + "dtypes: datetime64[ns](4), int64(6), object(10)\n", + "memory usage: 19.7+ MB\n" + ] + } + ], + "source": [ + "ret = load_pickle(folder[0])\n", + "data = ret[0]\n", + "print(f\"{len(data)=}\")\n", + "data = remove_non_relevant_obj_ids(data, thresh_unique_feat_per_id=3)\n", + "data.info()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0afe2888-ff74-4be8-b7e6-374546942070", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d22728c-2fd9-4742-a4be-61909f14cfd7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "79973397-4df2-43ca-9f40-182644592337", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(['Reparaturauftrag (Portal)', 'Wartung', 'Störungsmeldung'],\n", + " dtype=object)" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data['VorgangsTypName'].unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "b10fbf48-9e1a-4d2b-9b41-b6711b366a34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "timeline:INFO | 2024-05-22 13:45:12 +0000 | Filtering activities per ObjectID\n", + "timeline:INFO | 2024-05-22 13:45:12 +0000 | Activities per ObjectID filtered successfully\n" + ] + }, + { + "data": { + "text/plain": [ + "ObjektID\n", + "1 283\n", + "1654 243\n", + "7 223\n", + "151 189\n", + "140 140\n", + " ... \n", + "1698 2\n", + "683 2\n", + "1135 2\n", + "2170 2\n", + "297 2\n", + "Name: count, Length: 366, dtype: int64" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "activity_types = [\n", + " 'Reparaturauftrag (Portal)',\n", + " 'Störungsmeldung',\n", + "]\n", + "\n", + "(data, num_activities_per_obj_id) =\\\n", + "filter_activities_per_obj_id(data, relevant_activity_types=activity_types)\n", + "num_activities_per_obj_id" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "39207835-e7b2-43c8-b6a6-8d685ac488a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Index: 6104 entries, 0 to 123456\n", + "Data columns (total 20 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 VorgangsID 6104 non-null int64 \n", + " 1 ObjektID 6104 non-null int64 \n", + " 2 HObjektText 6103 non-null object \n", + " 3 ObjektArtID 6104 non-null int64 \n", + " 4 ObjektArtText 6087 non-null object \n", + " 5 VorgangsTypID 6104 non-null int64 \n", + " 6 VorgangsTypName 6104 non-null object \n", + " 7 VorgangsDatum 6104 non-null datetime64[ns]\n", + " 8 VorgangsStatusId 6104 non-null int64 \n", + " 9 VorgangsPrioritaet 6104 non-null int64 \n", + " 10 VorgangsBeschreibung 6104 non-null object \n", + " 11 VorgangsOrt 0 non-null object \n", + " 12 VorgangsArtText 6104 non-null object \n", + " 13 ErledigungsDatum 6104 non-null datetime64[ns]\n", + " 14 ErledigungsArtText 6079 non-null object \n", + " 15 ErledigungsBeschreibung 5812 non-null object \n", + " 16 MPMelderArbeitsplatz 5672 non-null object \n", + " 17 MPAbteilungBezeichnung 5672 non-null object \n", + " 18 Arbeitsbeginn 5915 non-null datetime64[ns]\n", + " 19 ErstellungsDatum 6104 non-null datetime64[ns]\n", + "dtypes: datetime64[ns](4), int64(6), object(10)\n", + "memory usage: 1001.4+ KB\n" + ] + } + ], + "source": [ + "data.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "b848d8ee-cdf3-4b92-ac5b-e5a74e562e92", + "metadata": {}, + "outputs": [], + "source": [ + "# VorgangsTypName, VorgangsArtText, VorgangsBeschreibung\n", + "prop_interest = ['VorgangsTypName', 'VorgangsArtText', 'VorgangsBeschreibung']\n", + "prop_interest = ['VorgangsArtText', 'VorgangsBeschreibung']\n", + "prop_interest = ['VorgangsBeschreibung']\n", + "\n", + "target_feature_name = 'nlp_model_input'" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "8d981bee-2fc9-4ea4-88a5-5589b1d76684", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "timeline:INFO | 2024-05-22 13:45:13 +0000 | Generating concatenation of model input features\n", + "timeline:INFO | 2024-05-22 13:45:13 +0000 | Model input generated successfully\n" + ] + } + ], + "source": [ + "anlys_data = generate_model_input(data, target_feature_name, model_input_features=prop_interest)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "616dc58f-f7d7-4787-b662-3900c66c3b64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Index: 6104 entries, 0 to 123456\n", + "Data columns (total 21 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 VorgangsID 6104 non-null int64 \n", + " 1 ObjektID 6104 non-null int64 \n", + " 2 HObjektText 6103 non-null object \n", + " 3 ObjektArtID 6104 non-null int64 \n", + " 4 ObjektArtText 6087 non-null object \n", + " 5 VorgangsTypID 6104 non-null int64 \n", + " 6 VorgangsTypName 6104 non-null object \n", + " 7 VorgangsDatum 6104 non-null datetime64[ns]\n", + " 8 VorgangsStatusId 6104 non-null int64 \n", + " 9 VorgangsPrioritaet 6104 non-null int64 \n", + " 10 VorgangsBeschreibung 6104 non-null object \n", + " 11 VorgangsOrt 0 non-null object \n", + " 12 VorgangsArtText 6104 non-null object \n", + " 13 ErledigungsDatum 6104 non-null datetime64[ns]\n", + " 14 ErledigungsArtText 6079 non-null object \n", + " 15 ErledigungsBeschreibung 5812 non-null object \n", + " 16 MPMelderArbeitsplatz 5672 non-null object \n", + " 17 MPAbteilungBezeichnung 5672 non-null object \n", + " 18 Arbeitsbeginn 5915 non-null datetime64[ns]\n", + " 19 ErstellungsDatum 6104 non-null datetime64[ns]\n", + " 20 nlp_model_input 6104 non-null object \n", + "dtypes: datetime64[ns](4), int64(6), object(11)\n", + "memory usage: 1.0+ MB\n" + ] + } + ], + "source": [ + "anlys_data.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "5e147157-5710-440e-a787-5f6a6d66bc76", + "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", + "
VorgangsIDObjektIDHObjektTextObjektArtIDObjektArtTextVorgangsTypIDVorgangsTypNameVorgangsDatumVorgangsStatusIdVorgangsPrioritaet...VorgangsOrtVorgangsArtTextErledigungsDatumErledigungsArtTextErledigungsBeschreibungMPMelderArbeitsplatzMPAbteilungBezeichnungArbeitsbeginnErstellungsDatumnlp_model_input
52136262888315NaN32Flurförderzeuge / Putzmaschine / Rasenmäher3Reparaturauftrag (Portal)2022-07-1150...NaNFlurförderzeug2022-07-12Intern UTT - WartungRäder gereinigtVersandVersand2022-07-122022-07-11Laufrollen verstopft (Garn)
\n", + "

1 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " VorgangsID ObjektID HObjektText ObjektArtID \\\n", + "52136 262888 315 NaN 32 \n", + "\n", + " ObjektArtText VorgangsTypID \\\n", + "52136 Flurförderzeuge / Putzmaschine / Rasenmäher 3 \n", + "\n", + " VorgangsTypName VorgangsDatum VorgangsStatusId \\\n", + "52136 Reparaturauftrag (Portal) 2022-07-11 5 \n", + "\n", + " VorgangsPrioritaet ... VorgangsOrt VorgangsArtText ErledigungsDatum \\\n", + "52136 0 ... NaN Flurförderzeug 2022-07-12 \n", + "\n", + " ErledigungsArtText ErledigungsBeschreibung MPMelderArbeitsplatz \\\n", + "52136 Intern UTT - Wartung Räder gereinigt Versand \n", + "\n", + " MPAbteilungBezeichnung Arbeitsbeginn ErstellungsDatum \\\n", + "52136 Versand 2022-07-12 2022-07-11 \n", + "\n", + " nlp_model_input \n", + "52136 Laufrollen verstopft (Garn) \n", + "\n", + "[1 rows x 21 columns]" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "anlys_data.loc[anlys_data['HObjektText'].isna()]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce40cc45-a2bc-4d84-9688-a4cf2797eecf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3141fb4b-b861-41fe-a887-fe3a9dbbab9c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "a08d8f39-d814-4583-b77c-51e660cba6ec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ObjektID\n", + "1 283\n", + "1654 243\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test = num_activities_per_obj_id.iloc[300:302].copy()\n", + "test = num_activities_per_obj_id.iloc[:2].copy()\n", + "test" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "03b9ef0f-0fc9-4e16-bd1c-0774c8c295e8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "366" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test = num_activities_per_obj_id.copy()\n", + "len(test)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "133ad574-7f55-421f-927b-969d26a80ffc", + "metadata": {}, + "outputs": [], + "source": [ + "tl_candidates = get_timeline_candidates_index(\n", + " data=anlys_data,\n", + " num_activities_per_obj_id=test,\n", + " model=model_stfr,\n", + " cos_sim_threshold=0.8,\n", + " model_input_feature=target_feature_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "26085123-1336-4e7f-8b90-68b7c5cb6711", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } + }, + "source": [ + "for (obj_id, cands) in tl_candidates:\n", + " print(f'{obj_id=}, {cands=}', flush=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14e2d02b-e24b-4a29-b410-17ef7a0d503d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "e690e6ff-92a5-48a8-b721-cf78dfe25ab7", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ec2ac9d40ab545d5b6d27b339eeac98e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/366 [00:00\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", + "
VorgangsIDObjektIDHObjektTextObjektArtIDObjektArtTextVorgangsTypIDVorgangsTypNameVorgangsDatumVorgangsStatusIdVorgangsPrioritaet...VorgangsOrtVorgangsArtTextErledigungsDatumErledigungsArtTextErledigungsBeschreibungMPMelderArbeitsplatzMPAbteilungBezeichnungArbeitsbeginnErstellungsDatumnlp_model_input
66961442391654WEBEREI ALLGEMEIN, Weberei allgemein,90UTT allgemein3Reparaturauftrag (Portal)2021-11-0750...NaNVerkabelung / Verdrahtung2021-11-08Intern UTT - ReparaturKnotex Netzkabel wurde getauscht. Maschine läu...WebereiWeberei2021-11-082021-11-07Knotex Netzkabel hat einen Wackelkontakt. Kabe...
502235087081654WEBEREI ALLGEMEIN, Weberei allgemein,90UTT allgemein3Reparaturauftrag (Portal)2023-01-2350...NaNVerkabelung / Verdrahtung2023-01-23Intern UTT - ReparaturKabel an Stecker ab, gekürzt alles OK.WebereiWeberei2023-01-232023-01-23Knotex Netzkabel hat Wackelkontakt.\\nHerr Bric...
1136201416061654WEBEREI ALLGEMEIN, Weberei allgemein,90UTT allgemein3Reparaturauftrag (Portal)2021-10-0650...NaNVerkabelung / Verdrahtung2021-10-06Intern UTT - ReparaturKabel repariert (beide Seiten Wackelkontakt). ...WebereiWeberei2021-10-062021-10-06Knotex Netzkabel hat Wackelkontakt. Kabel lieg...
\n", + "

3 rows × 21 columns

\n", + "" + ], + "text/plain": [ + " VorgangsID ObjektID HObjektText \\\n", + "6696 144239 1654 WEBEREI ALLGEMEIN, Weberei allgemein, \n", + "50223 508708 1654 WEBEREI ALLGEMEIN, Weberei allgemein, \n", + "113620 141606 1654 WEBEREI ALLGEMEIN, Weberei allgemein, \n", + "\n", + " ObjektArtID ObjektArtText VorgangsTypID VorgangsTypName \\\n", + "6696 90 UTT allgemein 3 Reparaturauftrag (Portal) \n", + "50223 90 UTT allgemein 3 Reparaturauftrag (Portal) \n", + "113620 90 UTT allgemein 3 Reparaturauftrag (Portal) \n", + "\n", + " VorgangsDatum VorgangsStatusId VorgangsPrioritaet ... VorgangsOrt \\\n", + "6696 2021-11-07 5 0 ... NaN \n", + "50223 2023-01-23 5 0 ... NaN \n", + "113620 2021-10-06 5 0 ... NaN \n", + "\n", + " VorgangsArtText ErledigungsDatum ErledigungsArtText \\\n", + "6696 Verkabelung / Verdrahtung 2021-11-08 Intern UTT - Reparatur \n", + "50223 Verkabelung / Verdrahtung 2023-01-23 Intern UTT - Reparatur \n", + "113620 Verkabelung / Verdrahtung 2021-10-06 Intern UTT - Reparatur \n", + "\n", + " ErledigungsBeschreibung \\\n", + "6696 Knotex Netzkabel wurde getauscht. Maschine läu... \n", + "50223 Kabel an Stecker ab, gekürzt alles OK. \n", + "113620 Kabel repariert (beide Seiten Wackelkontakt). ... \n", + "\n", + " MPMelderArbeitsplatz MPAbteilungBezeichnung Arbeitsbeginn \\\n", + "6696 Weberei Weberei 2021-11-08 \n", + "50223 Weberei Weberei 2023-01-23 \n", + "113620 Weberei Weberei 2021-10-06 \n", + "\n", + " ErstellungsDatum nlp_model_input \n", + "6696 2021-11-07 Knotex Netzkabel hat einen Wackelkontakt. Kabe... \n", + "50223 2023-01-23 Knotex Netzkabel hat Wackelkontakt.\\nHerr Bric... \n", + "113620 2021-10-06 Knotex Netzkabel hat Wackelkontakt. Kabel lieg... \n", + "\n", + "[3 rows x 21 columns]" + ] + }, + "execution_count": 229, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res = anlys_data.loc[cands].sort_index()\n", + "res" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "id": "3801daba-6927-44c4-ba22-f0033411372a", + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "import plotly.io as pio" + ] + }, + { + "cell_type": "code", + "execution_count": 267, + "id": "cbc277b4-f77f-4e52-9d3d-845a8dab6169", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "customdata": [ + [ + "Knotex Netzkabel hat einen Wackelkontakt. Kabel liegt in der Elektrowerkstatt." + ], + [ + "Knotex Netzkabel hat Wackelkontakt.\nHerr Brich weiß bescheid" + ], + [ + "Knotex Netzkabel hat Wackelkontakt. Kabel liegt in der Elektrowerkstatt auf der Werkbank." + ] + ], + "hovertemplate": "ErstellungsDatum=%{x|%d.%m.%Y}
ObjektID=%{y}
VorgangsBeschreibung=%{customdata[0]}", + "legendgroup": "", + "line": { + "color": "#636efa", + "dash": "solid" + }, + "marker": { + "color": "yellow", + "line": { + "color": "red", + "width": 2 + }, + "size": 12, + "symbol": "diamond" + }, + "mode": "markers+lines", + "name": "", + "orientation": "v", + "showlegend": false, + "type": "scatter", + "x": [ + "2021-11-07T00:00:00", + "2023-01-23T00:00:00", + "2021-10-06T00:00:00" + ], + "xaxis": "x", + "y": [ + 1654, + 1654, + 1654 + ], + "yaxis": "y" + } + ], + "layout": { + "autosize": true, + "hovermode": "x unified", + "legend": { + "tracegroupgap": 0 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "HObjektText: WEBEREI ALLGEMEIN, Weberei allgemein" + }, + "xaxis": { + "anchor": "y", + "autorange": true, + "domain": [ + 0, + 1 + ], + "range": [ + "2021-09-06 06:40:56.8194", + "2023-02-21 17:19:03.1806" + ], + "rangeslider": { + "autorange": true, + "range": [ + "2021-09-06 06:40:56.8194", + "2023-02-21 17:19:03.1806" + ], + "visible": true, + "yaxis": { + "_template": null, + "rangemode": "match" + } + }, + "tickformat": "%B\n%Y", + "title": { + "text": "ErstellungsDatum" + }, + "type": "date" + }, + "yaxis": { + "anchor": "x", + "autorange": true, + "domain": [ + 0, + 1 + ], + "range": [ + -1, + 1 + ], + "title": { + "text": "ObjektID" + }, + "type": "category" + } + } + }, + "image/png": "iVBORw0KGgoAAAANSUhEUgAABcAAAAFoCAYAAABws1hyAAAAAXNSR0IArs4c6QAAIABJREFUeF7t3Xu8ZWV9H/7vICCjXOQSLpMqCiZBGyuJVUkaKVVJFCQqBsTSRsVMppDEihTKmFpjrA4veCE2ieBkImJ+tSAmeCEglmioMQ2RoiQkQlMhXhoEBEYuMgjC/Fx7WMd11uzrWXvv9axnv88/ypy91vo+7++zL+ezn/3sVVu3bt0afggQIECAAAECBAgQIECAAAECBAgQIECAQGYCqwTgmXXUcAgQIECAAAECBAgQIECAAAECBAgQIECgJyAANxEIECBAgAABAgQIECBAgAABAgQIECBAIEsBAXiWbTUoAgQIECBAgAABAgQIECBAgAABAgQIEBCAmwMECBAgQIAAAQIECBAgQIAAAQIECBAgkKWAADzLthoUAQIECBAgQIAAAQIECBAgQIAAAQIECAjAzQECBAgQIECAAAECBAgQIECAAAECBAgQyFJAAJ5lWw2KAAECBAgQIECAAAECBAgQIECAAAECBATg5gABAgQIECBAgAABAgQIECBAgAABAgQIZCkgAM+yrQZFgAABAgQIECBAgAABAgQIECBAgAABAgJwc4AAAQIECBAgQIAAAQIECBAgQIAAAQIEshQQgGfZVoMiQIAAAQIECBAgQIAAAQIECBAgQIAAAQG4OUCAAAECBAgQIECAAAECBAgQIECAAAECWQoIwLNsq0ERIECAAAECBAgQIECAAAECBAgQIECAgADcHCBAgAABAgQIECBAgAABAgQIECBAgACBLAUE4Fm21aAIECBAgAABAgQIECBAgAABAgQIECBAQABuDhAgQIAAAQIECBAgQIAAAQIECBAgQIBAlgIC8CzbalAECBAgQIAAAQIECBAgQIAAAQIECBAgIAA3BwgQIECAAAECBAgQIECAAAECBAgQIEAgSwEBeJZtNSgCBAgQIECAAAECBAgQIECAAAECBAgQEICbAwQIECBAgAABAgQIECBAgAABAgQIECCQpYAAPMu2GhQBAgQIECBAgAABAgQIECBAgAABAgQICMDNAQIECBAgQIAAAQIECBAgQIAAAQIECBDIUkAAnmVbDYoAAQIECBAgQIAAAQIECBAgQIAAAQIEBODmAAECBAgQIECAAAECBAgQIECAAAECBAhkKSAAz7KtBkWAAAECBAgQIECAAAECBAgQIECAAAECAnBzgAABAgQIECBAgAABAgQIECBAgAABAgSyFBCAZ9lWgyJAgAABAgQIECBAgAABAgQIECBAgAABAbg5QIAAAQIECBAgQIAAAQIECBAgQIAAAQJZCgjAs2yrQREgQIAAAQIECBAgQIAAAQIECBAgQICAANwcIECAAAECBAgQIECAAAECBAgQIECAAIEsBQTgWbbVoAgQIECAAAECBAgQIECAAAECBAgQIEBAAG4OECBAgAABAgQIECBAgAABAgQIECBAgECWAgLwLNtqUAQIECBAgAABAgQIECBAgAABAgQIECAgADcHCBAgQIAAAQIECBAgQIAAAQIECBAgQCBLAQF4lm01KAIECBAgQIAAAQIECBAgQIAAAQIECBAQgJsDBAgQIECAAAECBAgQIECAAAECBAgQIJClgAA8wba+d+Ol8cUbbo4Lzjo19txjt5lVuPne++PkM8+L4485Io496vCZXceJCRAg0E+gfAx6waGHxFvXHZ81Ur+xXnbl5+P8D38yNp59Whx84Jqsx29wBAgQIECAAAECBAgQIECgLQEB+JjyWx56ON5xzoW9W7/z9JNi9S47LztyVJBThNofvPjKZce86XVH9Q19JgnAy+s+bc2+fesaNrxxA/B+Yyv/7cabbh0p+K4zTppqwD7N0GhYX2/5+m2x7oxz46gXv3C7Pl13w83xhrecFRe978x4/qGHRPnfgzCOfslhS/0p6n/72dvmUv2nblXWd8Vnrx3oXNYwrCf9ejBuzQ9973u9N0qqva6OZ+QEiIhyzAfst3ffsG/U/au8Rt191LXLHn7rjruX3bRe/0qdB83tcrzPedZBS29kTXKNfnOk7HP1PNXzlwMc9349zG7QY9Cwx5vS+pTXv3Ls+/uox81R/Z3V75s8rg6qaREC8GGPmbPqlfMSIECAAAECBAgQIECAAIFRAgLwUUKP/35UQDcoyCkDgX322mPZiu7y9nfdc+92gWAXAvB+bEVod+nl18x85fo0A/BiHIPO1y/ELMdd79Ekweyg65XnqIaqo+ZdtQ+D5mB53vobLpPUXDoVwX0ZxI5514lyDN/4xzvirs33Rb+AdNxxjltzNSSu11t9o6AeKhdj6vcGVz/n4g2BYQF0/ffjjrEeZNff3KoH6fUQfhoB+CDn8t/7vZGxkvul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IECAAAECBAgQIECgoYAAvCGgwwkQIECAAAECBAgQIECAAAECBAgQIEAgTQEBeJp9URUBAgQIECBAgAABAgQIECBAgAABAgQINBQQgDcEdDgBAgQIECBAgAABAgQIECBAgAABAgQIpCkgAE+zL6oiQIAAAQIECBAgQIAAAQIECBAgQIAAgYYCAvCGgA4nQIAAAQIECBAgQIAAAQIECBAgQIAAgTQFBOBp9kVVBAgQIECAAAECBAgQIECAAAECBAgQINBQQADeENDhBAgQIECAAAECBAgQIECAAAECBAgQIJCmgAA8zb6oigABAgQIECBAgAABAgQIECBAgAABAgQaCgjAGwI6nAABAgQIECBAgAABAgQIECBAgAABAgTSFBCAp9kXVREgQIAAAQIECBAgQIAAAQIECBAgQIBAQwEBeENAhxMgQIAAAQIECBAgQIAAAQIECBAgQIBAmgIC8DT7oioCBAgQIECAAAECBAgQIECAAAECBAgQaCggAG8I6HACBAgQIECAAAECBAgQIECAAAECBAgQSFNAAJ5mX1RFgAABAgQIECBAgAABAgQIECBAgAABAg0FBOANAR1OgAABAgQIECBAgAABAgQIECBAgAABAmkKCMDT7IuqCBAgQIAAAQIECBAgQIAAAQIECBAgQKChgAC8IaDDCRAgQIAAAQIECBAgQIAAAQIECBAgQCBNAQF4mn1RFQECBAgQIECAAAECBAgQIECAAAECBAg0FBCANwR0OAECBAgQIECAAAECBAgQIECAAAECBAikKSAAT7MvqiJAgAABAgQIECBAgAABAgQIECBAgACBhgIC8IaADidAgAABAgQIECBAgAABAgQIECBAgACBNAUE4Gn2RVUECBAgQIAAAQIECBAgQIAAAQIECBAg0FBAAN4Q0OEECBAgQIAAAQIECBAgQIAAAQIECBAgkKaAADzNvqiKAAECBAgQIECAAAECBAgQIECAAAECBBoKCMAbAjqcAAECBAgQIECAAAECBAgQIECAAAECBNIUEICn2RdVESBAgAABAgQIECBAgAABAgQIECBAgEBDAQF4Q0CHEyBAgAABAgQIECBAgAABAgQIECBAgECaAgLwNPuiKgIECBAgQIAAAQIECBAgQIAAAQIECBBoKCAAbwjocAIECBAgQIAAAQIECBAgQIAAAQIECBBIU0AAnmZfVEWA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/s4rt1mtfz/ZhOW5OYFsBATg2bTSQOYhUD7B7L/vXssC7HFXgI9691UAPo8upneNWc6rQedOT0FFBAgQIECAAAECBAgQWByBft8P1i/U7heAj/t3ni1QFmc+GelwAQG4GUJgTIFhTzDj7AE+KvwuyhCAj9mMjG42y3k17ouijDgNhQABAgQIECBAgAABAskL9Au/y6LruUA9xJ7k77zi0+nr37MpTj/lhDj4wDXJuyiQwKwEBOCzknXerARGbSFRf0e2/k3L9RXig3AE4FlNm5GDmeW8GnXukcW5AQECBAgQIECAAAECBAhMXWDUvt39vhSzKKLYRnXU33lFFvHUNfsubZ9S/Pell18TF5x1auy5x25TH4sTEuiKgAC8K51SZ6sC5buz37rj7mV1vOl1Ry1thVI8Sb3hLWf1fl/dZ6vcD/zGm25dduzRLzks3nn6SbF6l52j3J+rvMEB++0dG88+zTu0rXZ99hef5by67Y67Yt0Z58awOTv7EboCAQIECBAgQIAAAQIECFQF6n//l7+76H1nLguu3372hb1fVbODUX9D1n9vD3Bzj8A2AQG4mUCAAAECBAgQIECAAAECBAgQIECAAAECWQoIwLNsq0ERIECAAAECBAgQIECAAAECBAgQIECAgADcHCBAgAABAgQIECBAgAABAgQIECBAgACBLAUE4Fm21aAIECBAgAABAgQIECBAgAABAgQIECBAQABuDhAgQIAAAQIECBAgQIAAAQIECBAgQIBAlgIC8CzbalAECBAgQIAAAQIECBAgQIAAAQIECBAgIAA3BwgQIECAAAECBAgQIECAAAECBAgQIEAgSwEBeJZtNSgCBAgQIECAAAECBAgQIECAAAECBAgQEICbAwQIECBAgAABAgQIECBAgAABAgQIECCQpYAAPMu2GhQBAgQIECBAgAABAgQIECBAgAABAgQICMDNAQIECBAgQIAAAQIECBAgQIAAAQIECBDIUkAAnmVbDYoAAQIECBAgQIAAAQIECBAgQIAAAQIEBODmAAECBAgQIECAAAECBAgQIECAAAECBAhkKSAAz7KtBkWAAAECBAgQIECAAAECBAgQIECAAAECAnBzgAABAgQIECBAgAABAgQIECBAgAABAgSyFBCAZ9lWgyJAgAABAgQIECBAgAABAgQIECBAgAABAbg5QIAAAQIECBAgQIAA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hYyeAAECBAgQIECAwPQEBODTs3QmAgQIECBAgAABAiMF6tu3VLdoGXmwGxAgQIAAAQIECBAgMJGAAHwiLjcmQIAAAQIECBAgQIAAAQIECBAgQIAAga4ICMC70il1EiBAgAABAgQIECBAgAABAgQIECBAgMBEAgLwibjcmAABAgQIECBAgAABAgQIECBAgAABAgS6IiAA70qn1EmAAAECBAgQIECAAAECBAgQIECAAAECEwkIwCficmMCBAgQIECAAAECBAgQIECAAAECBAgQ6IqAALwrnVInAQIECBAgQIAAAQIECBAgQIAAAQIECEwkIACfiMuNCRAgQIAAAQIECBAgQIAAAQIECBAgQKArAgLwrnRKnQQIECBAgAABAgQIECBAgAABAgQIECAwkYAAfCIuNyZAgAABAgQIECBAgAABAgQIECBAgACBrggIwLvSKXUSIECAAAECBAgQIECAAAECBAgQIECAwEQCAvCJuNyYAAECBAgQIECAAAECBAgQIECAAAECBLoiIADvSqfUSYAAAQIECBAgQIAAAQIECBAgQIAAAQITCQjAJ+JyYwIECBAgQIAAAQIECBAgQIAAAQIECBDoioAAvCudUicBAgQIECBAgAABAgQIECBAgAABAgQITCQgAJ+Iy40JECBAgAABAgQIECBAgAABAgQIECBAoCsCAvCudEqdBAgQIECAAAECBAgQIECAAAECBAgQIDCRgAB8Ii43JkCAAAECBAgQIECAAAECBAgQIECAAIGuCAjAu9IpdRIgQIAAAQIECBAgQIAAAQIECBAgQIDARAIC8Im43JgAAQIECBAgQIAAAQIECBAgQIAAAQIEuiIgAO9Kp9RJgAABAgQIECBAgAABAgQIECBAgAABAhMJCMAn4nJjAgQIECBAgAABAgQIECBAgAABAgQIEOiKgAC8K51SJwECBAgQIECAAAECBAgQIECAAAECBAhMJCAAn4jLjQkQIECAAAECBAgQIECAAAECBAgQIECgKwIC8K50Sp0ECBAgQIAAAQIECBAgQIAAAQIECBAgMJGAAHwiLjcmQIAAAQIECBAgQIAAAQIECBAgQIAAga4ICMC70il1EiBAgAABAgQIECBAgAABAgQIECBAgMBEAgLwibjcmAABAgQIECBAgAABAgQIECBAgAABAgS6IiAA70qn1EmAAAECBAgQIECAAAECBAgQIECAAAECEwkIwCficmMCBAgQIECAAAECBAgQIECAAAECBAgQ6IqAALwrnVInAQIECBAgQIAAAQIECBAgQIAAAQIECEwkIACfiMuNCRAgQIAAAQIECBAgQIAAAQIECBAgQKArAgLwrnRKnQQIECBAgAABAgQIECBAgAABAgQIECAwkYAAfCIuNyZAgAABAgQIECBAgAABAgQIECBAgACBrggIwLvSKXUSIECAAAECBAgQIECAAAECBAgQIECAwEQCAvCJuNyYAAECBAgQIECAAAECBAgQIECAAAECBLoiIADvSqfUSYAAAQIECBAgQIAAAQIECBAgQIAAAQITCQjAJ+JyYwIECBAgQIAAAQIECBAgQIAAAQIECBDoioAAvCudUicBAgQIECBAgAABAgQIECBAgAABAgQITCQgAJ+Iy40JECBAgAABAgQIECBAgAABAgQIECBAoCsCAvCudEqdBAgQIECAAAECBAgQIECAAAECBAgQIDCRgAB8Ii43JkCAAAECBAgQIECAAAECBAgQIECAAIGuCAjAu9IpdRIgQIAAAQIECBAgQIAAAQIECBAgQIDARAIC8Im43JgAAQIECBAgQIAAAQIECBAgQIAAAQIEuiIgAO9Kp9RJgAABAgQIECBAgAABAgQIECBAgAABAhMJCMAn4nJjAgQIECBAgAABAgQIECBAgAABAgQIEOiKgAC8K51SJwECBAgQIECAAAECBAgQIECAAAECBAhMJPD/A4MVDsUCJ3q1AAAAAElFTkSuQmCC", + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "title = 'HObjektText: TEST'\n", + "\n", + "markers = {\n", + " 'size': 12,\n", + " 'color': 'yellow',\n", + " 'line': {\n", + " 'width': 2,\n", + " 'color': 'red',\n", + " },\n", + "}\n", + "hover_data = {\n", + " 'ErstellungsDatum': '|%d.%m.%Y',\n", + " 'VorgangsBeschreibung': True,\n", + "}\n", + "fig = px.line(\n", + " data_frame=res,\n", + " x='ErstellungsDatum',\n", + " y='ObjektID',\n", + " title=title,\n", + " hover_data=hover_data,\n", + ")\n", + "fig.update_traces(mode='markers+lines', marker=markers, marker_symbol='diamond')\n", + "fig.update_xaxes(\n", + " tickformat=\"%B\\n%Y\",\n", + " rangeslider_visible=True,\n", + ")\n", + "fig.update_yaxes(type='category')\n", + "fig.update_layout(hovermode=\"x unified\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8642ade9-30c1-4ac6-8257-9437e2e5e5a1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f909534-8a08-42c1-a640-453b0c76d3c9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}