Skip to content

Commit

Permalink
Merge pull request #121 from UnB-KnEDLe/segmentation-test
Browse files Browse the repository at this point in the history
Segmentation test
  • Loading branch information
Khalil09 authored May 27, 2021
2 parents 71fc763 + bf04d77 commit 9053a37
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 127 deletions.
2 changes: 1 addition & 1 deletion dodfminer/extract/polished/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def extract_segments(path):
bar = tqdm.tqdm(total=len(files), desc="[Segmentation] Progress")
i = 1
segments = ''
print(files)
for file in files:
text = ContentExtractor.extract_text(file)
# import pdb; pdb.set_trace()
segments += '\n' + Segmentation.extract_segments(text, 'Aposentadoria')
i += 1
bar.update(1)
Expand Down
44 changes: 1 addition & 43 deletions dodfminer/extract/polished/segmentation/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,48 +128,6 @@ def decode(self, x):

### Metrics

def relaxed_f1_score(model, dataloader, device):
TP = [0 for _ in range(len(model.num_classes))]
FP = [0 for _ in range(len(model.num_classes))]
FN = [0 for _ in range(len(model.num_classes))]

# for sent, tag, word, mask in dataloader:
# sent = sent.to(device)
# tag = tag.to(device)
# word = word.to(device)
# pred, _ = model.eval().decode(sent, word)
print("Still ongoing development!")

