Mineração de processo sem PM4PY





É muito fácil construir um gráfico a partir dos registros do processo. Actualmente, os analistas têm à sua disposição uma variedade suficiente de desenvolvimentos profissionais, como Celonis, Disco, PM4PY, ProM, etc., concebidos para facilitar o estudo de processos. É muito mais difícil encontrar desvios nos gráficos, tirar conclusões corretas deles.



E se alguns desenvolvimentos profissionais comprovados e de particular interesse não estiverem disponíveis por um motivo ou outro, ou se você quiser mais liberdade nos cálculos ao trabalhar com gráficos? É difícil escrever um minerador sozinho e implementar alguns dos recursos necessários para trabalhar com gráficos? Faremos isso na prática usando as bibliotecas Python padrão, implementaremos os cálculos e daremos, com sua ajuda, respostas a perguntas detalhadas que podem interessar aos proprietários do processo.



Eu gostaria de fazer uma reserva imediatamente que a solução dada no artigo não é uma implementação industrial. Esta é uma tentativa de começar a trabalhar com logs por conta própria com a ajuda de um código simples que funciona de forma compreensível e, portanto, facilita a adaptação. Essa solução não deve ser usada em big data; isso requer um refinamento significativo, por exemplo, usando cálculos de vetor ou alterando a abordagem de coleta e agregação de informações sobre eventos.



Antes de construir um gráfico, você precisa fazer cálculos. O cálculo real do gráfico será o mesmo mineiro mencionado anteriormente. Para realizar o cálculo, é necessário coletar conhecimentos sobre os eventos - os vértices do gráfico e as conexões entre eles e registrá-los, por exemplo, em livros de referência. As referências são preenchidas usando o procedimento de cálculo calc ( código no github) As referências completas são passadas como parâmetros para o procedimento de desenho de gráficos (veja o código do link acima). Este procedimento formata os dados conforme mostrado abaixo:



digraph f {"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit APPROVED by ADMINISTRATION (4839)" [label=4829 color=black penwidth=4.723857205400346] 
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by ADMINISTRATION (83)" [label=83 color=pink2 penwidth=2.9590780923760738] 
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by EMPLOYEE (231)" [label=2 color=pink2 penwidth=1.3410299956639813] 
start [color=blue shape=diamond] 
end [color=blue shape=diamond]}


e passa para o motor gráfico Graphviz para renderização.



Vamos começar a construir e examinar gráficos usando o minerador implementado. Repetiremos os procedimentos de leitura e ordenação dos dados, cálculo e desenho de gráficos, conforme os exemplos abaixo. Por exemplo, os registros de eventos são retirados de declarações internacionais da competição BPIC2020. Link para a competição.



Lemos os dados do registro, classificamos por data e hora. O formato .xes foi convertido anteriormente para .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)


Vamos calcular o gráfico.



dict_tuple_full = calc(df_full)


Vamos desenhar o gráfico.



draw(dict_tuple_full,'InternationalDeclarations_full')


Após completar os procedimentos, obtemos o gráfico do processo:







Como o gráfico resultante não é legível, nós o simplificamos.



Existem várias abordagens para melhorar a legibilidade ou simplificar o gráfico:



  1. usar filtragem por pesos de vértices ou links;
  2. livrar-se do ruído;
  3. agrupar eventos por similaridade de nome.


Vamos usar a abordagem 3.



Vamos criar um dicionário para combinar eventos:



