Кластеризация экземпляров процесса с помощью Python-блоков
Документация
Главная

Кластеризация экземпляров процесса с помощью Python-блоков

В этом документе показано, как построить собственный сценарий кластеризации экземпляров процесса в Proceset с помощью Python-блоков, вывести результат на дашборд и использовать его для анализа отклонений на Карте процесса и в Сфере процессов.

Контекст

Компания анализирует процесс согласования закупок. В рамках этого процесса заявки могут проходить по нескольким сценариям:

  • Часть заявок проходит по базовому маршруту без возвратов
  • Часть заявок возвращается на доработку
  • Часть заявок уходит на дополнительное согласование
  • Часть заявок связана с повышенной стоимостью и требует отдельного контроля

Ручной анализ каждого экземпляра занимает слишком много времени. Нужен сценарий, который:

  1. Автоматически собирает признаки по экземплярам процесса.
  2. Распределяет экземпляры по кластерам с помощью Python-блока.
  3. Показывает результат на дашборде.
  4. Позволяет открыть Карту процесса уже с фильтром по выбранному кластеру.
  5. Помогает определить, какой сценарий нужно оптимизировать в первую очередь.

Настройка кластеризации экземпляров процесса и анализа результата на дашборде

Настройка кластеризации экземпляров процесса и анализа результата на дашборде выполняется в шесть этапов:

  • Этап 1 — создание таблиц, настройка модели данных и подготовка структуры для хранения результатов кластеризации
  • Этап 2 — добавление пользовательского Python-блока, который выполняет кластеризацию экземпляров процесса
  • Этап 3 — создание и публикация скрипта, который собирает признаки по экземплярам процесса, запускает Python-блок и записывает результат в таблицу
  • Этап 4 — построение дашборда с KPI, диаграммами, таблицей характеристик кластеров
  • Этап 5 — создание процесса в модели данных и подготовка его для отображения в Карте процесса и Сфере процессов
  • Этап 6 — интерпретация кластеров, фильтрация процесса по выбранному кластеру и применение результата в работе аналитика

Также вы можете ознакомиться с примером рабочего процесса, демонстрирующим, как аналитик находит кластер с возвратами на доработку, открывает его на Карте процесса и определяет участок процесса, который требует оптимизации.

Подготовка таблиц и модели данных

Для реализации нужны две таблицы:

  • process_event_log — журнал событий процесса
  • cluster_case_clusters — таблица результатов кластеризации

Таблица «process_event_log»

Таблицу можно загрузить заранее для реализации примера, описанного в документе, либо собрать данные отдельно любым удобным для вас способом, например:

  • Собрать данные скриптом автоматизации из внешней системы и записать в таблицу
  • Использовать JS-трекер, если источник — сайт или веб-приложение
  • Подключить уже существующую таблицу из ClickHouse

Таблица должна содержать историю событий по экземплярам процесса. В ней должны быть следующие обязательные поля:

  • case_id — идентификатор экземпляра процесса. Используется для объединения всех событий, относящихся к одному кейсу, и для связи журнала событий с таблицей кейсов и таблицей результатов кластеризации. Значение должно быть уникальным в рамках одного экземпляра процесса, но может повторяться в нескольких строках таблицы, так как один кейс обычно содержит несколько событий
  • event_name — название события процесса. Описывает, какое действие или этап был выполнен в рамках кейса, например Create request, Check documents, Approve, Return for rework, Complete. На основе последовательности значений этого поля формируется маршрут экземпляра процесса, который затем используется в кластеризации как один из признаков
  • event_ts — дата и время наступления события. Используется для упорядочивания событий внутри кейса в правильной хронологической последовательности. Также на основе этого поля можно рассчитать длительность прохождения кейса, время между этапами и другие временные характеристики процесса
  • cost — стоимость, связанная с кейсом или событием. Используется как числовой признак для кластеризации и как аналитический показатель на дашборде. В зависимости от модели данных это может быть стоимость всей заявки, сумма по позиции, стоимость на момент события или другое числовое значение, которое помогает отличать дорогие сценарии от типовых
  • department — подразделение, отдел или другая организационная единица, связанная с кейсом. Поле позволяет анализировать, в каких подразделениях чаще возникают определенные сценарии процесса, например возвраты на доработку, длительное согласование или дорогие кейсы. Может использоваться для фильтрации и сегментации на дашборде
  • channel — канал поступления или создания кейса. Показывает, откуда пришел экземпляр процесса, например Email, Portal, Manual, API. Поле используется для анализа того, влияет ли источник поступления заявки на маршрут процесса, длительность, количество возвратов или вероятность попадания в проблемный кластер

Если вам нужно использовать дополнительные признаки в кластеризации, также подготовьте поля стоимости, подразделения, канала поступления или других атрибутов, которые помогают отличать сценарии исполнения.

