diff --git a/dag_confs/examples_and_tests/pubtype_example.yaml b/dag_confs/examples_and_tests/pubtype_example.yaml new file mode 100644 index 0000000..fbbb00a --- /dev/null +++ b/dag_confs/examples_and_tests/pubtype_example.yaml @@ -0,0 +1,21 @@ +dag: + id: pubtype_example + description: DAG de teste (filtro por tipo de publicação) + search: + sources: + - DOU + terms: + - programa + pubtype: + - Ato + - Portaria + - Edital + - Extrato + - Ratificação + - Resolução + - Retificação + - Protocolo + report: + emails: + - destination@economia.gov.br + subject: "Teste do Ro-dou" diff --git a/docs/docs/como_funciona/exemplos.md b/docs/docs/como_funciona/exemplos.md index f8fcf0a..e9b8d96 100644 --- a/docs/docs/como_funciona/exemplos.md +++ b/docs/docs/como_funciona/exemplos.md @@ -281,4 +281,32 @@ dag: emails: - destination@gestao.gov.br subject: "Teste do Ro-dou" +``` + +### Exemplo 12 +Esta configuração filtra os resultados por tipos de publicações selecionadas. +Disponível para as pesquisas no DOU e INLABS. + +```yaml +dag: + id: pubtype_example + description: DAG de teste (filtro por tipo de publicação) + search: + sources: + - DOU + terms: + - programa + pubtype: + - Ato + - Portaria + - Edital + - Extrato + - Ratificação + - Resolução + - Retificação + - Protocolo + report: + emails: + - destination@gestao.gov.br + subject: "Teste do Ro-dou" ``` \ No newline at end of file diff --git a/docs/docs/como_funciona/parametros.md b/docs/docs/como_funciona/parametros.md index 75320f0..1d09a27 100644 --- a/docs/docs/como_funciona/parametros.md +++ b/docs/docs/como_funciona/parametros.md @@ -22,6 +22,7 @@ A página abaixo lista os parâmetros configuráveis nos arquivos YAML: - **use_summary**: Define se no relatório será exibido a ementa, se existir. Valores: True ou False. Default: False. (Funcionalidade disponível apenas no INLABS) - **ignore_signature_match**: Ignora a correspondência de assinatura ao realizar a busca. Valores: True ou False. Default: False. - **is_exact_search**: Busca somente o termo exato. Valores: True ou False. Default: True. +- **pubtype**: Lista de tipos de publicações a serem filtradas na busca. Valores: [Lista de tipos de publicações](tipos_de_publicacoes.md). - **sources**: Fontes de pesquisa dos diários oficiais. Pode ser uma ou uma lista. Opções disponíveis: DOU, QD, INLABS. - **terms**: Lista de termos a serem buscados. Para o INLABS podem ser utilizados operadores avançados de busca. - **territory_id**: Lista de identificadores do id do município. Necessário para buscar no Querido Diário. diff --git a/docs/docs/como_funciona/tipos_de_publicacoes.md b/docs/docs/como_funciona/tipos_de_publicacoes.md new file mode 100644 index 0000000..2403893 --- /dev/null +++ b/docs/docs/como_funciona/tipos_de_publicacoes.md @@ -0,0 +1,230 @@ +## Tipos de publicações (parâmetro pubtype) +- Ação Direta de Inconstitucionalidade e Ação Declaratória de Constitucionalidade +- Acórdão +- Acordo Coletivo de Trabalho +- Ajuste +- Alvará +- Aresto +- Ata +- Ato +- Ato Complementar +- Ato Concessório +- Ato Declaratório +- Ato Declaratório Conjunto +- Ato Normativo +- Ato Regimental +- Ato Regulamentar +- Auto de Infração +- Autorização +- Aviso +- Aviso aos Acionistas +- Aviso de Adiamento +- Aviso de Adjudicação +- Aviso de Alienação +- Aviso de Alteração +- Aviso de Alteração de Resultado de Habilitação +- Aviso de Anulação +- Aviso de Audiência Pública +- Aviso de Cadastramento +- Aviso de Cancelamento +- Aviso de Cancelamento de Termo Aditivo +- Aviso de Chamamento Público +- Aviso de Coleta de Preços +- Aviso de Consulta Pública +- Aviso de Convalidação +- Aviso de Credenciamento +- Aviso de Dispensa de Licitação +- Aviso de Eleição +- Aviso de Extravio +- Aviso de Fato Relevante +- Aviso de Habilitação +- Aviso de Homologação +- Aviso de Homologação e Adjudicação +- Aviso de Índice Técnico +- Aviso de Inexigibilidade de Licitação +- Aviso de Julgamento +- Aviso de Licença +- Aviso de Licitação +- Aviso de Licitação-Concorrência +- Aviso de Licitação-Convite +- Aviso de Licitação Deserta +- Aviso de Licitação-Leilão +- Aviso de Licitação-Pregão +- Aviso de Licitação-RDC Eletrônico +- Aviso de Licitação-RDC Presencial +- Aviso de Licitação-Registro de Preços +- Aviso de Licitação-Tomada de Preços +- Aviso de Nota Técnica +- Aviso de Padronização +- Aviso de Penalidade +- Aviso de Preços Registrados +- Aviso de Pré-Qualificação +- Aviso de Processo Seletivo +- Aviso de Proposta Comercial +- Aviso de Proposta Técnica +- Aviso de Prorrogação +- Aviso de Reabertura de Prazo +- Aviso de Registro de Chapas +- Aviso de Registro de Diplomas +- Aviso de Registro de Preços +- Aviso de Relação de Compras +- Aviso de Rescisão +- Aviso de Retificação +- Aviso de Revogação +- Aviso de Seleção +- Aviso de Serviço e Compra +- Aviso de Sorteio +- Aviso de Suspensão +- Aviso de Tomada de Subsídios +- Balancete +- Balancete Financeiro +- Balancete Patrimonial e Financeiro +- Balanço Patrimonial +- Balanço Social +- Certidão +- Circular +- Comunicado +- Consulta Pública +- Contrato de Gestão +- Convênio +- Decisão +- Decreto de Pessoal +- Decreto Legislativo +- Decreto não numerado +- Decreto numerado +- Deliberação +- Demonstração Contábil +- Despacho +- Despacho Interministerial +- Edital +- Edital da Justiça Gratuita (Art. 32 Portaria 268/2009-IN) +- Edital de Citação +- Edital de Concurso Público +- Edital de Convocação +- Edital de Credenciamento +- Edital de Intimação +- Edital de Leilão +- Edital de Notificação +- Edital de Processo Seletivo +- Edital de Vestibular +- Emenda +- Emenda Constitucional +- Estatuto +- Extrato +- Extrato da Ata +- Extrato de Acordo de Cooperação +- Extrato de Acordo de Cooperação Federativa +- Extrato de Acordo de Cooperação Técnica +- Extrato de Adesão +- Extrato de Ajuste +- Extrato de Apostilamento +- Extrato de Autorização de Fornecimento de Material +- Extrato de Autorização de Serviço +- Extrato de Autorização de Uso +- Extrato de Carta Contrato +- Extrato de Carta Reversal +- Extrato de Cessão +- Extrato de Cessão de Uso +- Extrato de Comodato +- Extrato de Compromisso +- Extrato de Concessão de Uso +- Extrato de Contrato +- Extrato de Convênio +- Extrato de Cooperação Mútua +- Extrato de Credenciamento +- Extrato de Denúncia +- Extrato de Depósito +- Extrato de Dispensa de Chamamento Público +- Extrato de Dispensa de Licitação +- Extrato de Distrato +- Extrato de Doação +- Extrato de Escritura de Compra e Venda +- Extrato de Extinção (Lei nº 8.745 - contratação temporária) +- Extrato de Fornecimento +- Extrato de Inexigibilidade de Licitação +- Extrato de Instrumento Convocatório +- Extrato de Instrumentos Contratuais +- Extrato de Memorando de Entendimento +- Extrato de Nota de Empenho +- Extrato de Ordem de Compra +- Extrato de Ordem de Execução de Serviço +- Extrato de Ordem de Fornecimento +- Extrato de Parceria +- Extrato de Parecer Técnico +- Extrato de Permissão de Uso +- Extrato de Permuta +- Extrato de Prorrogação de Ofício +- Extrato de Protocolo de Cooperação +- Extrato de Protocolo de Intenção +- Extrato de Recolhimento +- Extrato de Reconhecimento +- Extrato de Reconhecimento de Dívida +- Extrato de Registro de Preços +- Extrato de Rerratificação +- Extrato de Rescisão +- Extrato de Rescisão Contratual +- Extrato de Resilição +- Extrato de Sub-rogação +- Extrato de Termo Aditivo +- Extrato de Termo de Ajustamento de Conduta +- Extrato de Termo de Cooperação Técnica +- Extrato de Termo de Entrega +- Extrato de Termo de Execução de Projeto +- Extrato de Termo de Execução Descentralizada +- Extrato de Termo de Fomento +- Extrato de Termo de Ocupação de Imóvel +- Extrato de Termo de Parceria +- Extrato de Transferência de Posse +- Extrato Prévio +- Fato Relevante +- Instrução +- Instrução Normativa +- Instrução Normativa Conjunta +- Lei +- Lei Complementar +- Lista de Antiguidade +- Medida Provisória +- Memorando de Entendimento +- Mensagem +- Norma Complementar +- Ofício circular +- Ordem de Serviço +- Orientação Normativa +- Pacto +- Parecer Normativo +- Pauta +- Portaria +- Portaria Conjunta +- Portaria Intergovernamental +- Portaria Interministerial +- Processo Disciplinar +- Proposta Orçamentária +- Protocolo +- Protocolo de Intenções +- Provimento +- Ratificação +- Recomendação +- Recurso Disciplinar +- Relatórios +- Republicação +- Resolução +- Resolução do Senado Federal +- Resultado de Análise +- Resultado de Avaliação Técnica +- Resultado de Cadastramento +- Resultado de Concurso Público +- Resultado de Eleição +- Resultado de Habilitação +- Resultado de Índice Técnico +- Resultado de Julgamento +- Resultado de Julgamento de Licitação +- Resultado de Leilão +- Resultado de Proposta Técnica +- Resultado de Qualificação +- Retificação +- Retificação (de Edital) +- Solução de Consulta +- Súmula +- Súmula Administrativa +- Termo de Autorização +- Termo de Liberação de Operação diff --git a/src/dou_dag_generator.py b/src/dou_dag_generator.py index 7de77f4..8361793 100755 --- a/src/dou_dag_generator.py +++ b/src/dou_dag_generator.py @@ -280,6 +280,7 @@ def perform_searches( use_summary: Optional[bool], result_as_email: Optional[bool], department: List[str], + pubtype: List[str], **context, ) -> dict: """Performs the search in each source and merge the results""" @@ -296,6 +297,7 @@ def perform_searches( ignore_signature_match=ignore_signature_match, force_rematch=force_rematch, department=department, + pubtype=pubtype, reference_date=get_trigger_date(context, local_time=True), ) elif "INLABS" in sources: @@ -307,6 +309,7 @@ def perform_searches( ignore_signature_match=ignore_signature_match, full_text=full_text, use_summary=use_summary, + pubtype=pubtype, reference_date=get_trigger_date(context, local_time=True), ) @@ -340,6 +343,7 @@ def perform_searches( search_dict["result"] = result search_dict["header"] = header search_dict["department"] = department + search_dict["pubtype"] = pubtype return search_dict @@ -490,6 +494,7 @@ def create_dag(self, specs: DAGConfig, config_file: str) -> DAG: "full_text": subsearch.full_text, "use_summary": subsearch.use_summary, "department": subsearch.department, + "pubtype": subsearch.pubtype, "result_as_email": result_as_html(specs), }, ) diff --git a/src/hooks/dou_hook.py b/src/hooks/dou_hook.py index c3ea23a..3c0b98f 100644 --- a/src/hooks/dou_hook.py +++ b/src/hooks/dou_hook.py @@ -19,8 +19,8 @@ class DOUHook(BaseHook): - IN_WEB_BASE_URL = "https://www.in.gov.br/web/dou/-/" - IN_API_BASE_URL = "https://www.in.gov.br/consulta/-/buscar/dou" + IN_WEB_BASE_URL = "http://www.in.gov.br/web/dou/-/" + IN_API_BASE_URL = "http://www.in.gov.br/consulta/-/buscar/dou" SEC_DESCRIPTION = { Section.SECAO_1.value: "Seção 1", Section.SECAO_2.value: "Seção 2", @@ -158,6 +158,7 @@ def search_text( item["id"] = content["classPK"] item["display_date_sortable"] = content["displayDateSortable"] item["hierarchyList"] = content["hierarchyList"] + item["arttype"] = content["artType"] all_results.append(item) diff --git a/src/hooks/inlabs_hook.py b/src/hooks/inlabs_hook.py index 1bb8145..b6e1bde 100644 --- a/src/hooks/inlabs_hook.py +++ b/src/hooks/inlabs_hook.py @@ -123,6 +123,7 @@ def _generate_sql(payload: dict) -> str: "name", "pubname", "artcategory", + "arttype", "identifica", "titulo", "subtitulo", diff --git a/src/notification/email_sender.py b/src/notification/email_sender.py index 95edaa9..e463c67 100644 --- a/src/notification/email_sender.py +++ b/src/notification/email_sender.py @@ -92,14 +92,23 @@ def generate_email_content(self) -> str: blocks.append(f"

{search['header']}

") if not self.report_config.hide_filters: - if search["department"]: + if search["department"] or search["pubtype"]: blocks.append( """

Filtrando resultados somente para:

""" ) - blocks.append("") + if search["department"]: + blocks.append("Unidades:") + blocks.append("") + + if search["pubtype"]: + blocks.append("Tipos de publicações:") + blocks.append("") for group, search_results in search["result"].items(): diff --git a/src/schemas.py b/src/schemas.py index 7644792..19d8aa2 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -135,6 +135,9 @@ class SearchConfig(BaseModel): "Valores: True ou False. Default: False. " "(Funcionalidade disponível apenas no INLABS)", ) + pubtype: Optional[List[str]] = Field( + default=None, description="Lista de tipo de publicações para filtrar a pesquisa" + ) class ReportConfig(BaseModel): diff --git a/src/searchers.py b/src/searchers.py index c20ed6a..ee2c549 100644 --- a/src/searchers.py +++ b/src/searchers.py @@ -155,6 +155,7 @@ def exec_search( ignore_signature_match: bool, force_rematch: bool, department: List[str], + pubtype: List[str], reference_date: datetime, ): search_results = self._search_all_terms( @@ -167,6 +168,7 @@ def exec_search( ignore_signature_match, force_rematch, department, + pubtype ) group_results = self._group_results(search_results, term_list, department) @@ -183,6 +185,7 @@ def _search_all_terms( ignore_signature_match, force_rematch, department, + pubtype ) -> dict: search_results = {} for search_term in term_list: @@ -212,6 +215,9 @@ def _search_all_terms( self._match_department(results, department) # results = [r for r in results if any(item in r.get('hierarchyList') for item in department)] + if pubtype: + self._match_pubtype(results, pubtype) + self._render_section_descriptions(results) self._add_standard_highlight_formatting(results) @@ -305,11 +311,20 @@ def _match_department(self, results: list, department: list) -> list: """ logging.info("Applying filter for department list") logging.info(department) - logging.info(results) for result in results[:]: if not any(dpt in result["hierarchyList"] for dpt in department): results.remove(result) + def _match_pubtype(self, results: list, pubtype: list) -> list: + """Aplica o filtro nos resultados pela lista de tipos de publicações + no parâmetro 'pubtype' do YAML + """ + logging.info("Applying filter for pubtype list") + logging.info(pubtype) + for result in results[:]: + if not any(pub in result["arttype"] for pub in pubtype): + results.remove(result) + def _get_prior_and_matched_name(self, raw_html: str) -> Tuple[str, str]: groups = self.SPLIT_MATCH_RE.match(raw_html).groups() return groups[0], groups[1] @@ -429,6 +444,7 @@ def exec_search( ignore_signature_match: bool, full_text: bool, use_summary: bool, + pubtype: List[str], reference_date: datetime = datetime.now(), ) -> Dict: """ @@ -447,6 +463,7 @@ def exec_search( signature content. full_text (bool): If trim result text content use_summary (bool): If exists, use summary as excerpt or full text + pubtype (List[str]): List of publication types to filter the search. reference_date (datetime, optional): Reference date for the search. Defaults to now. @@ -457,7 +474,7 @@ def exec_search( inlabs_hook = INLABSHook() search_terms = self._prepare_search_terms(terms) search_terms = self._apply_filters( - search_terms, dou_sections, department, reference_date, search_date + search_terms, dou_sections, department, pubtype, reference_date, search_date ) search_results = inlabs_hook.search_text( @@ -489,11 +506,12 @@ def _apply_filters( search_terms: Dict, sections: List[str], department: List[str], + pubtype: List[str], reference_date: datetime, search_date: str, ): - """Apply `sections`, `departments` and `date` filters to the - search_terms dictionary.""" + """Apply `sections`, `departments`, `pubtypes` and `date` filters + to the search_terms dictionary.""" if "TODOS" in sections: search_terms["pubname"] = ["DO1", "DO2", "DO3"] @@ -501,6 +519,8 @@ def _apply_filters( search_terms["pubname"] = self._parse_sections(sections) if department: search_terms["artcategory"] = department + if pubtype: + search_terms["arttype"] = pubtype publish_from = calculate_from_datetime( reference_date, SearchDate[search_date] ).strftime("%Y-%m-%d") diff --git a/tests/inlabs_searcher_test.py b/tests/inlabs_searcher_test.py index e20f02b..2b4bdad 100644 --- a/tests/inlabs_searcher_test.py +++ b/tests/inlabs_searcher_test.py @@ -7,18 +7,20 @@ @pytest.mark.parametrize( - "search_terms, sections, department, reference_date, search_date, filters_applyed", + "search_terms, sections, department, pubtype, reference_date, search_date, filters_applyed", [ ( {"texto": ["a", "b"]}, ["SECAO_2"], ["Ministério"], + ["Edital"], datetime.now(), "DIA", { "texto": ["a", "b"], "pubname": ["DO2"], "artcategory": ["Ministério"], + "arttype": ["Edital"], "pubdate": [ datetime.now().strftime("%Y-%m-%d"), datetime.now().strftime("%Y-%m-%d"), @@ -32,13 +34,14 @@ def test_apply_filters( search_terms, sections, department, + pubtype, reference_date, search_date, filters_applyed, ): assert ( inlabs_searcher._apply_filters( - search_terms, sections, department, reference_date, search_date + search_terms, sections, department, pubtype, reference_date, search_date ) == filters_applyed ) diff --git a/tests/parsers_test.py b/tests/parsers_test.py index 91d3375..5dcf472 100644 --- a/tests/parsers_test.py +++ b/tests/parsers_test.py @@ -22,6 +22,12 @@ { "id": "basic_example", "description": "DAG de teste", + "schedule": None, + "dataset": None, + "skip_null": True, + "doc_md": None, + "tags": {"dou", "generated_dag"}, + "owner": [], "search": [ { "terms": [ @@ -43,6 +49,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -51,13 +58,6 @@ "attach_csv": False, "discord_webhook": None, "slack_webhook": None, - "schedule": None, - "dataset": None, - "description": "DAG de teste", - "skip_null": True, - "doc_md": None, - "tags": {"dou", "generated_dag"}, - "owner": [], "hide_filters": False, "header_text": None, "footer_text": None, @@ -97,6 +97,7 @@ "full_text": True, "use_summary": True, "department": None, + "pubtype": None, } ], "report": { @@ -152,6 +153,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -195,6 +197,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -250,6 +253,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -296,6 +300,7 @@ "Ministério da Gestão e da Inovação em Serviços Públicos", "Ministério da Defesa", ], + "pubtype": None, } ], "report": { @@ -339,6 +344,7 @@ "full_text": False, "use_summary": True, "department": None, + "pubtype": None, } ], "report": { @@ -386,6 +392,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -432,6 +439,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, }, { "terms": [ @@ -474,6 +482,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, }, ], "report": { @@ -520,6 +529,7 @@ "Ministério da Gestão e da Inovação em Serviços Públicos", "Ministério da Defesa", ], + "pubtype": None, } ], "report": { @@ -563,6 +573,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -609,6 +620,7 @@ "full_text": False, "use_summary": False, "department": None, + "pubtype": None, } ], "report": { @@ -626,6 +638,61 @@ }, }, ), + ( + "pubtype_example.yaml", + { + "id": "pubtype_example", + "description": "DAG de teste (filtro por tipo de publicação)", + "schedule": None, + "dataset": None, + "doc_md": None, + "tags": {"dou", "generated_dag"}, + "owner": [], + "search": [ + { + "terms": [ + "programa", + ], + "header": None, + "sources": ["DOU"], + "sql": None, + "conn_id": None, + "dou_sections": ["TODOS"], + "search_date": "DIA", + "field": "TUDO", + "is_exact_search": True, + "ignore_signature_match": False, + "force_rematch": False, + "full_text": False, + "use_summary": False, + "department": None, + "pubtype": [ + "Ato", + "Portaria", + "Edital", + "Extrato", + "Ratificação", + "Resolução", + "Retificação", + "Protocolo" + ], + } + ], + "report": { + "emails": ["destination@economia.gov.br"], + "subject": "Teste do Ro-dou", + "attach_csv": False, + "discord_webhook": None, + "slack_webhook": None, + "skip_null": True, + "hide_filters": False, + "header_text": None, + "footer_text": None, + "no_results_found_text": "Nenhum dos termos pesquisados " + "foi encontrado nesta consulta", + }, + }, + ), ], ) def test_parse(filepath, result_tuple): diff --git a/tests/searchers_test.py b/tests/searchers_test.py index 66b54b0..d2fd5e6 100644 --- a/tests/searchers_test.py +++ b/tests/searchers_test.py @@ -140,6 +140,34 @@ def test_match_department(dou_searcher): dou_searcher._match_department(results, department) assert len(results) == 1 +def test_match_pubtype(dou_searcher): + pubtype = ["Edital"] + results = [ + { + "section": "Seção 3", + "title": "EXTRATO DE COMPROMISSO", + "href": "https://www.in.gov.br/web/dou/-/extrato-de-compromisso-342504508", + "abstract": "ALESSANDRO GLAUCO DOS ANJOS DE VASCONCELOS - Secretário-Executivo Adjunto...", + "date": "02/09/2021", + "arttype": [ + "Edital", + "Ata", + "Portaria", + ], + }, + { + "section": "Seção 3", + "title": "EXTRATO DE COMPROMISSO", + "href": "https://www.in.gov.br/web/dou/-/extrato-de-compromisso-342504508", + "abstract": "ALESSANDRO GLAUCO DOS ANJOS DE VASCONCELOS - Secretário-Executivo Adjunto...", + "date": "02/09/2021", + "arttype": ["Portaria"], + }, + ] + dou_searcher._match_pubtype(results, pubtype) + assert len(results) == 1 + + @pytest.mark.parametrize( "pre_term_list, casted_term_list",