_dict = {'Permit SUBMITTED by EMPLOYEE': 'Permit SUBMITTED',
 'Permit APPROVED by ADMINISTRATION': 'Permit APPROVED',
 'Permit APPROVED by BUDGET OWNER': 'Permit APPROVED',
 'Permit APPROVED by PRE_APPROVER': 'Permit APPROVED',
 'Permit APPROVED by SUPERVISOR': 'Permit APPROVED',
 'Permit FINAL_APPROVED by DIRECTOR': 'Permit FINAL_APPROVED',
 'Permit FINAL_APPROVED by SUPERVISOR': 'Permit FINAL_APPROVED',
 'Start trip': 'Start trip',
 'End trip': 'End trip',
 'Permit REJECTED by ADMINISTRATION': 'Permit REJECTED',
 'Permit REJECTED by BUDGET OWNER': 'Permit REJECTED',
 'Permit REJECTED by DIRECTOR': 'Permit REJECTED',
 'Permit REJECTED by EMPLOYEE': 'Permit REJECTED',
 'Permit REJECTED by MISSING': 'Permit REJECTED',
 'Permit REJECTED by PRE_APPROVER': 'Permit REJECTED',
 'Permit REJECTED by SUPERVISOR': 'Permit REJECTED',
 'Declaration SUBMITTED by EMPLOYEE': 'Declaration SUBMITTED',
 'Declaration SAVED by EMPLOYEE': 'Declaration SAVED',
 'Declaration APPROVED by ADMINISTRATION': 'Declaration APPROVED',
 'Declaration APPROVED by BUDGET OWNER': 'Declaration APPROVED',
 'Declaration APPROVED by PRE_APPROVER': 'Declaration APPROVED',
 'Declaration APPROVED by SUPERVISOR': 'Declaration APPROVED',
 'Declaration FINAL_APPROVED by DIRECTOR': 'Declaration FINAL_APPROVED',
 'Declaration FINAL_APPROVED by SUPERVISOR': 'Declaration FINAL_APPROVED',
 'Declaration REJECTED by ADMINISTRATION': 'Declaration REJECTED',
 'Declaration REJECTED by BUDGET OWNER': 'Declaration REJECTED',
 'Declaration REJECTED by DIRECTOR': 'Declaration REJECTED',
 'Declaration REJECTED by EMPLOYEE': 'Declaration REJECTED',
 'Declaration REJECTED by MISSING': 'Declaration REJECTED',
 'Declaration REJECTED by PRE_APPROVER': 'Declaration REJECTED',
 'Declaration REJECTED by SUPERVISOR': 'Declaration REJECTED',
 'Request Payment': 'Request Payment',
 'Payment Handled': 'Payment Handled',
 'Send Reminder': 'Send Reminder'}


Vamos agrupar os eventos e desenhar o gráfico do processo novamente.



df_full_gr = df_full.copy()
df_full_gr['concept:name'] = df_full_gr['concept:name'].map(_dict)
dict_tuple_full_gr = calc(df_full_gr)
draw(dict_tuple_full_gr,'InternationalDeclarations_full_gr'




Depois de agrupar eventos por similaridade de nome, a legibilidade do gráfico melhorou. Vamos tentar encontrar respostas para as perguntas. Link para a lista de perguntas. Por exemplo, quantas declarações não foram precedidas de uma autorização pré-aprovada?



Para responder à pergunta feita, filtramos o gráfico por eventos de interesse e desenhamos o gráfico do processo novamente.



df_full_gr_f = df_full_gr[df_full_gr['concept:name'].isin(['Permit SUBMITTED',
                                                            'Permit APPROVED',
                                                            'Permit FINAL_APPROVED',
                                                            'Declaration FINAL_APPROVED',
                                                            'Declaration APPROVED'])]
df_full_gr_f = df_full_gr_f.reset_index(drop=True)
dict_tuple_full_gr_f = calc(df_full_gr_f)
draw(dict_tuple_full_gr_f,'InternationalDeclarations_full_gr_isin')






Com a ajuda do gráfico resultante, podemos facilmente dar uma resposta à questão colocada - 116 e 312 declarações não foram precedidas de uma permissão pré-aprovada.



Além disso, você pode “falhar” (filtrar por 'caso: conceito: nome', participando do relacionamento desejado) para as conexões 116 e 312 e certificar-se de que não haverá eventos relacionados às permissões nos gráficos.



Vamos "falhar" para a comunicação 116:



df_116 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration FINAL_APPROVED'])]
df_116 = df_116.reset_index(drop=True)
dict_tuple_116 = calc(df_116)
draw(dict_tuple_116,'InternationalDeclarations_full_gr_isin_116')






Vamos "falhar" para a conexão 312:



df_312 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration APPROVED'])]
df_312 = df_312.reset_index(drop=True)
dict_tuple_312 = calc(df_312)
draw(dict_tuple_312,'InternationalDeclarations_full_gr_isin_312')






Como não há eventos relacionados às permissões nos gráficos recebidos, a correção das respostas 116 e 312 é confirmada.



Como você pode ver, escrever um minerador e implementar os recursos necessários para trabalhar com gráficos não é uma tarefa difícil, que as funções integradas de Python e Graphviz lidaram com sucesso como um mecanismo gráfico.



All Articles