Чтобы загрузить таблицу в модель данных:

  1. Нажмите + Добавить таблицуИмпортировать. Импорт таблицы
  2. Выберите нужный разделитель Точка с запятой (;), квалификатор Двойные кавычки (") и кодировку UTF-8 и кликните Нажмите или перетащите файл сюда. Выбор параметров
  3. Выберите скачанную таблицу в формате .csv и нажмите Продолжить. Продолжить
  4. В открывшемся окне нажмите Импортировать. Импортировать

Таблица «cluster_case_clusters»

Создайте пустую таблицу в модели данных cluster_case_clusters со следующими колонками:

Заметка

Колонка cluster_id хранится как строка, чтобы для алгоритма OPTICS можно было сохранить специальное значение noise для шума.

  • case_id — идентификатор экземпляра процесса. Соответствует полю case_id в журнале событий и таблице кейсов. Используется для связи результата кластеризации с исходными данными процесса, чтобы можно было фильтровать процессные и аналитические виджеты по выбранному кластеру
  • cluster_id — идентификатор кластера, к которому отнесен экземпляр процесса. Хранится как строка, чтобы поддерживать не только числовые идентификаторы кластеров, но и специальные значения, например noise для шума в алгоритме OPTICS. Это основное поле для группировки, фильтрации и сравнения сценариев на дашборде
  • cluster_size — размер кластера, то есть количество экземпляров процесса, попавших в этот кластер. Позволяет определить, насколько типовым или редким является сценарий. Используется для сравнения кластеров по объему и для поиска крупных или, наоборот, аномально малых групп экземпляров процесса
  • top_event — наиболее характерное событие внутри кластера. Рассчитывается как самое частотное событие среди экземпляров процесса данного кластера. Помогает быстро понять общий характер сценария, например связан ли кластер с возвратами, согласованием или завершением
  • event_sequence — последовательность событий конкретного экземпляра процесса в текстовом виде. Представляет маршрут экземпляра процесса как упорядоченный список событий, например Create request > Check documents > Approve > Complete. Используется как один из основных признаков при кластеризации экземпляров процесса, так как именно маршрут процесса чаще всего определяет различие между сценариями
  • duration_hours — общая длительность экземпляра процесса в часах. Рассчитывается как разница между первым и последним событием экземпляра процесса. Используется как числовой признак при кластеризации и как показатель для анализа затянутых сценариев на дашборде
  • event_count — общее количество событий в экземпляре процесса. Показывает сложность маршрута: число шагов, повторяющиеся действия, дополнительные ветки согласования
  • rework_count — количество возвратов, повторных проверок или иных событий доработки в экземпляре процесса. Используется как отдельный признак проблемности процесса. Поле особенно полезно для поиска кластеров, связанных с возвратами на доработку, повторным согласованием или циклическим прохождением одних и тех же этапов
  • cost — стоимость экземпляра процесса, используемая в результате кластеризации. Обычно содержит суммарное значение стоимости по экземпляру процесса. Поле помогает выявлять отдельные сценарии для дорогих кейсов и сравнивать кластеры не только по маршруту и длительности, но и по финансовой нагрузке
  • algorithm — название алгоритма кластеризации, использованного для расчета. Например, kmeans или optics. Поле хранит информацию о способе получения текущего набора кластеров и используется для сравнения результатов разных запусков
  • silhouette_score — значение коэффициента силуэта для текущего запуска кластеризации. Используется как метрика качества разбиения на кластеры. Чем выше значение, тем лучше объекты внутри одного кластера похожи друг на друга и отличаются от объектов других кластеров. Обычно это служебный показатель уровня запуска, но он записывается в каждую строку результата для удобства отображения на дашборде
  • davies_bouldin_score — значение коэффициента Дэвиса–Болдина для текущего запуска кластеризации. Это еще одна метрика качества кластерной структуры. В отличие от коэффициента силуэта, для нее меньшие значения обычно означают более качественное разделение кластеров. Также относится к показателям уровня запуска
  • run_ts — дата и время выполнения расчета кластеризации. Показывает, когда именно был получен данный результат. Поле помогает отличать результаты разных запусков, контролировать актуальность данных и при необходимости анализировать изменения кластеров во времени

Для создания таблицы в модели данных:

  1. Нажмите + Добавить таблицуСоздать. Создание таблицы
  2. Затем заполните колонки таблицы, как показано на скриншоте ниже, и нажмите Добавить. Настройка таблицы

После создания таблицы свяжите ее в модели данных с таблицей журнала событий по полю case_id. Эта связь нужна, чтобы процессные виджеты могли принимать фильтр по кластеру через связанную таблицу:

  1. Нажмите + Добавить связь на таблице process_event_log и выберите таблицу cluster_case_clusters для создания связи.
  2. Свяжите таблицы по полю case_id и укажите тип связи Многие к одной, после нажмите Сохранить. Добавление связи

Настройка процесса в модели данных

Нужно настроить процесс в модели данных.

  1. Перейдите во вкладку Процессы, нажмите + Процесс и заполните все поля, как показано на скриншоте ниже. Добавление процесса

Подготовка пакета Python-блоков

Для реализации кластеризации в рамках кейса создается Python-блок с использованием Python SDK.

Блок будет частью пакета — структурированного набора файлов, который описывает логику работы блока, его параметры и зависимости. Пакет формируется вручную — вы создаете файлы с кодом, настраиваете зависимости и размещаете их в нужной директории на сервере.

Ниже приведена структура пакета:

/usr/sbin/infomaximum/python/
├── entrypoint.py
├── requirements.txt
└── packages/
    └── process_clustering/
        ├── __init__.py
        ├── group.py
        └── blocks/
            ├── __init__.py
            └── cluster_instances_block.py

entrypoint.py и конфигурацию контейнера оставьте стандартными для вашего шаблона Python SDK.

Настройка requirements.txt

Файл requirements.txt должен находиться в корне пакета (/usr/sbin/infomaximum/python/). Если файла нет, создайте его. В файл добавьте библиотеки, необходимые для работы Python-блока. Если в файле уже есть другие зависимости, просто допишите строки ниже в конец файла:

pandas==2.2.3
numpy==2.1.3
scikit-learn==1.5.2
gensim==4.3.3
scipy==1.14.1

Добавление группы «Кластеризация»

Группа — это логическое объединение блоков в интерфейсе конструктора скриптов. Чтобы создать группу Кластеризация:

  1. В структуре пакета откройте или создайте файл packages/process_clustering/group.py.
  2. Добавьте в него код, представленный ниже.
    from sdk.abstract_group import AbstractGroup
    import os
    
    class ProcessClusteringGroup(AbstractGroup):
        def __init__(self):
            super().__init__()
        def get_uuid(self):
            return "process_clustering_group_7d5d1e43-b1e0-4fb5-9c4b-9f8a1a41b501"
        def get_name(self):
            return {
                "en": "Clustering",
                "ru": "Кластеризация",
            }
        def get_category(self):
            return "tools"
        def get_icon(self):
            return os.path.join(
                os.path.dirname(os.path.realpath(__file__)),
                "process_clustering_group.png"
            )
    
  3. Сохраните файл. После формирования пакета группа появится в интерфейсе конструктора скриптов в категории Инструменты. Кластеризация

Добавление блока «Кластеризация экземпляров процесса»

Блок — это исполняемый компонент, который принимает входные данные, выполняет логику и возвращает результат. Чтобы добавить блок Кластеризация экземпляров процесса в созданную выше группу:

  1. В структуре пакета откройте или создайте файл packages/process_clustering/blocks/cluster_instances_block.py.
  2. Добавьте в него код, представленный ниже. В текущей реализации блок:
    • Принимает case_id, последовательность событий и числовые признаки
    • Поддерживает алгоритмы kmeans и optics
    • Рассчитывает служебные метрики качества кластеризации
    from sdk.abstract_block import AbstractBlock
    
    from collections import Counter
    from datetime import datetime
    
    import numpy as np
    import pandas as pd
    from scipy.sparse import csr_matrix, hstack
    
    from sklearn.cluster import KMeans, OPTICS
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.metrics import davies_bouldin_score, silhouette_score
    from sklearn.preprocessing import StandardScaler
    
    class ClusterProcessInstancesBlock(AbstractBlock):
        """
        Агрегационный блок.
        Принимает набор экземпляров процесса, выполняет кластеризацию
        и возвращает одну строку результата на каждый экземпляр процесса.
        """
        def __init__(self):
            super().__init__()
        def get_uuid(self):
            return "cluster_process_instances_block_3623f5c4-b8b4-48cc-8a4f-0d0cc4fa7a11"
        def get_type(self):
            return "action"
        def get_name(self):
            return {
                "en": "Clustering process instances",
                "ru": "Кластеризация экземпляров процесса",
            }
        def get_description(self):
            return {
                "en": "Clusters process instances by event sequence and numeric features",
                "ru": "Кластеризует экземпляры процесса по последовательности событий и числовым признакам",
            }
        def get_compatible_connections(self):
            return []
        def get_optionals(self):
            return {
                "is_save_data_on_fail_block_type": False,
                "is_system_block_type": False,
            }
        def get_fields(self):
            return """[
                {
                    key: 'case_id',
                    type: 'text',
                    label: 'ID экземпляра',
                    required: true
                },
                {
                    key: 'event_sequence',
                    type: 'text',
                    label: 'Последовательность событий',
                    hint: 'Например: Создание > Проверка > Согласование > Завершение',
                    required: true,
                    enableFullscreen: true
                },
                {
                    key: 'duration_hours',
                    type: 'number',
                    label: 'Длительность, часы',
                    required: true
                },
                {
                    key: 'event_count',
                    type: 'number',
                    label: 'Количество событий',
                    required: true
                },
                {
                    key: 'rework_count',
                    type: 'number',
                    label: 'Количество возвратов',
                    required: false
                },
                {
                    key: 'cost',
                    type: 'number',
                    label: 'Стоимость',
                    required: false
                },
                {
                    key: 'algorithm',
                    type: 'text',
                    label: 'Алгоритм',
                    hint: 'kmeans или optics',
                    required: true,
                    default: 'kmeans'
                },
                {
                    key: 'n_clusters',
                    type: 'text',
                    label: 'Количество кластеров',
                    hint: 'Используется для kmeans',
                    required: false,
                    default: '4'
                },
                {
                    key: 'min_samples',
                    type: 'text',
                    label: 'min_samples',
                    hint: 'Используется для optics',
                    required: false,
                    default: '5'
                },
                {
                    key: 'random_state',
                    type: 'text',
                    label: 'Начальное состояние генератора',
                    required: false,
                    default: '42'
                },
                {
                    key: 'scale_numeric',
                    type: 'boolean',
                    label: 'Масштабировать числовые признаки',
                    default: true
                },
                {
                    key: 'separator',
                    type: 'text',
                    label: 'Разделитель событий',
                    required: true,
                    default: ' > '
                }
            ]"""
        def get_block_input(self):
            return {
                "case_id": "String",
                "event_sequence": "String",
                "duration_hours": "Double",
                "event_count": "Long",
                "rework_count": "Long",
                "cost": "Long",
                "algorithm": "String",
                "n_clusters": "String",
                "min_samples": "String",
                "random_state": "String",
                "scale_numeric": "Boolean",
                "separator": "String",
            }
        def get_block_output_options(self):
            return {
                "default": [
                    {"name": "case_id", "type": "String"},
                    {"name": "cluster_id", "type": "String"},
                    {"name": "cluster_size", "type": "Long"},
                    {"name": "top_event", "type": "String"},
                    {"name": "event_sequence", "type": "String"},
                    {"name": "duration_hours", "type": "Double"},
                    {"name": "event_count", "type": "Long"},
                    {"name": "rework_count", "type": "Long"},
                    {"name": "cost", "type": "Long"},
                    {"name": "algorithm", "type": "String"},
                    {"name": "silhouette_score", "type": "Double"},
                    {"name": "davies_bouldin_score", "type": "Double"},
                    {"name": "run_ts", "type": "String"}
                ]
            }
        def get_block_output(self, data_sample: dict):
            return self.get_block_output_options()["default"]
        def get_block_aggr_mode(self, data_sample: dict) -> bool:
            return True
        def get_batch_size(self):
            return 5000
        @staticmethod
        def _safe_float(value, default=0.0):
            if value is None or value == "":
                return float(default)
            return float(value)
        @staticmethod
        def _safe_int(value, default=0):
            if value is None or value == "":
                return int(default)
            return int(float(value))
        @staticmethod
        def _safe_str(value, default=""):
            if value is None:
                return default
            return str(value)
        @staticmethod
        def _split_sequence(sequence, separator):
            if sequence is None:
                return []
            return [token.strip() for token in str(sequence).split(separator) if token.strip()]
        def process_data(self, **data):
            block_data = data["block_data"]
            df = pd.DataFrame(block_data).copy()
            if df.empty:
                return []
            """
            Нормализация входных данных
            """
            df["case_id"] = df["case_id"].apply(lambda x: self._safe_str(x))
            df["event_sequence"] = df["event_sequence"].apply(lambda x: self._safe_str(x))
            df["duration_hours"] = df["duration_hours"].apply(lambda x: self._safe_float(x, 0.0))
            df["event_count"] = df["event_count"].apply(lambda x: self._safe_int(x, 0))
            df["rework_count"] = df["rework_count"].apply(lambda x: self._safe_int(x, 0))
            df["cost"] = df["cost"].apply(lambda x: self._safe_int(x, 0))
            first_row = df.iloc[0].to_dict()
            algorithm = self._safe_str(first_row.get("algorithm"), "kmeans").strip().lower()
            if algorithm not in ("kmeans", "optics"):
                algorithm = "kmeans"
            n_clusters = max(2, self._safe_int(first_row.get("n_clusters"), 4))
            min_samples = max(2, self._safe_int(first_row.get("min_samples"), 5))
            random_state = self._safe_int(first_row.get("random_state"), 42)
            scale_numeric = first_row.get("scale_numeric", True)
            if isinstance(scale_numeric, str):
                scale_numeric = scale_numeric.strip().lower() in ("true", "1", "yes")
            else:
                scale_numeric = bool(scale_numeric)
            separator = self._safe_str(first_row.get("separator"), " > ")
            if len(df) < 2:
                run_ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
                result = []
                for _, row in df.iterrows():
                    result.append([
                        str(row["case_id"]),
                        "0",
                        1,
                        "",
                        str(row["event_sequence"]),
                        float(row["duration_hours"]),
                        int(row["event_count"]),
                        int(row["rework_count"]),
                        int(row["cost"]),
                        algorithm,
                        -1.0,
                        -1.0,
                        run_ts,
                    ])
                return result
            tokenized_sequences = [
                self._split_sequence(value, separator)
                for value in df["event_sequence"].tolist()
            ]
            joined_sequences = [
                " ".join(tokens) if tokens else ""
                for tokens in tokenized_sequences
            ]
            if not any(joined_sequences):
                joined_sequences = ["empty_sequence"] * len(joined_sequences)
            vectorizer = TfidfVectorizer(
                lowercase=False,
                token_pattern=r"[^ ]+"
            )
            text_matrix = vectorizer.fit_transform(joined_sequences)
            numeric_matrix = df[[
                "duration_hours",
                "event_count",
                "rework_count",
                "cost",
            ]].to_numpy(dtype=float)
            if scale_numeric:
                scaler = StandardScaler()
                numeric_matrix = scaler.fit_transform(numeric_matrix)
            numeric_sparse = csr_matrix(numeric_matrix)
            feature_matrix = hstack([text_matrix, numeric_sparse]).tocsr()
            """
            Кластеризация
            """
            if algorithm == "optics":
                model = OPTICS(min_samples=min_samples)
                labels = model.fit_predict(feature_matrix.toarray())
            else:
                model = KMeans(
                    n_clusters=min(n_clusters, len(df)),
                    random_state=random_state,
                    n_init=10,
                )
                labels = model.fit_predict(feature_matrix)
            df["cluster_label"] = labels
            """
            Расчет метрик
            """
            silhouette = -1.0
            davies = -1.0
            valid_mask = (df["cluster_label"] != -1).to_numpy()
            valid_labels = df.loc[valid_mask, "cluster_label"].to_numpy()
            n_samples = int(valid_mask.sum())
            n_labels = len(np.unique(valid_labels)) if n_samples > 0 else 0
            if 2 <= n_labels <= n_samples - 1:
                valid_features = feature_matrix[valid_mask].toarray()
                silhouette = float(silhouette_score(valid_features, valid_labels))
                davies = float(davies_bouldin_score(valid_features, valid_labels))
            cluster_sizes = Counter(df["cluster_label"].tolist())
            top_events = {}
            for label in cluster_sizes.keys():
                cluster_tokens = []
                for tokens, token_label in zip(tokenized_sequences, df["cluster_label"].tolist()):
                    if token_label == label:
                        cluster_tokens.extend(tokens)
                if cluster_tokens:
                    top_events[label] = Counter(cluster_tokens).most_common(1)[0][0]
                else:
                    top_events[label] = ""
            run_ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
            result = []
            for _, row in df.iterrows():
                raw_label = int(row["cluster_label"])
                cluster_id = "noise" if raw_label == -1 else str(raw_label)
                result.append([
                    str(row["case_id"]),
                    cluster_id,
                    int(cluster_sizes[row["cluster_label"]]),
                    str(top_events[row["cluster_label"]]),
                    str(row["event_sequence"]),
                    float(row["duration_hours"]),
                    int(row["event_count"]),
                    int(row["rework_count"]),
                    int(row["cost"]),
                    algorithm,
                    float(round(silhouette, 6)),
                    float(round(davies, 6)),
                    run_ts,
                ])
            return result
    
  3. Сохраните файл. После формирования пакета блок станет доступен в конструкторе скриптов в группе Кластеризация. Кластеризация экземпляров процесса

Создание скрипта кластеризации

Создайте скрипт Пересчитать кластеры экземпляров процесса. Для этого:

  1. В пространстве нажмите + ДобавитьСкрипт. Добавление скрипта
  2. В открывшемся окне введите название скрипта Пересчитать кластеры экземпляров процесса и нажмите Добавить. Добавить
  3. Добавьте блок Планировщик и настройте расписание выполнения скрипта, например, каждые 15 минут. Протестируйте блок. Планировщик
  4. Добавьте блок Очистить таблицу и укажите в нем таблицу cluster_case_clusters. Протестируйте блок. Очистить таблицу
  5. Затем добавьте блок Выбрать строки через SQL-запрос и добавьте SQL-запрос, представленный ниже. Протестируйте блок.
    SELECT
        toString(case_id) AS case_id,
        toString(
            arrayStringConcat(
                arrayMap(x -> x.2, arraySort(x -> x.1, groupArray((event_ts, event_name)))),
                ' > '
            )
        ) AS event_sequence,
        toFloat64(dateDiff('minute', min(event_ts), max(event_ts)) / 60.0) AS duration_hours,
        toInt64(count()) AS event_count,
        toInt64(countIf(event_name IN ('Return for rework', 'Repeated approval'))) AS rework_count,
        toInt64(sum(cost)) AS cost
    FROM process_event_log
    GROUP BY case_id
    
    В этом запросе:
    • event_sequence используется для векторизации последовательности событий экземпляра процесса
    • duration_hours, event_count, rework_count и cost используются как числовые признаки Выбрать строки через SQL-запрос
  6. Добавьте два одинаковых Python-блока «Кластеризация экземпляров процесса» — они должны быть подключены к блоку Выбрать строки через SQL-запрос параллельно. Заполните поля каждого из них следующим образом:
    ПолеЗначение для первого блокаЗначение для второго блока
    case_idcase_id (из предыдущего блока)case_id (из предыдущего блока)
    event_sequenceevent_sequenceevent_sequence
    duration_hoursduration_hoursduration_hours
    event_countevent_countevent_count
    rework_countrework_countrework_count
    costcostcost
    algorithmkmeansoptics
    n_clusters44
    min_samples55
    random_state4242
    scale_numerictruetrue
    separator>>
    Протестируйте каждый блок отдельно. Блок на Python
  7. Добавьте два блока Добавить строки. Каждый блок размещается сразу после Python-блока «Кластеризация экземпляров процесса» параллельно: первый блок Добавить строки — после Python-блока с алгоритмом kmeans, второй — после Python-блока с алгоритмом optics. Заполните поля каждого блока с помощью кнопки Автозаполнение полей выходными данными и укажите целевую таблицу cluster_case_clusters. Протестируйте блоки. Блок на Python
  8. Протестируйте скрипт полностью и опубликуйте его. Тестирование и публикация скрипта
  9. После публикации активируйте скрипт. Публикация скрипта
  10. Перейдите в модель данных и проверьте, что таблица cluster_case_clusters заполнена данными. Модель данных

Создание дашборда «Анализ кластеризации экземпляров процесса»

Чтобы визуализировать данные, собранные в таблицах cluster_case_clusters и process_event_log, создайте дашборд:

  1. Нажмите + ДобавитьДашборд. Дашборд
  2. В открывшемся окне введите название дашборда Анализ кластеризации экземпляров процесса и нажмите Сохранить. Сохранить
  3. Добавьте заголовок дашборда. Для этого добавьте виджет Текст и укажите название. Заголовок можно оформить с помощью Markdown и HTML-разметки, например: > ## <font color="161E54"> **Анализ кластеризации экземпляров процесса**. Текст
  4. Добавьте виджет Панель с показателями и настройте его:
    • Мера: Количество экземпляров в расчетеcount("cluster_case_clusters"."case_id")
    • Мера: Количество кластеровcountDistinct("cluster_case_clusters"."cluster_id")
    • Мера: Коэффициент силуэтаmax("cluster_case_clusters"."silhouette_score")
    • Мера: Коэффициент Дэвиса–Болдинаmax("cluster_case_clusters"."davies_bouldin_score") Добавление меры
  5. Добавьте три виджета Фильтр и настройте их:
    • Фильтр: Кластер"cluster_case_clusters"."cluster_id" Добавление фильтра
    • Фильтр: Алгоритм"cluster_case_clusters"."algorithm" Добавление фильтра
    • Фильтр: Событие"cluster_case_clusters"."event_count" Добавление фильтра
  6. Добавьте виджет Кольцевая диаграмма и настройте его:
    • Разрез: cluster_id"cluster_case_clusters"."cluster_id"
    • Мера: Количество case_idcount("cluster_case_clusters"."case_id") Добавление разреза
  7. Добавьте виджет Комбинированная диаграмма и настройте его:
    • Разрез: cluster_id"cluster_case_clusters"."cluster_id" Добавление разреза
    • Мера: Среднее duration_hoursavg("cluster_case_clusters"."duration_hours") Добавление меры
    • Мера: Медиана duration_hoursmedian("cluster_case_clusters"."duration_hours") Добавление меры
  8. Добавьте виджет Столбиковая диаграмма и настройте его:
    • Разрез: cluster_id"cluster_case_clusters"."cluster_id" Добавление разреза
    • Мера: Сумма costsum("cluster_case_clusters"."cost") Добавление меры
  9. Добавьте виджет Таблица и настройте его:
    • Разрез: ID_кластера"cluster_case_clusters"."cluster_id" Добавление разреза
    • Мера: Число экземпляровcount("cluster_case_clusters"."case_id")
    • Мера: Средняя длительностьavg("cluster_case_clusters"."duration_hours")
    • Мера: Медианная длительностьmedian("cluster_case_clusters"."duration_hours")
    • Мера: Среднее число событийavg("cluster_case_clusters"."event_count")
    • Мера: Стоимостьsum("cluster_case_clusters"."cost") Добавление меры
  10. Добавьте виджет Воронка и настройте его:
    • Этап процесса: Создание запросаОдно из событийCreate request
    • Этап процесса: Проверка документовОдно из событийCheck documents
    • Этап процесса: Возврат на доработкуОдно из событийReturn for rework
    • Этап процесса: Согласование менеджераОдно из событийManager approval
    • Этап процесса: Финансовое согласованиеОдно из событийFinance approval
    • Этап процесса: Согласование директораОдно из событийDirector approval
    • Этап процесса: Завершение процессаОдно из событийComplete Добавление этапа процесса
    • Мера: Количество событий Добавление меры

Настройка процесса для карты процесса и сферы процессов

В системе доступны процессные виджеты, включая Карту процесса и Сферу процессов.

Чтобы использовать результат кластеризации для анализа процесса:

  1. Добавьте на дашборд виджет Карта процесса. Оставьте настройки по умолчанию.
    • Показатели Количество событий, Количество переработок, Количество переходов и Медианное время добавятся автоматически Добавление показателей перехода
  2. Добавьте на дашборд виджет Сфера процессов и настройте его:
    • Добавьте процесс: Process Добавление процесса
    • Добавьте показатель: Количество переработок Добавление показателей события
    Показатели Количество событий, Количество переходов и Длительность добавятся автоматически.

В результате аналитик сначала выбирает интересующий кластер на диаграмме или в таблице, а затем сразу видит только соответствующий маршрут на Карте процесса.

  • Выбранный кластер передается в Карту процесса и Сферу процессов через стандартную фильтрацию дашборда. Таблица cluster_case_clusters связана с журналом событий process_event_log по полю case_id, поэтому при выборе значения cluster_id в диаграмме, таблице или виджете Фильтр дашборд ограничивает набор экземпляров процесса, а процессные виджеты перестраиваются только по экземплярам выбранного кластера
  • Три виджета Фильтр нужны для разных уровней отбора. Фильтр Кластер позволяет выбрать группу экземпляров процесса для анализа, фильтр Алгоритм отделяет результаты kmeans и optics, а третий фильтр дополнительно сужает выборку внутри уже выбранного набора данных. Поэтому пользователь не настраивает отображение Карты процесса вручную: она автоматически принимает фильтры, примененные к дашборду

Публикация дашборда и настройка автообновления

После настройки виджетов:

  1. Включите автообновление дашборда. Автообновление
  2. Чтобы опубликовать дашборд, нажмите Опубликовать. Опубликовать
  3. Проверьте, что после запуска скрипта обновляются все виджеты.

Анализ результатов и применение в работе

После запуска скрипта и обновления дашборда можно переходить к анализу результата. На этом этапе важно не только увидеть, что экземпляры процесса распределились по кластерам, но и понять, какие сценарии требуют отдельного внимания.

Рекомендуется анализировать результат в следующем порядке:

  1. Выберите алгоритм в фильтре Алгоритм и убедитесь, что дашборд показывает актуальный набор кластеров для выбранного расчета.
  2. Оцените качество разбиения по KPI:
    • Коэффициент силуэта — чем выше значение, тем больше объекты внутри одного кластера похожи друг на друга и отличаются от объектов других кластеров
    • Коэффициент Дэвиса–Болдина — чем ниже значение, тем лучше разделены кластеры
    Эти показатели удобны для сравнения разных запусков, например kmeans и optics, но окончательное решение лучше принимать не только по метрикам, а по содержанию кластеров и маршрутам экземпляров процесса.
  3. Сравните кластеры между собой по основным бизнес-характеристикам:
    • По количеству экземпляров — чтобы отделить массовые сценарии от редких
    • По средней и медианной длительности — чтобы найти затянутые сценарии
    • По среднему числу событий — чтобы выявить сложные маршруты с дополнительными шагами
    • По стоимости — чтобы выделить кластеры, связанные с дорогими кейсами
  4. Определите кластеры, которые стоит разбирать в первую очередь. Обычно в приоритет попадают:
    • Крупные кластеры, если сценарий массовый и влияет на большую часть процесса
    • Кластеры с повышенной длительностью, если задача состоит в сокращении времени прохождения
    • Кластеры с возвратами на доработку, если нужно уменьшить количество повторных действий
    • Кластеры с высокой стоимостью, если требуется отдельный контроль дорогих кейсов
  5. Уточните смысл выбранного кластера:
    • Используйте Таблицу, чтобы сравнить характеристики кластера с остальными
    • Используйте Воронку, чтобы понять, на каком этапе происходит основная потеря экземпляров
    • Используйте Карту процесса, чтобы увидеть фактический маршрут после фильтрации по кластеру
    • Используйте Сферу процессов, чтобы оценить, насколько сценарий разветвлен и где появляются дополнительные переходы
  6. Если для алгоритма optics появился кластер noise, проанализируйте его отдельно. Такое значение показывает, что часть экземпляров процесса не вошла в устойчивые группы. В этом кластере часто оказываются редкие, смешанные или аномальные сценарии, которые полезно рассматривать как отдельный предмет анализа.
  7. После интерпретации кластера сформулируйте рабочий вывод:
    • Какой сценарий выявлен
    • На каком этапе возникает отклонение
    • Что именно делает этот сценарий проблемным: длительность, возвраты, стоимость или комбинация факторов
    • Какой участок процесса нужно проверить в первую очередь

Результат кластеризации можно использовать не как итоговый отчет, а как инструмент приоритизации. Сначала аналитик находит кластер, который отличается по длительности, количеству возвратов или стоимости, затем открывает этот сценарий на Карте процесса и уже по схеме определяет участок процесса, который требует оптимизации.

После внесения изменений в процесс рекомендуется повторно запустить расчет и сравнить новый результат с предыдущим. Это позволяет проверить, уменьшились ли размер проблемного кластера, длительность прохождения и количество возвратов.

Пример рабочего процесса: анализ кластера с возвратами на доработку

Ситуация

Компания анализирует процесс согласования закупок. Для этого настроен дашборд Анализ кластеризации экземпляров процесса, который показывает результат расчета кластеров по экземплярам процесса.

В дашборде видно, что:

  • Виджет Кольцевая диаграмма показывает распределение экземпляров по кластерам Кольцевая диаграмма
  • Виджет Комбинированная диаграмма показывает среднюю и медианную длительность по кластерам Комбинированная диаграмма
  • Виджет Таблица показывает характеристики кластеров: число экземпляров, среднюю и медианную длительность, среднее число событий и стоимость Таблица
  • Виджет Воронка показывает прохождение по ключевым этапам процесса:
    • Create request
    • Check documents
    • Return for rework
    • Manager approval
    • Finance approval
    • Director approval
    • Complete
    Воронка
  • Виджеты Карта процесса и Сфера процессов связаны с результатом кластеризации через поле case_id, поэтому при выборе кластера аналитик сразу видит соответствующий маршрут процесса

В процессе согласования часть заявок проходит по базовому сценарию, а часть возвращается на доработку после проверки документов. Из-за этого появляются дополнительные события, увеличивается длительность кейсов и формируется отдельный сценарий исполнения экземпляра процесса.

Формирование сценария с возвратами

  • Создание заявки — сотрудник создает заявку на закупку и отправляет ее в процесс
  • Проверка документов — заявка попадает на этап Check documents
  • Возврат на доработку — если в заявке не хватает данных или вложений, она переходит на этап Return for rework
  • Повторная проверка — после исправления заявка снова возвращается на Check documents
  • Дальнейшее согласование — только после успешной проверки заявка идет дальше по этапам согласования и завершается на Complete

Пересчет кластеров

  • При запуске скрипта кластеризации:
    • Для каждого экземпляра процесса собираются последовательность событий, длительность, количество событий, количество возвратов и стоимость
    • Поле rework_count учитывает возвраты на доработку как один из признаков проблемного сценария
    • Экземпляры процесса с повторяющимся фрагментом Check documents → Return for rework → Check documents выделяются в отдельный кластер
    • Результат записывается в таблицу cluster_case_clusters и становится доступен на дашборде

Отображение результата на дашборде

  • После пересчета аналитик видит, что один из кластеров отличается от остальных:
    • По длительности — на комбинированной диаграмме он находится среди самых долгих сценариев
    • По числу событий — в таблице у него больше событий, чем у базового маршрута
    • По характеру маршрута — воронка и процессные виджеты показывают наличие возврата на доработку
  • При выборе этого кластера на Кольцевой диаграмме или в Таблице:
    • Остальные виджеты автоматически фильтруются
    • На Карте процесса остается только маршрут выбранного кластера
    • В Сфере процессов становится виден соответствующий сценарий исполнения экземпляра процесса

Возможные действия аналитика процесса

  • Аналитик процесса:
    • Открывает дашборд Анализ кластеризации экземпляров процесса
    • Сравнивает кластеры по длительности и числу событий
    • Выбирает кластер, связанный с возвратами на доработку
    • Проверяет Воронку и видит, что отклонение возникает после этапа Check documents
    • Открывает Карту процесса и видит маршрут с возвратом на Return for rework
    • Открывает Сферу процессов и убеждается, что это не единичный случай, а повторяющийся сценарий исполнения экземпляра процесса

Результат: аналитик делает вывод, что проблемный участок находится на этапе проверки документов. Именно здесь возникает возврат на доработку, который увеличивает длительность процесса и число событий в экземпляре процесса.

Дополнительные бизнес-действия

  • Владелец процесса — проверяет, по каким причинам заявки чаще всего возвращаются после Check documents
  • Руководитель закупок — уточняет требования к составу документов и правилам заполнения заявки
  • Аналитик процесса — после изменений повторно запускает расчет кластеров и сравнивает результат с предыдущим
  • Команда процесса — отслеживает, уменьшился ли размер кластера с возвратами и сократилась ли длительность его прохождения

Ключевые преимущества, продемонстрированные в процессе

  • Автоматизация — сценарий с возвратами выявляется без ручного разбора всех экземпляров процесса
  • Наглядность — кластер можно сравнить на диаграммах, в таблице, на воронке и на процессных виджетах
  • Быстрый переход к анализу причины — после выбора кластера аналитик сразу видит соответствующий маршрут на Карте процесса
  • Приоритизация улучшений — команда понимает, какой участок процесса требует оптимизации в первую очередь
  • Повторная проверка эффекта — после изменений можно пересчитать кластеры и сравнить результат

Преимущества для пользователя

РольПольза
Аналитик процессаБыстро находит кластер, в котором есть возвраты на доработку, и сразу видит соответствующий маршрут на Карте процесса
Владелец процессаПонимает, на каком этапе формируется отклонение и какой сценарий сильнее всего влияет на длительность процесса
Руководитель закупокПолучает основание для изменения правил проверки заявки и состава обязательных документов
Операционный менеджерМожет сравнить результат до и после изменений и оценить, уменьшился ли проблемный кластер

Была ли статья полезна?

Предыдущая
MS Exchange: Регулярное получение событий из календарей с помощью временной метки SyncState
Следующая
Известные ограничения

Дайджест новостей и обновлений —

один раз в месяц

Заполняя форму, я даю согласие на обработку моих персональных данных
430006, Саранск,
Северо-восточное шоссе, д. 3
ОКВЭД 62.01
ИНН 1328​909857
Код вида деятельности
в области ИТ 15.02 и 17.01
Языки программирования