sábado, 8 de junho de 2024

Usando o difflib do Python para configurar reuniões virtuais

Introdução

O difflib é um módulo da biblioteca padrão do Python que permite comparar sequências, tais como arquivos, listas, strings e outros dados. Ele é especialmente útil para identificar e destacar diferenças entre essas sequências.

Neste post, focaremos na função get_close_matches(). Vale a pena, porém, conhecer outras funções contidas no módulo. 

Estrutura da função

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

A função get_close_matches retorna uma lista das melhores correspondências entre uma sequência de caracteres (geralmente palavras) e uma lista de possibilidades. As n respostas mais prováveis são retornadas em ordem decrescente de semelhança. O parâmetro n deve ser maior que 0, sendo 3 o valor padrão.

O ponto de corte (cutoff) é um valor float do intervalo [0, 1], com padrão em 0.6. Pontuações inferiores ao ponto de corte são ignoradas.

Um caso de uso

Durante a última campanha para Procurador-Geral, precisávamos criar reuniões virtuais no MS Teams com um número variado de colegas, às vezes ultrapassando a centena.

Possuíamos uma planilha Excel que relacionava nomes de colegas a seus respectivos e-mails funcionais. Era um documento oficial, de acesso permitido a qualquer membro da Instituição.

Então, listávamos num arquivo texto os nomes aproximados de quem, deveria ser convidado para as reuniões de campanha (às vezes incompletos ou mesmo com algum erro de grafia) e incumbíamos o Python de nos entregar os e-mails correspondentes aos nomes da lista oficial que mais se assemelhassem  aos fornecidos. O acerto chegava próximo dos 100%! 

O script relacionava os e-mails em arquivo texto, de forma que bastava colá-los no campo próprio do MS Teams para que a reunião já estivesse configurada.

O código

import pandas as pd
import difflib

df = pd.read_excel('emails_dos_membros_2024_01.xlsx')
df['Nome'] = df['Nome'].str.lower()
df['E-mail'] = df['E-mail'].str.lower()

# Relação de nomes para a reunião
arq = open("emails.txt", "r")
nomes_para_a_reuniao = ['antonio carlos']

# Itera sobre cada linha do arquivo
for linha in arq:
# Adiciona a linha à lista
nomes_para_a_reuniao.append(linha.rstrip("\n").lower())

# Fecha o arquivo
arq.close()

# Imprime a lista
print(f'Total de nomes: {len(nomes_para_a_reuniao)}')
relacao_oficial = df['Nome'].to_list()

lista_correspondencia = []

for nome in nomes_para_a_reuniao:
correspondencia = difflib.get_close_matches(nome, relacao_oficial, 1, 0.3)
try:
nome_encontrado = correspondencia[0]
lista_correspondencia.append(nome_encontrado)
print(nome, ">>>", nome_encontrado.strip())
except:
print(f'\nPulamos {nome}!!!\n')

print(f"Nomes encontrados: {len(lista_correspondencia)}")
e_mails = []

for index, row in df.iterrows():
for nome in lista_correspondencia:
if row['Nome']==nome:
e_mails.append(row['E-mail'])

# Saída
nome_arquivo = "saida.txt"

with open(nome_arquivo, 'w') as arquivo:
for e_mail in e_mails:
arquivo.write(e_mail + '\n')


Explicação do código

Primeiro, o código importa as bibliotecas necessárias (pandas para manipulação de dados e difflib para comparação de strings). Em seguida, carrega um arquivo Excel contendo nomes e e-mails, converte os valores das colunas 'Nome' e 'E-mail' para letras minúsculas para garantir a consistência nas comparações, e exibe uma amostra dos dados.

Depois, abre um arquivo de texto contendo nomes adicionais e adiciona esses nomes a uma lista preexistente, convertendo cada nome para minúsculas. Em seguida, imprime o total de nomes nessa lista combinada.

Para cada nome na lista combinada, o código usa difflib.get_close_matches para encontrar a correspondência mais próxima na lista de nomes do DataFrame (lista oficial), com um índice de similaridade mínimo. Se encontrar uma correspondência, adiciona o nome correspondente a uma nova lista e imprime a correspondência. Se não encontrar, imprime uma mensagem indicando que o nome foi pulado, para que se possa fazer a inclusão manual. Após isso, imprime o total de nomes encontrados.