def exact_f1_score(model, dataloader, device):
TP = np.array([0 for _ in range((model.num_classes-2)//2)])
FP = np.array([0 for _ in range((model.num_classes-2)//2)])
FN = np.array([0 for _ in range((model.num_classes-2)//2)])

for x, y, mask in dataloader:
x = x.to(device)
y = y.to(device)
pred, _ = model.eval().decode(x)

batch_size = pred.shape[0]
for i in range(batch_size):
predicted_entities = find_entities(pred[i])
real_entities = find_entities(y[i])
for entity in predicted_entities:
if entity in real_entities:
TP[(entity[2]//2)-1] += 1
else:
FP[(entity[2]//2)-1] += 1
for entity in real_entities:
if entity not in predicted_entities:
FN[(entity[2]//2)-1] += 1

precision = TP/(TP+FP+0.000001)
recall = TP/(TP+FN+0.000001)
f1 = 2*(precision*recall)/(precision+recall)

occurrences = TP + FN
return occurrences, f1

def find_entities(tag):
entities = []
prev_tag = 1
Expand Down Expand Up @@ -263,7 +221,7 @@ def get_acts(predictions, sentences):

class Segmentation:
@staticmethod
def extract_segments(text, act_name):
def extract_segments(text, act_name='Aposentadoria'):
if act_name in model_cover_list:
if not os.path.isfile(os.path.dirname(__file__) + '/model/cnn_cnn_lstm.pt'):
print('[Segmentation] The model is not present in local files Downloding...')
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ coveralls==2.1.2
torch==1.7.1
nltk==3.5
gdown==3.12.2
pysocks
Binary file added tests/support/DODF 195 14-10-2020 INTEGRA.pdf
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/support/DODF 195 14-10-2020 INTEGRA.pdf:Zone.Identifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://www.dodf.df.gov.br/index/visualizar-arquivo/?pasta=2020/10_Outubro/DODF%20195%2014-10-2020&arquivo=DODF%20195%2014-10-2020%20INTEGRA.pdf
HostUrl=https://www.dodf.df.gov.br/index/visualizar-arquivo/?pasta=2020/10_Outubro/DODF%20195%2014-10-2020&arquivo=DODF%20195%2014-10-2020%20INTEGRA.pdf
8 changes: 4 additions & 4 deletions tests/support/dodf_pdfs/cessoes.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
,nome,matricula,cargo_efetivo,classe,padrao,orgao_cedente,orgao_cessionario,onus,fundamento legal,processo_SEI,vigencia,matricula_SIAPE,cargo_orgao_cessionario,simbolo,hierarquia_lotacao
0,JULIO CESAR MENEGOTTO,74.682-7,,,,,,"onus para o orgao
0,SADI PERES MARTINS,79.206-3,,,,,,"ONUS FINANCEIRO: orgao cessionario, com ressarcimento mensal a origem.",,-,,, resolve: SUSPENDER,,
1,JULIO CESAR MENEGOTTO,74.682-7,,,,,,"onus para o orgao
de origem, conforme Decisao da Diretoria Executiva, exarada pela Sessao no 4.",,00112-00037276/2019-21.,,, usando das atribuicoes conferidas pelo Art. 25,,
1,ARLETE OLIVEIRA SANTOS GONDAR,124.604-6,,,,,,ONUS FINANCEIRO: orgao cedente.,,00138-00007294/2019-45,,, com alicerce no art. 2o,,
2,ROBERT WAGNER DE SANTANA,1.430.783-9,,,,,,ONUS FINANCEIRO: orgao cedente.,,04019-00000669/2019-17.,,, com alicerce no art. 2o,,
3,SADI PERES MARTINS,79.206-3,,,,,,"ONUS FINANCEIRO: orgao cessionario, com ressarcimento mensal a origem.",,-,,, resolve: SUSPENDER,,
2,ARLETE OLIVEIRA SANTOS GONDAR,124.604-6,,,,,,ONUS FINANCEIRO: orgao cedente.,,00138-00007294/2019-45,,, com alicerce no art. 2o,,
3,ROBERT WAGNER DE SANTANA,1.430.783-9,,,,,,ONUS FINANCEIRO: orgao cedente.,,04019-00000669/2019-17.,,, com alicerce no art. 2o,,
10 changes: 5 additions & 5 deletions tests/support/dodf_pdfs/sem_efeito_aposentadoria.csv
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
,tipo_ato,tipo_documento,numero_documento,data_documento,numero_dodf,data_dodf,pagina_dodf,nome,matricula,matricula_SIAPE,cargo_efetivo,classe,padrao,quadro,orgao,processo_SEI,tipo_edicao
0,Atos tornados sem efeito - aposentadoria,Ordem de Servico,,03 de dezembro de 2019,04,04 de dezembro de 2019,," FRANCINEIDE
DANIEL DE LIMA",66.260-7,, no Cargo de Professor de Educacao Basica,,,,,00040-00024368/2019-32,normal
1,Atos tornados sem efeito - aposentadoria,,,26 de fevereiro de 2019,42,28 de fevereiro 2019, 29,GENI TEREZINHA SPIES DA SILVEIRA,30735-1,, totalizando 731 dias,,,,,00401.00003406/2019-59,normal
2,Atos tornados sem efeito - aposentadoria,,,,137,19/07/2017,. 42, DELFINO BERNARDES RABELO,100.652-5. ,,,,,,,0070-001865/2016,normal
3,Atos tornados sem efeito - aposentadoria,,,05 de fevereiro de 1990,248,"29 de
0,Atos tornados sem efeito - aposentadoria,,,,137,19/07/2017,. 42, DELFINO BERNARDES RABELO,100.652-5. ,,,,,,,0070-001865/2016,normal
1,Atos tornados sem efeito - aposentadoria,,,05 de fevereiro de 1990,248,"29 de
dezembro de 2017", 39.,"MARCO
ANTONIO CATTANI FRANCA","129661-2,",, 129.661-2,,,,,271.000.680/2017,normal
2,Atos tornados sem efeito - aposentadoria,Ordem de Servico,,03 de dezembro de 2019,04,04 de dezembro de 2019,," FRANCINEIDE
DANIEL DE LIMA",66.260-7,, no Cargo de Professor de Educacao Basica,,,,,00040-00024368/2019-32,normal
3,Atos tornados sem efeito - aposentadoria,,,26 de fevereiro de 2019,42,28 de fevereiro 2019, 29,GENI TEREZINHA SPIES DA SILVEIRA,30735-1,, totalizando 731 dias,,,,,00401.00003406/2019-59,normal
145 changes: 145 additions & 0 deletions tests/test_extract_polished_segmentation_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Todos os imports necessários para testar o nosso módulo de segmentação.

# A classe ContentExtractor é responsável por extrair o texto puro dos Pdfs.
from dodfminer.extract.pure.core import ContentExtractor

# Essa é a nossa classe principal do core de Segmentação. Ela é o nosso foco de teste.
from dodfminer.extract.polished.segmentation.core import Segmentation
import os

# Os arquivos necessários para fazer os testes sempre estarão dentro da pasta "tests/support".
# Coloquei um DODF na pasta support que tenho certeza que o modelo encontra segmentos de aposentadoria nele.
file_not_empty = ""+os.path.dirname(__file__)+"/support/DODF 195 14-10-2020 INTEGRA.pdf"


# O modelo n encontra modelos de aposentadoria neste DODF.
file_empty = ""+os.path.dirname(__file__)+"/support/DODF 001 01-01-2019 EDICAO ESPECIAL.pdf"

"""
Começaremos os testes agora.....
Toda função de teste deve começar com prefixo "test_". Isso que indica para
a biblioteca que essa função testa alguma coisa no nosso código.
Outro padrão é o nome auto explicativo, ou seja, vcs irão descrever brevemente no
nome da função o que ela testa. Dessa maneira, caso ocorra algum erro, o pytest
apresenta o nome da função que falhou. Assim se torna mais fácil identificar
onde esta o erro.
Inicialmente testaremos somente a classe Segmentation. Ela possui somente
uma função "extract_segments" e é a classe que utiliza todo o código contido no core.
Se garantirmos que ela funciona podemos ter uma maior confiança no resto
do código do módulo. Portanto, testaremos todas as declarações que
existem dentro dela, ou seja, todos os possíveis caminhos que são tomados dentro dela.
Antes de testar qualquer função é importante entender as entradas e as saídas dela.
A função "Segmentation.extract_segments", recebe um texto e um argumento act_name como entrada,
ambos tipo string. Tal função esta localizada no core do módulo segmentation. O caminho
até o módulo se apresenta na parte dos imports.
O argumento "text" é o texto que o módulo usará para extrair os segmentos. E "act_name" é o nome
do ato q está sendo extraido. A função usa isso pra checar se o modelo atual
tem treinamento para extrair o ato desejado. Essa informação é passada para o código manualmente
na variável "model_cover_list" definida no início do código do módulo. ATUALMENTE SÓ TEMOS
O MODELO TREINADO COM OS ATOS DE APOSENTADORIA.
Caso o modelo atual n tenha treinamento do ato solicitado a função avisa o usuário
e retorna o texto originalmente passado como argumento.
"""


"""
Nota Geral: Vcs podem checar as mensagens que foram apresentadas no terminal utilizando:
captured = capsys.readouterr()
E passando como argumento da função de teste a variável "capsys". "def teste(capsys)"
"""



"""
Como exemplo, fiz o teste da funcionalidade mais básica da função. Aqui eu suponho que
a função funcionara da maneira correta com todos os parâmetros corretos.
Para isso utilizo o arquivo "file_not_empty" na qual sei que existem segmentos nele.
Eu sei q a saída dessa função quando é correta se apresenta como uma lista de strings.
Então como um teste básico, basta eu checar se o tamanho da lista de retorno é maior que 0.
"""
def test_segmentation_extract_segments_act_in_cover_list():
# Perceba que a função extract_segments não extrai o texto dos pdfs, quem faz isso
# é um outra classe do DODFMiner o ContentExtractor. E pode ser usada como na linha abaixo.
text = ContentExtractor.extract_text(file_not_empty)
# Retirado o texto do PDF agora podemos passar para a função a ser testada.
segments = Segmentation.extract_segments(text, 'Aposentadoria')
# A linha assert dirá para o pytest se o teste foi bem sucedido.
assert len(segments) > 0

# THAIS
def test_segmentation_extract_segments_act_not_in_cover_list(capsys):
"""
Nesse teste faremos o teste do primeiro if da função fazendo a pergunta:
E se o modelo n tiver sido treindo com o ato passado?
Tecnicamente, e se act_name n estiver em model_cover_list?
Se checarmos o resultado vemos que ela deve retornar um texto logo depois de uma mensagem.
Então nossos "asserts" devem checar tanto se a mensagem foi apresentada quanto se o texto esta
presente no retorno.
"""
text = ContentExtractor.extract_text(file_not_empty)
segments = Segmentation.extract_segments(text, 'reversoes')
captured = capsys.readouterr()
assert '[Segmentation] The current model is not trained with this act yet. Changing to pure text' in captured.out
assert segments == text

# DANIEL
def test_segmentation_extract_segments_none_act_found():
"""
Nesse teste checaremos se ao colocar um DODF em que ele n encontra nenhum segmento, ele
retorna corretamente uma mensagem e o texto original no DODF. Igual ao teste de cima
somente muda a mensagem que mostra para o usuário.
"""
text = ContentExtractor.extract_text(file_empty)

segmentation = Segmentation.extract_segments(text, 'Aposentadoria')

assert segmentation == text

# IAN
def test_segmentation_extract_segments_successful_correct_message(capsys):
"""
Esse teste é um tanto simples, ele repete o que o primeiro teste faz, porém ao invés
de checar se os segmentos foram extraidos, ele checará se as 3 mensagens para o usuário aparecem
caso ele consiga extrair corretamente os segmentos.
"""
text = ContentExtractor.extract_text(file_not_empty)
segments = Segmentation.extract_segments(text, 'Aposentadoria')

captured = capsys.readouterr()

assert '[Segmentation] Segmentation is being applied...' and '[Segmentation] Finished.' in captured.out

# GUILHERME
def test_segmentation_extract_segments_first_argument_not_text(capsys):
"""
Nesse teste faremos o teste se o código realmente retorna alguma exceção caso
não seja passado um texto como argumento primário.
"""
segments = Segmentation.extract_segments('Aposentadoria')

captured = capsys.readouterr()

assert '[Segmentation] No act found by segmentation returning the entire text' in captured.out

# GUILHERME
def test_segmentation_extract_segments_if_model_is_not_present():
"""
Essa função faz a extração de segmentos utilizando um modelo de IA, portanto esse modelo
precisa ser carregado de algum lugar já que não vem durante a instalação da biblioteca.
Esse teste irá checar se a biblioteca realmente efetua o download do modelo caso ele n esteja presente
nos arquivos da biblioteca. Como solução proponho excluir o modelo existente
(Ver na função qual o caminho para o modelo) e executá-la para extrair normalmente.
Se ao final vc obtiver os segmentos dentro de file_not_empty o assert é correto.
"""
assert True == True


74 changes: 0 additions & 74 deletions vitor.py

This file was deleted.

0 comments on commit 9053a37

Please sign in to comment.