Время прочтения: 4 мин.

Анализ процессов не может ограничиваться анализом графов. Попробуем проанализировать логи процесса и выполнить мониторинг отклонений без применения графовой аналитики, но с использованием так называемых “слов процесса” в вариантах реализации процесса.

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

Пусть у нас есть лог файл. Для примера взят лог событий по международным декларациям из соревнования BPIC2020. Ссылка на соревнование https://icpmconference.org/2020/bpi-challenge/.

Считаем данные из лога, отсортируем их по дате и времени. Предварительно формат .xes преобразован в .xlsx.

df_full = pd.read_excel('InternationalDeclarations.xlsx')
df_full = df_full[['id-trace','concept:name','time:timestamp']]
df_full.columns = ['case:concept:name', 'concept:name', 'time:timestamp']
df_full['time:timestamp'] = pd.to_datetime(df_full['time:timestamp'])
df_full = df_full.sort_values(['case:concept:name','time:timestamp'], ascending=[True,True])
df_full = df_full.reset_index(drop=True)

Для упрощения кодирования «слов процесса», которое нам предстоит выполнить далее, уменьшим количество вариантов событий (не путать с вариантами развития процесса или с цепочками событий), для этого создадим словарь объединения событий и примапим его:

_dict = {'Permit SUBMITTED by EMPLOYEE': 'Permit SUBMITTED',
 'Permit APPROVED by ADMINISTRATION': 'Permit APPROVED',
 'Permit APPROVED by BUDGET OWNER': 'Permit APPROVED',

. . .

 'Request Payment': 'Request Payment',
 'Payment Handled': 'Payment Handled',
 'Send Reminder': 'Send Reminder'}

df_full_gr = df_full.copy()
df_full_gr['concept:name'] = df_full_gr['concept:name'].map(_dict)

!!! Маппинг выполняется только в данном конкретном примере и только лишь для упрощения иллюстрации самого примера !!!

Далее получим варианты развития процесса, они же цепочки событий:

variant = df_full_gr.groupby('case:concept:name')['concept:name'].agg(lambda col: ' -> '.join(col))
df_variant = pd.DataFrame(variant)
df_variant.columns = ['variant']

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

Permit SUBMITTED -> Permit APPROVED -> Permit FINAL_APPROVED -> Start trip -> End trip -> Declaration SUBMITTED -> Declaration APPROVED -> Declaration FINAL_APPROVED -> Request Payment -> Payment Handled

Permit SUBMITTED -> Permit APPROVED -> Permit APPROVED -> Permit FINAL_APPROVED -> Start trip -> End trip -> Declaration SUBMITTED -> Declaration APPROVED -> Declaration APPROVED -> Declaration FINAL_APPROVED -> Request Payment -> Payment Handled

Start trip -> End trip -> Permit SUBMITTED -> Permit APPROVED -> Permit APPROVED -> Permit FINAL_APPROVED -> Declaration SUBMITTED -> Declaration REJECTED -> Declaration REJECTED -> Declaration SUBMITTED -> Declaration APPROVED -> Declaration APPROVED -> Declaration FINAL_APPROVED -> Request Payment -> Payment Handled

Очевидно, что с такими вариантами работать неудобно, поэтому прибегнем к использованию «слов процесса»:

import string
graph_word_dict = {}
for index,x in enumerate(df_full_gr['concept:name'].value_counts().keys()):
    graph_word_dict[x] = string.ascii_uppercase[index]
    print(string.ascii_uppercase[index]+' '+x)
df_full_gr['short:concept:name'] = df_full_gr['concept:name'].map(graph_word_dict)

А вот и сами «слова процесса»:

A Declaration SUBMITTED
B Permit APPROVED
C Declaration APPROVED
D End trip
E Start trip
F Declaration FINAL_APPROVED
G Permit SUBMITTED
H Payment Handled
I Request Payment
J Permit FINAL_APPROVED
K Declaration REJECTED
L Permit REJECTED
M Send Reminder
N Declaration SAVED

Снова получим варианты и выведем их на экран:

variant_short = df_full_gr.groupby('case:concept:name')['short:concept:name'].agg(lambda col: ''.join(col))
df_variant_short = pd.DataFrame(variant_short)
df_variant_short.columns = ['variant']

df_variant_short['variant'].value_counts()

GBJEDACFIH                     1689
GBBJEDACCFIH                    652
GJEDAFIH                        361
GBJEDAKKACFIH                   350
GBBJEDAKKACCFIH                 208
                               ... 
GBBJEDAKKACKKAKKAKKACCFIH         1
EDGBBLLGBJACFIH                   1
GEDBBJMAKKAKKACCFIH               1
GBEDBJMACFIH                      1
EGBBJACDCFIH                      1
Name: variant, Length: 621, dtype: int64

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

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

no_dev = ['GBJEDACFIH','GBBJEDACCFIH','GJEDAFIH','GBJEDAKKACFIH','GBBJEDACFIH',
        'EDGBJACFIH','EDGBBJACCFIH','ACFIHED','GBJEDMACFIH','GJEDACFIH']

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

df_variant_short[~df_variant_short['variant'].isin(no_dev)]['variant'].value_counts()

Вот они:

GBBJEDAKKACCFIH         208
AFIHED                  111
ACCFIHED                 87
GBEJDACFIH               86
GBBBJEDACCFIH            64
                       ... 
GJAEKDKAFIH               1
GBJEACDCFIH               1
ACFEIHD                   1
GBBJEDAKKAKKAKKACFIH      1
EDGLLGJAFIH               1
Name: variant, Length: 611, dtype: int64

Далее по вариантам можно вычислить соответствующие кейсы

list = df_variant_short[~df_variant_short['variant'].isin(no_dev)].index

И, например отфильтровать исходный лог по полученным кейсам

df_full[df_full['case:concept:name'].isin(list)].head()

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

Ссылка на код: https://github.com/evgshtina1976/pm/blob/main/Variant%26WordProcess.ipynb