Em seguida, o código percorre cada linha do DataFrame, verificando se o nome na linha corresponde a algum nome na lista de correspondência. Se houver correspondência, adiciona o e-mail correspondente a uma lista de e-mails.

Finalmente, o código escreve os e-mails encontrados em um arquivo de texto, um por linha.

Em conclusão: o código compara listas de nomes (lista oficial e lista fornecida), encontra as correspondências e extrai os e-mails associados.

quinta-feira, 28 de março de 2024

Consulta com Python à API pública do DataJud (Base de Dados do Poder Judiciário) do CNJ

O Conselho Nacional de Justiça - CNJ disponibiliza uma API pública que fornece os metadados e movimentações dos processos judiciais não gravados de sigilo. A documentação da ferramenta está disponível aqui.

A API permite pesquisas pelo número de processo ou por classe processual e órgão julgador. A resposta pode conter até 10.000 registros por página.

Cada tribunal tem seu "endpoint" específico. Na documentação da API encontramos a URL do TJSP e a chave pública para a consulta. Usando o Python, criamos uma solicitação POST para obter os dados sobre ANPPs.

Sabendo que a API utiliza o Elasticsearch - uma linguagem de consulta que permite recuperar informações segundo determinados critérios de pesquisa - restringimos a requisição à classe 12729 (que corresponde à "Execução de Medidas Alternativas"). Com o método response.json(), convertemos a resposta da API (em JSON) em dicionário Python, facilitando a criação do dataframe.
Mais à frente, utilizamos a busca pela string "Acordo de Não Persecução Penal" na coluna "assuntos" para restringir o dataframe ao que, de fato, nos interessava (dados sobre os ANPPs).

O código também converteu todos os horários para o fuso de São Paulo.

A partir desse ponto, foi possível agrupar as informações por períodos de tempo e horários, para as devidas análises e plotagens.
Descobrimos, assim, que; - 23,31% dos ajuizamentos de ANPP ocorrem fora do horário de expediente.
- De agosto de 2023 para cá houve uma sensível queda do número de ANPPs ajuizados no Estado de São Paulo, o que talvez se explique em razão da nova regulamentação do ANPP que foi estabelecida no âmbito do MPSP.

O notebook com o código usado na análise pode ser acessado a partir do meu GitHub. Experimente rodá-lo no Colab.

Uma vez que a coluna "movimentos" do dataframe pode conter o Cód: 12735, relativo à extinção da punibilidade pelo cumprimento do ANPP, pode ser interessante, num projeto futuro, verificar qual a porcentagem de acordos cumpridos e o tempo médio para que isso ocorra.










segunda-feira, 20 de novembro de 2023

Gerenciando ambientes virtuais com Conda

O Conda – que vem com o Anaconda ou Miniconda – facilita o gerenciamento de ambientes virtuais para os diferentes projetos em Python.

Uma vez disponível (para conferir se está ok: conda --version), podemos construir, ativar, alternar ou deletar os ambientes com os seguintes comandos:


1. Criar novo ambiente virtual:

conda create --name meu_ambiente python=3.8

conda create --name meu_ambiente


2. Ativar o ambiente virtual:

conda activate meu_ambiente


3. Instalar pacotes:

conda install numpy

ou

pip install numpy


4. Listar ambientes:

conda env list


5. Desativar ambiente:

conda deactivate


6. Remover um ambiente:

conda env remove --name meu_ambiente


Outras dicas:


7. Criação de um arquivo yml com as dependências do ambiente: 

conda env export > environment.yml (recomendo excluir a linha iniciada por “prefix”, que se refere ao caminho local do seu sistema de arquivos)

ou

pip freeze > requirements.txt (padrão do pip)


Obs. : para recriar ambiente a partir de environment.yml:

conda env create -f environment.yml


8. Mantenha o Conda atualizado: 

conda update conda


quinta-feira, 9 de novembro de 2023

Em breve, nós promotores de justiça compartilharemos nossos próprios GPTs

Quando ingressei no Ministério Público, era comum recebermos dos Centros de Apoio (CAOs) alguns CDs (de 650 MB) com modelos de peças e jurisprudência. 

Com a expansão da Internet e a implantação do portal institucional, a forma de compartilhar conhecimento no âmbito do MPSP mudou completamente. Nossa capacidade atual de fazer o reuso de teses e reaproveitar as peças elaboradas pelos colegas está diretamente relacionada à organização do site e à eficácia do mecanismo buscador. 

