Serviço simples que permite testar um conjunto de regras pré-definidas com seus rótulos correspondentes, simulando um classificador multilabel só que no lugar do modelo, tem-se um conjunto de regras textuais. Daí pode-se identificar fluxos automáticos para sistemas, definir alertas, etc.
-
O componente pode ser usado baixando a pasta componente e instalando o componente, depois baixando o exemplo do serviço na pasta app
-
Esse é um serviço simples de exemplo do uso da classe de avaliação de regras para gerar um classificador multilabel por regras.
-
O arquivo regras.json contém uma lista de regras de exemplo. As regras podem estar em um banco de dados que o serviço carrega ao subir, ou em um arquivo texto mesmo. Depois basta chamar o serviço passando o texto que ele retorna os rótulos aplicáveis com base nas regras carregadas.
-
A responsabilidade do serviço é rotular o texto recebido, comportando-se como um classificador multilabel por regras.
-
Opcionalmente pode-se informar ao serviço que regras devem ser testadas, passando uma tag ou conjunto de tags ou o nome do grupo da regra ou outros tipos de filtros.
-
É possível usar regex no lugar dos critérios textuais para regras mais refinadas. Para isso, basta registrar a regra com r: no início da regra. Ex.: r:oficio \d+
-
No caso de regex, existem expressões prontas para facilitar o uso como CPF, CNPJ e podem ser personalizadas. Para usar uma expressão pronta, basta criar a regra r:| por exemplo. O conjunto de expressões prontas está em UtilExtracaoRe().PRONTOS {"CPF" : "...", ...}
-
O serviço de exemplo está nesta pasta, basta baixá-lo.
Exemplos de regras:
- casa adj2 papel remover(aspas) remover(casa do papel)
- oficio adj5 remetido remover(de oficio)
{"texto": "esse é um texto legal", "criterio": " texto PROX10 legal", "detalhar": 1, "grifar":1}
Retorno
{ "criterios": "texto PROX10 legal",
"criterios_analise": " texto PROX10 legal",
"criterios_aon": "texto AND legal",
"retorno": true,
"tempo": 0.001,
"texto": "esse e um texto legal",
"texto_analise": "esse é um texto legal",
"texto_grifado": "esse é um <mark>texto</mark> <mark>legal</mark>"}
{"texto": "esse texto tem umas receitas de pão e de bolos legais 123 456 um dois três com o oficio número 5.174", "detalhar": 0 }
Retorno
{ "extracoes": [{"fim": 41,"inicio": 22,"texto": "oficio numero 5.174"}],
"rotulos": ["oficio","Receita de Bolo","Receita de Pao"],
"tempo": 0.003
}
- POST: http://localhost:8000/analisar_regras
- o detalhar=1 nesse exeplo retorna a regra identificada por cada rótulo e o texto processado
- a chave opcional tags pode ser usada para filtrar e avaliar apenas regras que contenham uma das tags configuradas
- a chave opcional grupo pode ser usada para filtrar e avaliar apenas regras de um determinado grupo
- qualquer chave criada no arquivo de regras pode ser usada para filtras o grupo de regras que será aplicado
{"texto": "esse ofício 12 texto tem umas receitas de pão e de bolos legais 123 456 um dois são vários testes três com o oficio número 5.174",
"detalhar":0, "tags":"oficio"}
Retorno
{ "extracoes": [{"fim": 14,"inicio": 5,"texto": "oficio 12"},
{"fim": 41,"inicio": 22,"texto": "oficio numero 5.174"}],
"rotulos": ["oficio"],
"tempo": 0.001
}
Regras desse exemplo (arquivo regras.json):
- a chave tags, qtd_cabecalho, qtd_rodape são opcionais
- regra: é a regra usando os operadores de pesquisa textual, ou um regex. No caso de regex, a regra deve começar com r: regex desejado
- rotulo: é o rótulo do grupo que será retornado se a regra retornar true
- qtd_cabecalho: a regra é aplicada no início do texto até o caracter da posição informada
- qtd_rodape: a regra é aplicada no final do texto, do caracter da posição informada até o fim
- qtd_cabecalho e qtd_rodape: a regra é aplicada removento o miolo do texto de qtd_cabecalho até qtd_rodape
- ordem é opcional, as regras são carregadas ordenadas pelo nome do grupo e depois por esse campo se existir. A ordenação afeta o retorno de rótulos únicos de grupos com o parâmetro
primeiro_do_grupo: True
, fazendo com que as outras regras do grupo sejam ignoradas quando a primeira for verdadeira, retornando apenas um rótulo por grupo.
{"regras": [
{"grupo" : "receitas_bolo", "rotulo": "Receita de Bolo", "regra": "receita ADJ10 bolo", "tags": "receita bolo", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "receita"},
{"grupo" : "receitas_bolo", "rotulo": "Receita de Bolo", "regra": "aprenda ADJ5 fazer ADJ10 bolo", "tags": "receita bolo", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "receita"},
{"grupo" : "receitas_pao", "rotulo": "Receita de Pao", "regra": "receita PROX15 pao", "tags": "receita pao", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "receita"},
{"grupo" : "grupo_teste", "rotulo": "teste", "regra": "teste", "tags": "teste", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "grupo"},
{"grupo" : "grupo_regex", "rotulo": "teste regex", "regra": "r:teste|testar?", "tags": "teste", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "grupo"},
{"grupo" : "grupo_oficio", "rotulo": "oficio", "regra": "r:oficio\\s?(n.{0,10})?\\d+", "tags": "teste oficio", "qtd_cabecalho":20, "qtd_rodape":20, "filtro_tipo" : "grupo"},
{"grupo" : "grupo_erro", "rotulo": "teste erro regra", "regra": "regra nao (nao" , "filtro_tipo" : "erro"},
{"grupo" : "grupo_erro", "rotulo": "teste erro regex", "regra": "r: (?)" , "filtro_tipo" : "erro"},
{"grupo" : "grupo_aspas_termo", "rotulo": "teste aspas termo", "regra": "usando nao testando remover('testando')" , "filtro_tipo" : "grupo"},
{"grupo" : "grupo_aspas", "rotulo": "teste_aspas", "regra": "teste remover(aspas)" , "filtro_tipo" : "grupo"},
{"grupo" : "grupo_multi", "rotulo": "multi2", "regra": "R:multi", "tags": "multi", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "multi", "ordem": 1},
{"grupo" : "grupo_multi", "rotulo": "multi1", "regra": "multi", "tags": "multi", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "multi", "ordem": 0},
{"grupo" : "grupo_multi", "rotulo": "multi3", "regra": "multi", "tags": "multi", "qtd_cabecalho":0, "qtd_rodape":0, "filtro_tipo" : "multi", "ordem": 3}
]
}
- Você pode ter uma base de dados em algum BD com as regras e o serviço carregar as regras dessa base. É mantido um cache de 5min para o serviço não ficar carregando as regras o tempo todo o que inviabilizaria um volume grande de acessos ao serviço.
- Abra o arquivo
app_config.py
- Identifique no final do arquivo as configurações disponibilizadas
- aponte o objeto
obj_regras_model
para o objeto desejado para carga de regras e conversão de dados de saída - crie novos padrões prontos de regex para serem aproveitados nas regras, como o exemplo abaixo para OAB.
###################################################################################
# configurando novos padrões prontos de regex
from pesquisabr import UtilExtracaoRe
novos_prontos = {'OAB' : r'\b([a-z]{2}[\.\-,; ]?\d{2,7}|\d{2,7}[\.\-,; ]?[a-z]{2})\b'}
UtilExtracaoRe.PRONTOS.update(novos_prontos)
###################################################################################
# define o tipo de objeto de regras que será carregado
# pode-se criar um tipo personalizado carregando do banco ou de onde for necessário
from regras_model import RegrasModelArquivo
obj_regras_model = RegrasModelArquivo()
- Abra o arquivo
regras_model.py
- verifique a classe
RegrasModelBase
que é usada para carregar as regras e converter os dados de entrada e/ou saída do serviço, podendo ser personalizadas conforme necessário
- verifique a classe
class RegrasModelBase():
# sobrescrever esse método para retornar uma lista de regras
# pode carregar do banco, do disco, de outro serviço, etc
# [{"grupo" : "nome_grupo", "rotulo": "rotulo1", "regra": "critérios da regra", "tags": "receita bolo", "qtd_cabecalho":0, "qtd_rodape":0, "ordem": 0},]
# incluir algum filtro com a chave filtro_tipo facilita testes na tela exemplo do serviço
def get_regras_db(self):
return []
# pode-se transforar os dados de saída do serviço com base em alguma
# regra específica do contexto da aplicação
# pode-se tratar a chave regras com as regras aplicadas, a chave rótulos, etc
def conversao_retorno(self, retorno: dict):
pass
# pode-se transforar os dados de entrada no serviço com base em alguma
# regra específica do contexto da aplicação
# pode-se converter dados ou incluir outras regras de negócio específicas
# front_end, analisar_regras, analisar_criterios indicam o contexto dos dados de entrada
def conversao_entrada(self, dados: dict, front_end = False, analisar_regras=False, analisar_criterios=False):
pass
...
# Exemplo de personalização da classe
class RegrasModelArquivo(RegrasModelBase):
ARQ_REGRAS = './regras.json' # arquivo texto no formato [{regra}, {regra}, {regra}]
def get_regras_db(self):
regras = []
if os.path.isfile(self.ARQ_REGRAS):
with open(self.ARQ_REGRAS,mode='r') as f:
regras = f.read()
regras = json.loads(regras.strip())
regras = regras.get('regras',[])
return RegrasPesquisaBR.ordenar_regras(regras)
# converte os dados retornados pelo controller
def conversao_retorno(self, retorno: dict):
# para manter a compatibilidade com os testes básicos do serviço
# os testes preenchem uma chave 'rodando-testes' para
# que essa informação possa ser usada no controle de conversão
if 'rodando-testes' in retorno:
print('Em teste - ignorando conversão de retorno')
return
# exemplo para injetar uma chave com os grupos retornados
# poderia buscar outros dados no BD, em outro serviço, transformar dados, etc
if 'regras' in retorno:
regras = retorno['regras']
grupos = []
for r in regras:
if r.get('grupo') and r.get('grupo') not in grupos:
grupos.append(r.get('grupo'))
retorno['grupos'] = grupos
# converte os dados de entrada para o serviço
def conversao_entrada(self, dados: dict, front_end = False, analisar_regras=False, analisar_criterios=False):
# para manter a compatibilidade com os testes básicos do serviço
if 'rodando-testes' in dados:
print('Em teste - ignorando conversão de entrada')
return
# front-end de exemplo do serviço, para não ser impactado pela conversão de saída
if front_end or analisar_criterios:
return
# exemplo para converter uma chave em tags de pesquisa
# ou retirar dados desnecessários dos resultados
# ou tratar dados antes de devolver
# poderia buscar outros dados no BD, em outro serviço, transformar dados, etc
if analisar_regras:
if 'valor_filtro' in dados:
dados['tags'] = f"{dados.get('tags','')} {dados['valor_filtro']}".strip()
dados.pop('valor_filtro')
# remove as chaves sem valor de filtro - chaves enviadas como null por exemplo
# para não serem filtradas nas chaves das regras
for chave in list(dados.keys()):
if dados[chave] is None:
dados.pop(chave)
# força o resultado sempre detalhado
dados['detalhar'] = 1