Время прочтения: 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