Há um “game changing” acontecendo nessa área. 

No evento “DevDay” do último dia 6, a primeira conferência direcionada a desenvolvedores promovida pela OpenAI, foram anunciadas inovações ao ChatGPT que permitirão, mesmo aos não desenvolvedores, criar versões customizadas do bot, denominadas GPTs (Generative Pre-trained Transformers), para tarefas específicas. 

O ChatGPT poderá, agora, se conectar a bases de dados, aprender com dados pessoais e acessar a internet. Não será preciso escrever código para que criemos soluções próprias destinadas a minutar denúncias, alegações finais e outras manifestações... e com o nosso estilo. Esses bots poderão ser publicados numa plataforma – a “GPT Store” – e compartilhados. 

É urgente, portanto, que conheçamos a política de privacidade dessas soluções e que a alta gestão do MPSP se atente ao tema.

Pensando no MPSP como um todo, precisaremos, também, ter em nossos quadros engenheiros de dados (para coletar, preparar e organizar as informações corporativas para treinamento de IAs) e engenheiros de softwares (para implementar as soluções em produção). 

Para saber mais: https://openai.com/blog/introducing-gpts

quarta-feira, 26 de julho de 2023

Operações com datas e horas usando Python

Com Python, fazemos operações com datas e horas usando as bibliotecas datetime e relativedelta.

A biblioteca datetime possui as seguintes classes principais: date, time, datetime e timedelta

A classe date representa uma data, a classe time representa um horário e a classe datetime representa uma combinação de data e horário. A classe timedelta é usada para realizar operações matemáticas com datas e tempos. Representa, portanto, um intervalo de tempo (com dias, horas, minutos e segundos).

Uma estratégia bastante usada nos programas é converter, previamente, as strings representativas de datas em objetos datetime para em seguida realizar as operações desejadas. Vamos fazer isso, calculando há quantos dias estou vivo.


Primeiro obtemos a data de hoje:

>>> from datetime import datetime, timedelta

>>> hoje = datetime.now()

>>> print(hoje)

2023-07-26 07:01:20.328655


Depois, convertemos a string em objeto datetime:

>>> nascimento = '24/03/1966'

>>> data_nasc = datetime.strptime(nascimento, "%d/%m/%Y")

>>> print(type(data_nasc))

<class 'datetime.datetime'>


Agora, é só calcular…

>>> dias_vividos = hoje - data_nasc

>>> print(dias_vividos)

20943 days, 7:01:20.328655

>>> print(dias_vividos.days)

20943


A classe relativedelta é mais poderosa que a timedelta, pois permite lidar com unidades de tempo maiores, como meses e anos, além dos componentes habituais de tempo, como dias, horas e minutos. Deve ser importada de from dateutil.relativedelta.

Com ela podemos, por exemplo, adicionar ou subtrair uma semana, um mês ou um ano de uma determinada data, considerando as diferenças de dias contidos nos meses e com anos bissextos.


Usando a classe relativedelta, poderíamos obter o seguinte resultado para o tempo vivido.

>>> from dateutil.relativedelta import relativedelta

>>> relativedelta(data_nasc, hoje)

relativedelta(years=-57, months=-4, days=-2, hours=-7, minutes=-1, seconds=-21, microseconds=+671345)


Poderíamos calcular o termo final de um prazo penal (contando o dia do início e excluindo o dia do fim) de 1 ano e 6 meses a partir de hoje da seguinte forma:

>>> termo_final = hoje + relativedelta(years=1, months=6) + relativedelta(days= -1)

>>> print(termo_final)

2025-01-25 07:01:20.328655


Ou, com uma saída mais amigável:

>>> print(termo_final.strftime("%d/%m/%Y"))

25/01/2025


domingo, 9 de julho de 2023

Roteiro para a criação de contêiner Docker para uma aplicação com Python

Quero rodar uma aplicação escrita em Python com suas dependências em um contêiner Docker, construído do zero.

Para isso, preciso:

1 - Definir um diretório de trabalho na máquina local.

2 - Construir a imagem Docker, a partir das instruções do arquivo "Dockerfile" (sem extensão) e das dependências listadas no arquivo "requirements.txt".

3 - Construir a imagem. Nesta fase, são instaladas as dependências encontradas em "requirements.txt".

4 - Executar o contêiner, mapeando a pasta local com a pasta do contêiner onde roda a aplicação.

Só isso!